Files
app-grosir/lib/services/offline_queue_service.dart

162 lines
4.9 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class OfflineQueueService {
static final OfflineQueueService _instance = OfflineQueueService._internal();
factory OfflineQueueService() => _instance;
OfflineQueueService._internal();
final SupabaseClient _supabase = Supabase.instance.client;
final Connectivity _connectivity = Connectivity();
List<Map<String, dynamic>> _queue = [];
bool _isOnline = true;
bool _syncing = false;
static const Duration _timeout = Duration(seconds: 30);
Future<T> _withTimeout<T>(Future<T> future, [String? operation]) async {
try {
return await future.timeout(
_timeout,
onTimeout: () => throw TimeoutException('${operation ?? 'Operasi'} timeout setelah $_timeout'),
);
} on TimeoutException catch (e) {
debugPrint('OfflineQueue timeout: $e');
rethrow;
}
}
List<Map<String, dynamic>> get queue => List.unmodifiable(_queue);
bool get isOnline => _isOnline;
bool get hasPendingItems => _queue.isNotEmpty;
Future<void> init() async {
await _loadQueue();
_connectivity.onConnectivityChanged.listen(_updateConnectionStatus);
await _checkConnection();
if (_isOnline) await _processQueue();
}
Future<void> _loadQueue() async {
final prefs = await SharedPreferences.getInstance();
final queueJson = prefs.getString('offline_queue');
if (queueJson != null) {
final List<dynamic> decoded = json.decode(queueJson);
_queue = decoded.cast<Map<String, dynamic>>();
}
}
Future<void> _saveQueue() async {
final prefs = await SharedPreferences.getInstance();
await prefs.setString('offline_queue', json.encode(_queue));
}
Future<void> _checkConnection() async {
final result = await _connectivity.checkConnectivity();
_updateConnectionStatus(result);
}
void _updateConnectionStatus(List<ConnectivityResult> result) {
_isOnline = result.any((r) => r != ConnectivityResult.none);
if (_isOnline && _queue.isNotEmpty) {
_processQueue();
}
}
Future<void> addToQueue(Map<String, dynamic> transaction) async {
_queue.add(transaction);
await _saveQueue();
if (_isOnline) {
await _processQueue();
}
}
Future<void> _processQueue() async {
if (_syncing || !_isOnline || _queue.isEmpty) return;
_syncing = true;
while (_queue.isNotEmpty && _isOnline) {
final item = _queue.first;
try {
await _processTransaction(item);
_queue.removeAt(0);
await _saveQueue();
} catch (e) {
_syncing = false;
return;
}
}
_syncing = false;
}
Future<void> _processTransaction(Map<String, dynamic> transaction) async {
final sale = transaction['sale'] as Map<String, dynamic>;
final items = (transaction['items'] as List?) ?? [];
final saleId = sale['id'] as String;
final storeId = sale['store_id'] as String?;
final existing = await _withTimeout(
_supabase
.from('sales')
.select('id, total_cost')
.eq('id', saleId)
.eq('store_id', storeId ?? '00000000-0000-0000-0000-000000000000')
.maybeSingle(),
'Check existing sale',
);
if (existing == null) {
await _withTimeout(
_supabase.from('sales').insert(sale),
'Insert queued sale',
);
} else {
final existingTotalCost = (existing['total_cost'] as num?)?.toDouble() ?? 0;
if (existingTotalCost <= 0 && sale['total_cost'] != null) {
await _withTimeout(
_supabase
.from('sales')
.update({'total_cost': sale['total_cost']})
.eq('id', saleId)
.eq('store_id', storeId ?? '00000000-0000-0000-0000-000000000000'),
'Update sale total_cost',
);
}
}
for (final it in items) {
final productId = it['product_id'] as String;
final qty = (it['qty'] as num).toInt();
final product = await _withTimeout(
_supabase
.from('products')
.select()
.eq('id', productId)
.eq('store_id', storeId ?? '00000000-0000-0000-0000-000000000000')
.maybeSingle(),
'Load product for stock update',
);
if (product != null) {
final currentStock = (product['stock'] as num).toInt();
final newStock = (currentStock - qty).clamp(0, 1 << 62);
await _withTimeout(
_supabase
.from('products')
.update({'stock': newStock})
.eq('id', productId)
.eq('store_id', storeId ?? '00000000-0000-0000-0000-000000000000'),
'Update product stock',
);
}
}
}
Future<void> clearQueue() async {
_queue.clear();
await _saveQueue();
}
}