7.6 KiB
Dựa trên hình ảnh giao diện image.png và các yêu cầu cải tiến trải nghiệm người dùng (UX) của bạn, dưới đây là kế hoạch Markdown chi tiết để triển khai cử chỉ vuốt đồng bộ trên ảnh, tạo hiệu ứng chớp màn hình khi chụp (Shutter Flash) và chuyển toàn bộ tác vụ xử lý ảnh AI/Merge xuống nền (Background Processing) để tối ưu hóa hiệu năng.
🛠️ KẾ HOẠCH CẢI TIẾN UX: ĐỒNG BỘ CỬ CHỈ SWIPE & XỬ LÝ ẢNH CHỤP DƯỚI NỀN
Mục tiêu:
- Cho phép người dùng vuốt trực tiếp trên vùng ảnh xem trước (Camera Preview) để thay đổi thông số Ruler (cho cả Presets màu và Độ nhòe Blur) theo cùng một chiều đồng nhất: Sang phải = Tăng, Sang trái = Giảm.
- Loại bỏ hoàn toàn vòng xoay Loading (UI Freeze) gây ức chế khi bấm nút Shutter.
- Tạo hiệu ứng màn hình chớp trắng (Flash Effect) và hiệu ứng thu nhỏ ảnh (Fly-to-Thumbnail) bay về góc dưới bên trái để thay thế biểu tượng Media (như vị trí icon Gallery trong file
image.png).
📐 1. Cải Tiến Cử Chỉ Vuốt Đồng Nhất Trên Ảnh (Gesture Control)
Chúng ta sẽ bao bọc vùng kính ngắm CameraPreview bằng một Widget GestureDetector để bắt sự kiện kéo ngang (onHorizontalDragUpdate). Cần tính toán một hệ số nhạy (Sensitivity) để bước chuyển số được mượt mà.
💻 Đoạn mã cấu trúc cử chỉ vuốt (CameraScreen.dart):
double _dragStartX = 0.0;
Widget _buildCameraPreviewWithGestures() {
return GestureDetector(
// Bắt đầu chạm vào ảnh
onHorizontalDragStart: (details) {
_dragStartX = details.globalPosition.dx;
},
// Thực hiện kéo (Swipe)
onHorizontalDragUpdate: (details) {
// Tính toán khoảng cách dịch chuyển (dx dương = sang phải, dx âm = sang trái)
double deltaX = details.delta.dx;
// Hệ số nhạy điều chỉnh (Càng nhỏ thì vuốt phải dài hơn mới tăng/giảm nhiều)
double sensitivity = 0.5;
double changeValue = deltaX * sensitivity;
setState(() {
if (_currentTab == EditorTab.portraitBlur) {
// 1. Nếu đang ở tab Xóa nền -> Tăng giảm độ nhòe (_blurIntensity từ 0.0 -> 1.0)
_blurIntensity = (_blurIntensity + (changeValue / 100.0)).clamp(0.0, 1.0);
} else if (_currentTab == EditorTab.presets && _selectedAttribute != null) {
// 2. Nếu đang ở tab Presets -> Tăng giảm thông số slider đang chọn hiện tại
_updateCurrentPresetAttribute(changeValue);
}
});
},
child: AspectRatio(
aspectRatio: 3 / 4,
child: Stack(
children: [
CameraPreview(_cameraController),
if (_selectedBgMode == BackgroundMode.blur) _buildLiveBlurOverlay(),
// Lớp hiệu ứng chớp màn hình (Flash Effect)
if (_showFlashEffect) _buildFlashOverlay(),
],
),
),
);
}
⚡ 2. Hiệu Ứng Chớp Máy & Thu Nhỏ Ảnh Về Biểu Tượng Media
Thay vì khóa màn hình bằng vòng quay Loading, ngay khi nhấn nút Shutter, ứng dụng sẽ thực hiện song song 2 việc: Chớp sáng màn hình và bất đồng bộ hóa luồng lưu ảnh.
🎥 Bước 2.1: Tạo hiệu ứng chớp trắng (Shutter Flash)
bool _showFlashEffect = false;
String? _latestThumbnailPath; // Đường dẫn ảnh vừa chụp xong để làm thumbnail
void _triggerShutterAnimation() {
setState(() {
_showFlashEffect = true;
});
// Tự động tắt lớp màu trắng sau 100 miligiây để tạo cảm giác chớp máy như máy ảnh thật
Future.delayed(const Duration(milliseconds: 100), () {
setState(() {
_showFlashEffect = false;
});
});
}
Widget _buildFlashOverlay() {
return Positioned.fill(
child: Container(color: Colors.white),
);
}
💻 Bước 2.2: Cập nhật Panel Shutter ở đáy màn hình
Dựa theo thiết kế trong ảnh image.png, nút Media nằm ở góc dưới bên trái của nút Shutter chính. Chúng ta sẽ thay biểu tượng mặc định bằng ảnh vừa chụp dạng bo góc tròn (Thumbnail preview).
Widget _buildMediaButton() {
return Container(
width: 45,
height: 45,
decoration: BoxDecoration(
color: Colors.grey[900],
borderRadius: BorderRadius.circular(8),
image: _latestThumbnailPath != null
? DecorationImage(
image: FileImage(File(_latestThumbnailPath!)),
fit: BoxFit.cover,
)
: null,
),
child: _latestThumbnailPath == null
? const Icon(Icons.image, color: Colors.white, size: 24) // Icon mặc định như trong ảnh mẫu
: null,
);
}
⚙️ 3. Kiến Trúc Xử Lý Ẩn Dưới Nền (Background Work Pipeline)
Để nút Shutter phản hồi ngay lập tức, toàn bộ tiến trình xử lý nặng (AI nhận diện, tách nền, áp thông số sliders, nén tệp) sẽ được tách hoàn toàn ra khỏi luồng giao diện chính (UI Thread) bằng cách sử dụng Flutter Isolate (compute).
Future<void> onShutterPressed() async {
// 1. Kích hoạt hiệu ứng chớp máy ngay lập tức
_triggerShutterAnimation();
try {
// 2. Chụp ảnh thô thật nhanh từ phần cứng camera để lấy file tạm
final XFile rawPhoto = await _cameraController.takePicture();
// 3. Cập nhật ngay lập tức ảnh thô này làm ảnh đại diện cho nút Media (Tạo cảm giác phản hồi tức thì)
setState(() {
_latestThumbnailPath = rawPhoto.path;
});
// 4. ĐẨY LỆNH XỬ LÝ NẶNG XUỐNG LUỒNG NGẦM (BACKGROUND ISOLATE)
// Máy không xuất hiện vòng xoay, người dùng có thể tiếp tục chỉnh sửa hoặc chụp phát tiếp theo
_runBackgroundProcessingPipeline(
rawPath: rawPhoto.path,
bgMode: _selectedBgMode,
blurValue: _blurIntensity,
bgAsset: _selectedBgAssetPath,
presetJson: _currentPreset.toJson(),
isFrame: _isFrameApplied,
);
} catch (e) {
print("Lỗi chụp ảnh: $e");
}
}
// Hàm chạy ngầm tách biệt, không gây lag kính ngắm camera
void _runBackgroundProcessingPipeline({required String rawPath, ...}) async {
// Thực hiện gọi luồng ngầm qua Isolate tính toán:
// - Quét AI bóc tách viền tóc mảnh mượt mà
// - Áp dụng dải màu sliders hoành tráng
// - Ép đè khung instaxframe.png
// - Cuối cùng gọi Gal.putImage() để ghi âm thầm vào thư viện máy.
}
📅 4. Các Đầu Việc Cần Triển Khai (Checklist)
- Đồng bộ hóa chiều vuốt: Kiểm tra kỹ xem khi vuốt sang phải, giá trị trên thanh Ruler của tab
portraitBlurvà các thuộc tính trongpresetscó tăng tiến đồng đều không hay có cái nào bị ngược hướng. - Tối ưu hóa bộ đệm (Thumbnail Cache): Đảm bảo tệp ảnh hiển thị ở nút Media được thu nhỏ (Kích thước khoảng 100x100 pixel) để không làm hao tổn bộ nhớ RAM khi hiển thị xem trước ở góc trái ảnh
image.png. - Kiểm thử chụp liên tiếp (Burst Shot Test): Thử nghiệm nhấn chụp liên tục 3-4 lần để kiểm tra xem hệ thống hàng đợi (Queue) dưới nền có xử lý tuần tự và lưu đủ số lượng ảnh vào Gallery hệ thống mà không gây văng ứng dụng hay không.