import 'dart:async'; import 'dart:developer'; import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/product/product_local_datasource.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import '../../../data/datasources/product_remote_datasource.dart'; part 'data_sync_event.dart'; part 'data_sync_state.dart'; part 'data_sync_bloc.freezed.dart'; enum SyncStep { products, categories, variants, completed } class SyncStats { final int totalProducts; final int totalCategories; final int totalVariants; final double databaseSizeMB; SyncStats({ required this.totalProducts, required this.totalCategories, required this.totalVariants, required this.databaseSizeMB, }); } class DataSyncBloc extends Bloc { final ProductRemoteDatasource _remoteDatasource = ProductRemoteDatasource(); final ProductLocalDatasource _localDatasource = ProductLocalDatasource.instance; Timer? _progressTimer; bool _isCancelled = false; DataSyncBloc() : super(const DataSyncState.initial()) { on<_StartSync>(_onStartSync); on<_CancelSync>(_onCancelSync); } @override Future close() { _progressTimer?.cancel(); return super.close(); } Future _onStartSync( _StartSync event, Emitter emit, ) async { log('🔄 Starting data sync...'); _isCancelled = false; try { // Step 1: Clear existing local data emit(const DataSyncState.syncing( SyncStep.products, 0.1, 'Membersihkan data lama...')); await _localDatasource.clearAllProducts(); if (_isCancelled) return; // Step 2: Sync products await _syncProducts(emit); if (_isCancelled) return; // Step 3: Generate final stats emit(const DataSyncState.syncing( SyncStep.completed, 0.9, 'Menyelesaikan sinkronisasi...')); final stats = await _generateSyncStats(); emit(DataSyncState.completed(stats)); log('✅ Sync completed successfully'); } catch (e) { log('❌ Sync failed: $e'); emit(DataSyncState.error('Gagal sinkronisasi: $e')); } } Future _syncProducts(Emitter emit) async { log('đŸ“Ļ Syncing products...'); int page = 1; int totalSynced = 0; int? totalCount; int? totalPages; bool shouldContinue = true; while (!_isCancelled && shouldContinue) { // Calculate accurate progress based on total count double progress = 0.2; if (totalCount != null && (totalCount ?? 0) > 0) { progress = 0.2 + (totalSynced / (totalCount ?? 0)) * 0.6; } emit(DataSyncState.syncing( SyncStep.products, progress, totalCount != null ? 'Mengunduh produk... ($totalSynced dari $totalCount)' : 'Mengunduh produk... ($totalSynced produk)', )); final result = await _remoteDatasource.getProducts( page: page, limit: 50, // Bigger batch for sync ); await result.fold( (failure) async { throw Exception(failure); }, (response) async { final products = response.data?.products ?? []; final responseData = response.data; // Get pagination info from first response if (page == 1 && responseData != null) { totalCount = responseData.totalCount; totalPages = responseData.totalPages; log('📊 Total products to sync: $totalCount (${totalPages} pages)'); } if (products.isEmpty) { shouldContinue = false; return; } // Save to local database in batches await _localDatasource.saveProductsBatch(products); totalSynced += products.length; page++; log('đŸ“Ļ Synced page ${page - 1}: ${products.length} products (Total: $totalSynced)'); // Check if we reached the end using pagination info if (totalPages != null && page > (totalPages ?? 0)) { shouldContinue = false; return; } // Fallback check if pagination info not available if (products.length < 50) { shouldContinue = false; return; } // Small delay to prevent overwhelming the server await Future.delayed(Duration(milliseconds: 100)); }, ); } emit(DataSyncState.syncing( SyncStep.completed, 0.8, 'Produk berhasil diunduh ($totalSynced dari ${totalCount ?? totalSynced})', )); log('✅ Products sync completed: $totalSynced products synced'); } Future _generateSyncStats() async { final dbStats = await _localDatasource.getDatabaseStats(); return SyncStats( totalProducts: dbStats['total_products'] ?? 0, totalCategories: dbStats['total_categories'] ?? 0, totalVariants: dbStats['total_variants'] ?? 0, databaseSizeMB: dbStats['database_size_mb'] ?? 0.0, ); } Future _onCancelSync( _CancelSync event, Emitter emit, ) async { log('âšī¸ Cancelling sync...'); _isCancelled = true; _progressTimer?.cancel(); emit(const DataSyncState.initial()); } }