buyme_on_device
Flutter SDK chạy model AI trên thiết bị (on-device) để kiểm định / chụp ảnh xe theo thời gian thực qua camera — nhận diện góc xe, bộ phận và biển số.
Mục lục
- Cài đặt
- OpenCV (
opencv_dart) - Cấu hình nền tảng (Permissions)
- Bắt đầu nhanh
- Hai cách sử dụng:
BuyMeOnDevicevsBuyMeOnDeviceCamera - Chi tiết các tham số cấu hình
- Callback sự kiện:
onPhotoCaptured,onComplete,onError,onImageUploaded - Quản lý phiên chụp & Khôi phục (Session Recovery)
- Cache ảnh phiên chụp
Cài đặt
Thêm vào pubspec.yaml của app:
dependencies:
buyme_on_device: ^0.1.1
Sau đó import:
import 'package:buyme_on_device/buyme_on_device.dart';
Yêu cầu Flutter
>=3.38.0/ Dart>=3.10(YOLO native + Native Assets hooks cho OpenCV).
OpenCV (opencv_dart)
SDK dùng opencv_dart (^2.2.1) để kiểm tra chất lượng ảnh real-time trên Dart layer (blur / độ sáng / chói — glare). Native camera chỉ sample + encode JPEG nhỏ; thuật toán OpenCV chạy hoàn toàn phía Dart.
Bạn không cần thêm opencv_dart thủ công vào app — dependency đã nằm trong package. Host app chỉ cần đủ Flutter/Dart version hỗ trợ Native Assets / hooks.
Yêu cầu
| Mục | Giá trị |
|---|---|
| Flutter | >=3.38.0 |
| Dart SDK | >=3.10.0 |
| Package | opencv_dart: ^2.2.1+4 (transitive) |
| Modules dùng | imgproc, imgcodecs (core luôn có sẵn) |
Hooks đã cấu hình sẵn trong package
Trong pubspec.yaml của buyme_on_device:
hooks:
user_defines:
dartcv4:
include_modules:
- imgproc
- imgcodecs
Từ
opencv_dart2.2 trở đi, mặc định build OpenCV từ source (không còn prebuilt static binary). Lần build đầu có thể lâu và cần toolchain native (CMake / NDK trên Android, Xcode trên iOS). Các lần sau dùng cache.
Lưu ý khi integrate app
- Giữ Flutter
>=3.38—opencv_dart2.x không chạy trên Flutter cũ (v1.x là nhánh riêng). - Lần
flutter run/flutter buildđầu tiên trên máy có thể mất nhiều phút để biên dịch OpenCV. - Không cần copy thêm permission riêng cho OpenCV; nó dùng frame JPEG đã có từ camera pipeline của SDK.
- Nếu app cũng dùng
opencv_dart/dartcv4trực tiếp, có thể khai báo thêmhooks.user_defines.dartcv4ởpubspec.yamlcủa app — tránhexclude_moduleslàm mấtimgproc/imgcodecsvì SDK phụ thuộc hai module này.
Cấu hình nền tảng (Permissions)
SDK sử dụng camera, vị trí (geolocation) và lưu ảnh vào gallery. Cần khai báo quyền cho từng nền tảng.
iOS — ios/Runner/Info.plist
<key>NSCameraUsageDescription</key>
<string>Ứng dụng cần camera để chụp ảnh kiểm định xe</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Ứng dụng cần vị trí để gắn toạ độ ảnh chụp</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Ứng dụng cần lưu ảnh đã chụp vào thư viện</string>
Android — android/app/src/main/AndroidManifest.xml
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
SDK tự yêu cầu quyền (camera, vị trí) khi cần.
Bắt đầu nhanh
import 'package:buyme_on_device/buyme_on_device.dart';
import 'package:flutter/material.dart';
void openInspection(BuildContext context) {
final config = BuyMeConfig(
generalConfig: GeneralConfig(
apiToken: '<API_TOKEN>', // bắt buộc — liên hệ để được cấp
documentId: '<DOCUMENT_ID>', // bắt buộc — ID hồ sơ (so_id_hs)
organization: AiCycleOrg.aicycle,
environment: BuyMeEnvironment.stage,
),
carInformation: CarInformation(
companyName: 'toyota',
modelName: 'vios',
licensePlate: '30A12345',
),
modelConfig: ModelConfig(), // dùng ngưỡng mặc định
validateConfig: const ValidateConfig(
angleMode: CaptureAngleMode.sixAngles, // hoặc CaptureAngleMode.eightAngles (8 góc có thêm 2 góc sườn)
requireFullAnglePanoramicPhotos: false,
requireLicensePlate: true, // mặc định: bắt buộc đọc được biển trước khi auto-capture
),
);
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => BuyMeOnDeviceCamera(
buyMeConfig: config,
onComplete: () => Navigator.pop(context),
onError: (error) => debugPrint('SDK Error: $error'),
onImageUploaded: (data) => debugPrint('Uploaded: $data'),
),
),
);
}
Hai cách sử dụng: BuyMeOnDevice vs BuyMeOnDeviceCamera
SDK cung cấp 2 widget điểm vào (entry point). Cả hai đều nhận buyMeConfig, onComplete, onError, onImageUploaded. Khác nhau ở luồng chuẩn bị model:
| Tiêu chí | BuyMeOnDevice |
BuyMeOnDeviceCamera |
|---|---|---|
| Màn hình quản lý / chọn model trước khi mở camera | ✅ Có (ModelManagerScreen) |
❌ Không |
| Tự tải & chuẩn bị model | Người dùng chọn model rồi mới tải | Tự tải toàn bộ model cần thiết |
| Phù hợp khi | Muốn người dùng xem/chọn model đã tải | Muốn vào thẳng camera, đơn giản nhất |
| Cách dùng | Push widget vào Navigator | Push widget vào Navigator |
Bên trong,
BuyMeOnDevicesau khi người dùng chọn model sẽ tự push sangBuyMeOnDeviceCamera. Nếu không cần màn chọn model, dùng thẳngBuyMeOnDeviceCamera.
1. BuyMeOnDevice (widget)
Hiển thị màn Model Manager để người dùng xem/chọn model trước, sau đó vào camera.
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => BuyMeOnDevice(
buyMeConfig: config,
onComplete: () => Navigator.pop(context),
onError: (error) => showError(error),
onImageUploaded: (data) { /* xử lý từng ảnh upload thành công */ },
),
),
);
Tham số:
| Tên | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
buyMeConfig |
BuyMeConfig |
✅ | Toàn bộ cấu hình SDK (xem bên dưới). |
onComplete |
void Function()? |
❌ | Gọi khi hoàn tất luồng (sau khi upload xong). |
onError |
void Function(String error)? |
❌ | Gọi khi có lỗi (khởi tạo hồ sơ, tải model, …). |
onImageUploaded |
OnImageUploaded? |
❌ | Gọi mỗi khi một ảnh upload thành công, kèm JSON server trả về. |
Nút back trên màn Model Manager được điều khiển bằng DisplayConfig.showBackButton.
2. BuyMeOnDeviceCamera (widget)
Điểm vào trực tiếp: tự tạo/lấy hồ sơ (folder), tự tải & chuẩn bị model rồi vào thẳng camera. Đây là cách dùng phổ biến nhất.
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => BuyMeOnDeviceCamera(
buyMeConfig: config,
onComplete: () => Navigator.pop(context),
onError: (error) => showError(error),
onImageUploaded: (data) { /* ... */ },
),
),
);
Tham số:
| Tên | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
buyMeConfig |
BuyMeConfig |
✅ | Toàn bộ cấu hình SDK. |
onComplete |
void Function()? |
❌ | Gọi khi hoàn tất luồng (sau khi upload ảnh). |
onError |
void Function(String error)? |
❌ | Gọi khi có lỗi. |
onImageUploaded |
OnImageUploaded? |
❌ | Gọi mỗi khi một ảnh upload thành công. |
carCornerModelPath |
String? |
❌ | Đường dẫn model "góc xe" có sẵn (bỏ trống → SDK tự tải). |
carPartModelPath |
String? |
❌ | Đường dẫn model "bộ phận xe" có sẵn (bỏ trống → SDK tự tải). |
carQualityModelPath |
String? |
❌ | Đường dẫn model "chất lượng ảnh" (hiện tạm tắt trên UI). |
licensePlateModelPath |
String? |
❌ | Đường dẫn model OCR biển số (bỏ trống → SDK tự tải). |
Các tham số
*ModelPathchủ yếu dùng nội bộ (khi đã có model từ màn chọn model). Bình thường không cần truyền — SDK tự tải.
Model đang bật: carCorner, carPart, licensePlate (carQuality tạm tắt).
Chi tiết các tham số cấu hình
BuyMeConfig
Đối tượng gốc gom toàn bộ cấu hình.
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
generalConfig |
GeneralConfig |
✅ | — | Cấu hình chung (token, hồ sơ, môi trường…). |
modelConfig |
ModelConfig |
✅ | — | Ngưỡng confidence / IOU cho từng model. |
carInformation |
CarInformation |
✅ | — | Thông tin xe được kiểm định. |
displayConfig |
DisplayConfig |
❌ | DisplayConfig() |
Tuỳ chỉnh hiển thị. |
validateConfig |
ValidateConfig |
❌ | ValidateConfig() |
Tuỳ chỉnh ràng buộc chụp ảnh (chế độ 6 góc / 8 góc, biển số…). |
vbiConfig |
VBIConfig? |
⚠️ | null |
Bắt buộc khi organization == AiCycleOrg.vbi. |
initialPhotos |
Map<int, List<String>>? |
❌ | null |
Map các góc đã chụp trước đó (angleId -> [filePath]) để khôi phục phiên chụp và tiếp tục chụp các góc còn lại (0..5 với 6 góc, 0..7 với 8 góc). |
final config = BuyMeConfig(
generalConfig: GeneralConfig(...),
modelConfig: ModelConfig(),
carInformation: CarInformation(...),
displayConfig: const DisplayConfig(showBackButton: true),
validateConfig: const ValidateConfig(
angleMode: CaptureAngleMode.sixAngles, // hoặc CaptureAngleMode.eightAngles
requireFullAnglePanoramicPhotos: true,
requireLicensePlate: true,
minCarSpanThreshold: 0.35,
maxCarSpanThreshold: 0.85,
),
initialPhotos: {
0: ['/path/to/front.jpg'], // Góc 0 đã có ảnh -> SDK bỏ qua góc này và chụp tiếp
},
vbiConfig: null, // hoặc VBIConfig(...) nếu organization là vbi
);
GeneralConfig (bắt buộc)
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
apiToken |
String |
✅ | — | Token API (liên hệ để được cấp). |
documentId |
String |
✅ | — | ID hồ sơ (so_id_hs). |
organization |
AiCycleOrg |
✅ | — | Tổ chức dùng SDK: aicycle, vbi, others. |
environment |
BuyMeEnvironment |
❌ | develop |
Môi trường: develop, stage, production. |
documentName |
String? |
❌ | null |
Tên hồ sơ. |
loggingEnabled |
bool |
❌ | false |
Bật/tắt log của SDK. |
savePhotoAfterShot |
bool |
❌ | true |
Có lưu ảnh đã chụp vào gallery của máy không. |
alwaysCache |
bool |
❌ | false |
Khi thoát màn camera, giữ cache ảnh đã chụp và không hiện dialog xác nhận xoá cache. |
aicBaseUrl |
String? |
❌ | null |
Base URL API theo domain đối tác (on-premise). |
GeneralConfig(
apiToken: '<API_TOKEN>',
documentId: '<DOCUMENT_ID>',
organization: AiCycleOrg.aicycle,
environment: BuyMeEnvironment.production,
documentName: 'Hồ sơ kiểm định xe',
loggingEnabled: false,
savePhotoAfterShot: true,
alwaysCache: false,
// Ví dụ override domain API cho môi trường đối tác.
// aicBaseUrl: 'https://partner-api.example.com',
);
⚠️ Với
organization: AiCycleOrg.vbi,aicBaseUrllà bắt buộc và phải khác rỗng.
CarInformation (bắt buộc)
Tất cả các trường đều tùy chọn (có giá trị mặc định là chuỗi rỗng / null), nhưng nên điền đầy đủ để hồ sơ chính xác.
| Trường | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
companyName |
String |
'' |
Hãng xe, ví dụ "toyota". |
modelName |
String |
'' |
Dòng/hiệu xe, ví dụ "vios". |
manufacturingYear |
int? |
null |
Năm sản xuất, ví dụ 2022. |
vehicleVersionName |
String |
'' |
Phiên bản xe, ví dụ "1.5C". |
licensePlate |
String |
'' |
Biển số xe, ví dụ "30A12345". |
vehicleType |
String? |
null |
Loại xe, ví dụ "sedan", "pickup". |
color |
String? |
null |
Màu xe dạng hex #RRGGBB, ví dụ "#A2A8A1". |
garageId |
String |
'' |
ID garage. |
vehicleBrandId |
String |
'' |
ID brand. |
CarInformation(
companyName: 'toyota',
modelName: 'vios',
manufacturingYear: 2022,
vehicleVersionName: '1.5C',
licensePlate: '30A12345',
vehicleType: 'sedan',
color: '#A2A8A1',
garageId: '',
vehicleBrandId: '',
);
ModelConfig (bắt buộc)
Ngưỡng cho từng model AI và thuật toán kiểm tra chất lượng ảnh OpenCV. Các giá trị phải tuân thủ điều kiện chặn biên (vi phạm sẽ ném assert).
| Trường | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
carPartConfThreshold |
double |
0.5 |
Confidence threshold model nhận diện bộ phận xe (0.0–1.0). |
carPartIouThreshold |
double |
0.45 |
IOU threshold model nhận diện bộ phận xe (0.0–1.0). |
carCornerConfThreshold |
double |
0.3 |
Confidence threshold model phân loại góc xe (0.0–1.0). |
carQualityConfThreshold |
double |
0.3 |
Confidence threshold model đánh giá chất lượng ảnh (0.0–1.0). |
licensePlateConfThreshold |
double |
0.5 |
Confidence threshold model OCR biển số (0.0–1.0). |
blurThreshold |
double |
30.0 |
Ngưỡng Laplacian variance cơ bản để xác định ảnh mờ (>= 0.0). |
blurAdvancedThresholds |
List<double> |
[3.0, 10.0] |
Cặp ngưỡng [sharpnessGrade, blurScore] nhận diện ảnh mờ nâng cao (>= 0.0). |
brightThreshold |
double |
85.0 |
Ngưỡng độ sáng trung bình tối thiểu (0.0–255.0, dưới ngưỡng này là ảnh tối). |
glareThreshold |
double |
0.18 |
Ngưỡng tỷ lệ điểm trắng/lóa tối đa (0.0–1.0, vượt ngưỡng này là ảnh chói sáng). |
// Dùng mặc định
ModelConfig();
// Hoặc tinh chỉnh
ModelConfig(
carPartConfThreshold: 0.5,
carPartIouThreshold: 0.45,
carCornerConfThreshold: 0.3,
carQualityConfThreshold: 0.3,
licensePlateConfThreshold: 0.5,
blurThreshold: 30.0,
blurAdvancedThresholds: const [3.0, 10.0],
brightThreshold: 85.0,
glareThreshold: 0.18,
);
Conf threshold càng thấp → bắt được nhiều đối tượng hơn nhưng dễ nhiễu. IOU threshold điều chỉnh việc gộp các bounding box trùng nhau. Image quality thresholds điều chỉnh độ nhạy khi phát hiện ảnh mờ, tối hoặc chói sáng.
DisplayConfig (tùy chọn)
| Trường | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
loadingWidget |
Widget? |
null |
Widget loading tùy chỉnh. |
showBackButton |
bool |
false |
Hiển thị nút back trên màn quản lý model. |
const DisplayConfig(
showBackButton: true,
);
ValidateConfig (tùy chọn)
| Trường | Kiểu | Mặc định | Mô tả |
|---|---|---|---|
angleMode |
CaptureAngleMode |
CaptureAngleMode.sixAngles |
Chế độ góc chụp toàn cảnh: sixAngles (6 góc truyền thống), sixAnglesWithSides (6 góc sườn & chéo), hoặc eightAngles (8 góc có thêm 2 sườn). |
requireFullAnglePanoramicPhotos |
bool |
false |
Bắt buộc chụp ảnh toàn cảnh đủ tất cả các góc theo angleMode trước khi xem kết quả. Khi false: chỉ cần ít nhất một góc đã chụp. |
require6AnglePanoramicPhotos |
bool |
false |
(Tương thích ngược) Tương đương requireFullAnglePanoramicPhotos khi ở chế độ 6 góc. |
requireLicensePlate |
bool |
true |
Bắt buộc nhận diện / đọc được biển số trước khi auto-capture ảnh toàn cảnh (tự động miễn trừ với các góc sườn). |
minCarSpanThreshold |
double |
0.35 |
Ngưỡng chiều dài tối thiểu của xe trong khung hình (từ 0.0 đến 1.0). Nếu xe nhỏ hơn ngưỡng này khi thiếu bộ phận -> nhắc tiến lại gần thay vì lùi ra xa. |
maxCarSpanThreshold |
double |
0.85 |
Ngưỡng chiều dài tối đa của xe trong khung hình (từ minCarSpanThreshold đến 1.0). |
const ValidateConfig(
angleMode: CaptureAngleMode.sixAngles, // sixAngles, sixAnglesWithSides, hoặc eightAngles
requireFullAnglePanoramicPhotos: true,
requireLicensePlate: true,
minCarSpanThreshold: 0.35,
maxCarSpanThreshold: 0.85,
);
Chế độ góc chụp (CaptureAngleMode)
SDK hỗ trợ 3 chế độ góc chụp toàn cảnh thông qua enum CaptureAngleMode:
| Chế độ | Số góc | Danh sách các góc (angleId) |
Mô tả |
|---|---|---|---|
CaptureAngleMode.sixAngles (mặc định) |
6 | 0: Đầu xe1: Đầu phụ (45° phải trước)2: Đuôi phụ (45° phải sau)3: Đuôi xe4: Đuôi lái (45° trái sau)5: Đầu lái (45° trái trước) |
6 góc truyền thống: Trước, Sau và 4 góc chéo 45°. |
CaptureAngleMode.sixAnglesWithSides |
6 | 0: Đầu phụ (45° phải trước)1: Sườn phải (90° bên phải)2: Đuôi phụ (45° phải sau)3: Đuôi lái (45° trái sau)4: Sườn trái (90° bên trái)5: Đầu lái (45° trái trước) |
6 góc sườn & chéo: Loại bỏ góc Trước/Sau, radar chia vạch tại đầu/đuôi xe và 2 cung sườn hông. |
CaptureAngleMode.eightAngles |
8 | 0: Đầu xe1: Đầu phụ (45° phải trước)2: Sườn phải (90° bên phải)3: Đuôi phụ (45° phải sau)4: Đuôi xe5: Đuôi lái (45° trái sau)6: Sườn trái (90° bên trái)7: Đầu lái (45° trái trước) |
8 góc toàn diện: Trước, Sau, 2 góc sườn và 4 góc chéo 45°. |
Đặc điểm chụp của các góc sườn xe (sixAnglesWithSides và eightAngles):
- Bộ phận nhận diện bắt buộc: Khung hình camera cần nhận diện đủ 3 nhóm bộ phận:
Cánh cửa,Tai (vè trước) xe,Hông (vè sau) xe / thùng xe. - Cơ chế miễn trừ biển số (
requiresPlate = false): Ở góc chụp trực diện 90° vào sườn xe, biển số trước và sau sẽ bị che khuất hoặc nghiêng góc lớn. SDK tự động bỏ qua kiểm tra biển số cho các góc sườn, ngay cả khirequireLicensePlate: true. - Tooltip điều hướng: Cung cấp tooltip chỉ dẫn trực quan tương ứng: "Vui lòng di chuyển sang sườn phải xe" và "Vui lòng di chuyển sang sườn trái xe".
- Vòng tròn tiến độ (
CarProgressRing): Tự động chia các cung tròn và vạch ngăn tương ứng theoangleMode. Ở chế độsixAnglesWithSides, vạch ngăn ranh giới nằm chính giữa đầu xe và đuôi xe, 2 cung sườn nằm ở hông xe.
Cơ chế hoạt động của minCarSpanThreshold:
- Định nghĩa: Tỷ lệ chiều dài của toàn bộ chiếc xe (bao gồm mọi bộ phận xe nhận diện được) so với chiều dài màn hình (từ
0.0đến1.0). Mặc định là0.35(35% màn hình). - Mục đích: Khắc phục tình trạng khi người dùng đứng quá xa xe, hệ thống bị thiếu các bộ phận nhỏ nhưng lại nhắc sai thành "Vui lòng lùi ra xa xe hơn" (do lầm tưởng xe bị tràn khung).
- Hành vi:
- Nếu xe đứng xa và chiếm dưới
minCarSpanThresholdmàn hình mà vẫn thiếu bộ phận $\rightarrow$ Tooltip sẽ thông minh hướng dẫn: "Vui lòng tiến lại gần xe hơn". - Chỉ khi xe đủ lớn nhưng bị cắt mép khung hình $\rightarrow$ Mới nhắc: "Vui lòng lùi ra xa xe hơn".
- Nếu xe đứng xa và chiếm dưới
VBIConfig (bắt buộc khi organization = vbi)
Chỉ dùng khi generalConfig.organization == AiCycleOrg.vbi. SDK thực hiện upload ảnh lên endpoint chuẩn hóa:
POST {apiUploadBaseUrl}/api/{apiVersionCode}/Aicycle/buy-me/upload
Phân định trách nhiệm dữ liệu:
- SDK tự động xử lý:
Img: File ảnh nhị phân vừa chụp từ Camera.DirectionSlug: SDK tự động ánh xạ dựa trên góc chụp hiện tại (angleId: 0..5) sang slug Aicycle, hoặc fallback vềdefaultDirectionSlug(mặc định:'45-trai-truoc-C1xM02').RequestedTime: SDK tự sinh thời gian upload hiện tại (DateTime.now().toIso8601String()).
- App Host truyền cấu hình động qua
VBIConfig:
| Trường | Kiểu | Bắt buộc | Mặc định | Mô tả |
|---|---|---|---|---|
apiUploadBaseUrl |
String |
✅ | — | Base URL upload VBI (VD: https://uat-api-mobile.evbi.vn). |
apiVersionCode |
String |
❌ | 'v1' |
Version code API VBI. |
authorityId |
String? |
❌ | null |
Mã Authority đính kèm header Authority (tùy chọn). |
signatureKey |
String? |
❌ | null |
Khóa chữ ký đính kèm header Signature (tùy chọn). |
soId |
String? |
❌ | documentId |
Mã hồ sơ VBI (SoId), tự fallback về documentId nếu không truyền. |
soIdDt |
String? |
❌ | externalSessionId |
Mã đối tượng / phiên phụ (SoIdDt). |
externalSessionId |
String? |
❌ | soIdDt |
ID phiên bên ngoài (ExternalSessionId). |
isValidate |
bool |
❌ | true |
Cờ validate (IsValidate), khi true thì DirectionSlug là bắt buộc. |
defaultDirectionSlug |
String |
❌ | '45-trai-truoc-C1xM02' |
Slug góc chụp mặc định khi không xác định góc. |
angleSlugMap |
Map<int, String>? |
❌ | null |
Cho phép App Host ghi đè slug riêng cho từng góc angleId (0..5 với 6 góc, 0..7 với 8 góc). |
carCompany |
String? |
❌ | carInfo.companyName |
Hãng xe (CarCompany). |
licensePlate |
String? |
❌ | carInfo.licensePlate |
Biển số xe (LicensePlate). |
hasLicensePlate |
bool? |
❌ | licensePlate != '' |
Cờ có biển số hay không (HasLicensePlate). |
externalUsedCar |
String? |
❌ | '' |
Cờ xe cũ / đã qua sử dụng (ExternalUsedCar). |
location |
String? |
❌ | lat,long GPS máy |
Toạ độ GPS (Location), nếu để trống SDK sẽ tự lấy toạ độ của thiết bị. |
Danh sách Slug góc chụp mặc định theo angleId:
SDK tự động ánh xạ theo angleMode được cấu hình trong ValidateConfig:
-
Chế độ 6 góc truyền thống (
CaptureAngleMode.sixAngles):0(Đầu xe):truoc-sT9qgX1(Đầu phụ):45-phai-truoc-UoYzs62(Đuôi phụ):45-phai-sau-fRzY3r3(Đuôi xe):sau-htBwjB4(Đuôi lái):45-trai-sau-1q3G3J5(Đầu lái):45-trai-truoc-C1xM02
-
Chế độ 6 góc gồm sườn (
CaptureAngleMode.sixAnglesWithSides):0(Đầu phụ):45-phai-truoc-UoYzs61(Sườn phải):suon-phai2(Đuôi phụ):45-phai-sau-fRzY3r3(Đuôi lái):45-trai-sau-1q3G3J4(Sườn trái):suon-trai5(Đầu lái):45-trai-truoc-C1xM02
-
Chế độ 8 góc (
CaptureAngleMode.eightAngles):0(Đầu xe):truoc-sT9qgX1(Đầu phụ):45-phai-truoc-UoYzs62(Sườn phải):suon-phai3(Đuôi phụ):45-phai-sau-fRzY3r4(Đuôi xe):sau-htBwjB5(Đuôi lái):45-trai-sau-1q3G3J6(Sườn trái):suon-trai7(Đầu lái):45-trai-truoc-C1xM02
-
Các góc khác / Không xác định: Fallback về
defaultDirectionSlug('45-trai-truoc-C1xM02'). Có thể tùy biến toàn bộ quaangleSlugMap.
final config = BuyMeConfig(
generalConfig: GeneralConfig(
apiToken: '<API_TOKEN>',
documentId: '<DOCUMENT_ID>', // SoId
organization: AiCycleOrg.vbi, // ⚠️ bắt buộc vbiConfig
aicBaseUrl: 'https://uat-api-mobile.evbi.vn', // ⚠️ bắt buộc với VBI
),
modelConfig: ModelConfig(),
carInformation: CarInformation(
companyName: 'Toyota',
licensePlate: '30A12345',
),
vbiConfig: VBIConfig(
apiUploadBaseUrl: 'https://uat-api-mobile.evbi.vn',
apiVersionCode: 'v1',
authorityId: '<AUTHORITY_ID>',
signatureKey: '<SIGNATURE_KEY>',
soId: '202609230001',
soIdDt: '202609230002',
externalSessionId: '202609230002',
isValidate: true,
defaultDirectionSlug: '45-trai-truoc-C1xM02', // hoặc tuỳ chỉnh
externalUsedCar: '',
),
);
⚠️ Nếu
organization == AiCycleOrg.vbimàvbiConfig == null, constructorBuyMeConfigsẽ némassert.
Callback sự kiện: onPhotoCaptured, onComplete, onError, onImageUploaded
Cả BuyMeOnDevice và BuyMeOnDeviceCamera đều hỗ trợ các callback sau:
| Tên Callback | Kiểu dữ liệu / Signature | Thời điểm gọi |
|---|---|---|
onPhotoCaptured |
void Function(CapturedPhotoData photo) |
Realtime ngay khi chụp xong một ảnh thành công và lưu file xuống đĩa. Dùng để App Host lưu đường dẫn và quản lý ảnh local. |
onComplete |
void Function() |
Sau khi hoàn tất chụp, người dùng bấm "Xem kết quả" và upload ảnh xong. |
onError |
void Function(String error) |
Khi xảy ra lỗi (tạo hồ sơ thất bại, tải model lỗi, xác thực token lỗi…). |
onImageUploaded |
void Function(Map<String, dynamic> data) |
Gọi mỗi lần một ảnh upload thành công lên server. |
Cấu trúc dữ liệu CapturedPhotoData
class CapturedPhotoData {
final String documentId; // Mã hồ sơ (documentId)
final int angleId; // ID góc chụp (0..5 ở chế độ 6 góc, hoặc 0..7 ở chế độ 8 góc)
final String filePath; // Đường dẫn file ảnh gốc tuyệt đối trên đĩa
final String thumbnailPath; // Đường dẫn file thumbnail tuyệt đối trên đĩa
final int photoIndex; // Thứ tự ảnh trong góc đó (thường là 0)
final DateTime capturedAt; // Thời gian chụp
}
BuyMeOnDeviceCamera(
buyMeConfig: config,
onPhotoCaptured: (CapturedPhotoData photo) {
debugPrint('Đã chụp góc ${photo.angleId}: ${photo.filePath}');
// App Host lưu lại thông tin để hiển thị hoặc chụp tiếp
},
onComplete: () {
Navigator.pop(context);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Hoàn tất')),
);
},
onError: (error) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Lỗi SDK: $error')),
);
},
onImageUploaded: (data) {
debugPrint('Ảnh upload thành công: $data');
},
);
Quản lý phiên chụp & Khôi phục (Session Recovery)
SDK hỗ trợ cơ chế cho phép App Host toàn quyền quản lý thời gian và vòng đời file ảnh (ví dụ: hồ sơ còn hạn trong 30 phút thì cho phép chụp tiếp, quá 30 phút thì chụp từ đầu).
1. Lấy ảnh realtime qua onPhotoCaptured
Mỗi khi chụp xong một góc, SDK lưu file vào bộ nhớ thiết bị và bắn callback onPhotoCaptured. App Host nhận đối tượng photo và có thể render ngay lên giao diện:
// Hiển thị ảnh bằng Image.file:
final file = File(photo.thumbnailPath.isNotEmpty ? photo.thumbnailPath : photo.filePath);
if (file.existsSync()) {
Image.file(file, fit: BoxFit.cover);
}
2. Tiếp tục chụp các góc còn thiếu qua initialPhotos
Nếu người dùng thoát camera hoặc tắt ứng dụng, khi mở lại SDK:
-
Nếu muốn chụp tiếp: App Host gom danh sách ảnh hiện có thành
Map<int, List<String>>và truyền vàoBuyMeConfig(initialPhotos: ...):final initialPhotos = <int, List<String>>{ 0: ['/path/to/angle_0.jpg'], // Góc 0 đã có 1: ['/path/to/angle_1.jpg'], // Góc 1 đã có }; final config = BuyMeConfig( generalConfig: GeneralConfig(documentId: 'doc_123', ...), initialPhotos: initialPhotos, // 👈 Truyền map ảnh đã chụp // ... );Hành vi SDK khi nhận
initialPhotos:- Tự động kiểm tra các file ảnh còn tồn tại trên máy.
- Vòng tròn tiến độ tự động đánh dấu xanh các góc đã có ảnh.
- Bỏ qua màn hình hướng dẫn ban đầu (Guide Sheet).
- Tự động hướng dẫn người dùng di chuyển máy sang góc còn thiếu tiếp theo (ví dụ: Góc 2).
-
Nếu muốn chụp lại từ đầu: App Host truyền
initialPhotos: null(hoặc map rỗng), SDK sẽ bắt đầu một phiên hoàn toàn mới từ góc 0.
3. Dọn dẹp & Xoá ảnh local
- Tự động dọn dẹp khi "Kết thúc chụp ảnh" thành công:
Khi người dùng hoàn tất quá trình chụp và bấm "Kết thúc chụp ảnh" (upload toàn bộ ảnh lên server thành công), SDK sẽ tự động xoá sạch toàn bộ ảnh local và thumbnail của
documentIdđó trên máy để giải phóng bộ nhớ. - Nếu chưa bấm kết thúc (thoát ngang / back camera): Ảnh vẫn được giữ nguyên vẹn trên máy để phục vụ chụp tiếp.
- App host tự xoá thủ công từng file:
final file = File(photo.filePath); if (file.existsSync()) await file.delete(); - Hoặc chủ động xoá sạch qua helper SDK:
await BuyMeOnDevice.clearSessionPhotos(documentId);
Cache ảnh phiên chụp
SDK lưu ảnh đã chụp theo documentId (session) trên disk để sống sót khi app bị kill / thoát giữa chừng.
- Ảnh được ghi cache ngay sau mỗi lần chụp.
- Nếu lần trước thoát khi đang upload (
upload pending), lần mở lại SDK có thể khôi phục và tiếp tục màn upload.
GeneralConfig(
apiToken: '<API_TOKEN>',
documentId: '<DOCUMENT_ID>',
organization: AiCycleOrg.aicycle,
alwaysCache: true, // giữ ảnh đã chụp khi thoát camera
);
Libraries
- buyme_on_device
- The public API for the buyme_on_device package.