feat: consolidate backend and docker-compose setup
This commit is contained in:
commit
ff3753a745
306 files changed
+35450
No files matched your search
@@ -0,0 +1,216 @@
|
||||
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();
|
||||
});
|
||||
Reference in new issue
Block a user