Files
pfm-ocr/lib/features/documents/pending_documents_provider.dart
T

217 lines
6.1 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:dio/dio.dart';
import 'package:geolocator/geolocator.dart';
import '../../models/document_model.dart';
import '../../core/location/location_service.dart';
import '../../core/network/api_client.dart';
import '../../config/app_config.dart';
enum PendingDocumentStatus { uploading, processing, success, error }
class PendingDocument {
final String id;
final String imagePath;
final PendingDocumentStatus status;
final DocumentModel? document;
final double? latitude;
final double? longitude;
final String? errorMessage;
final DateTime createdAt;
PendingDocument({
required this.id,
required this.imagePath,
this.status = PendingDocumentStatus.uploading,
this.document,
this.latitude,
this.longitude,
this.errorMessage,
DateTime? createdAt,
}) : createdAt = createdAt ?? DateTime.now();
PendingDocument copyWith({
PendingDocumentStatus? status,
DocumentModel? document,
double? latitude,
double? longitude,
String? errorMessage,
}) {
return PendingDocument(
id: id,
imagePath: imagePath,
status: status ?? this.status,
document: document ?? this.document,
latitude: latitude ?? this.latitude,
longitude: longitude ?? this.longitude,
errorMessage: errorMessage,
createdAt: createdAt,
);
}
}
class PendingDocumentsNotifier extends StateNotifier<List<PendingDocument>> {
PendingDocumentsNotifier() : super([]);
void addDocument(String imagePath, {double? latitude, double? longitude}) {
final id = DateTime.now().millisecondsSinceEpoch.toString();
final newDoc = PendingDocument(
id: id,
imagePath: imagePath,
latitude: latitude,
longitude: longitude,
);
state = [newDoc, ...state];
_uploadAndProcess(id, imagePath, latitude: latitude, longitude: longitude);
}
Future<Position?> _determinePosition() async {
return await LocationService.determinePosition();
}
Future<void> _uploadAndProcess(String id, String imagePath, {double? latitude, double? longitude}) async {
double? lat = latitude;
double? lng = longitude;
if (lat == null || lng == null) {
try {
final position = await _determinePosition();
if (position != null) {
lat = position.latitude;
lng = position.longitude;
if (mounted) {
state = [
for (final item in state)
if (item.id == id)
item.copyWith(latitude: lat, longitude: lng)
else
item
];
}
}
} catch (_) {
// Gracefully continue without location tags if fetch fails
}
}
try {
// API call to upload and process
final apiClient = ApiClient();
final formData = FormData.fromMap({
'image': await MultipartFile.fromFile(
imagePath,
filename: imagePath.split(RegExp(r'[/\\]')).last,
),
if (lat != null) 'latitude': lat.toString(),
if (lng != null) 'longitude': lng.toString(),
});
final response = await apiClient.client.post(
AppConfig.uploadDocumentEndpoint,
data: formData,
);
final doc = DocumentModel.fromJson(response.data['data']);
if (!mounted) return;
state = [
for (final item in state)
if (item.id == id)
item.copyWith(status: PendingDocumentStatus.processing, document: doc)
else
item
];
// Poll until the document is parsed on the server
bool isParsed = false;
DocumentModel? parsedDoc;
int retries = 0;
const maxRetries = 60; // 2 minutes max polling (60 retries * 2s)
while (!isParsed && retries < maxRetries && mounted) {
await Future.delayed(const Duration(seconds: 2));
if (!mounted) return;
try {
final getRes = await apiClient.client.get(AppConfig.fetchDocumentsEndpoint);
if (getRes.statusCode == 200 && getRes.data['data'] is List) {
final list = (getRes.data['data'] as List)
.map((e) => DocumentModel.fromJson(Map<String, dynamic>.from(e)))
.toList();
final found = list.firstWhere(
(element) => element.id == doc.id,
orElse: () => doc,
);
if (found != doc) {
isParsed = true;
parsedDoc = found;
}
}
} catch (_) {
// Ignore network glitch during polling
}
retries++;
}
if (!mounted) return;
if (isParsed && parsedDoc != null) {
state = [
for (final item in state)
if (item.id == id)
item.copyWith(status: PendingDocumentStatus.success, document: parsedDoc)
else
item
];
} else {
state = [
for (final item in state)
if (item.id == id)
item.copyWith(
status: PendingDocumentStatus.error,
errorMessage: 'Gagal mengekstrak data dari dokumen (Timeout).',
)
else
item
];
}
} catch (e) {
if (!mounted) return;
state = [
for (final item in state)
if (item.id == id)
item.copyWith(status: PendingDocumentStatus.error, errorMessage: e.toString())
else
item
];
}
}
void removeDocument(String id) {
state = state.where((item) => item.id != id).toList();
}
void retryUpload(String id) {
final doc = state.firstWhere((item) => item.id == id);
final lat = doc.latitude;
final lng = doc.longitude;
state = [
for (final item in state)
if (item.id == id)
item.copyWith(
status: PendingDocumentStatus.uploading,
errorMessage: null,
)
else
item
];
_uploadAndProcess(id, doc.imagePath, latitude: lat, longitude: lng);
}
}
final pendingDocumentsProvider = StateNotifierProvider<PendingDocumentsNotifier, List<PendingDocument>>((ref) {
return PendingDocumentsNotifier();
});