Compare commits
No commits in common. "6892895021154769b0a4e66ef7525308f6f3ffe4" and "dea5de8828bb86451bacfd832bcc7c79a01954a9" have entirely different histories.
6892895021
...
dea5de8828
@ -1,345 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
import '../../../domain/product/product.dart';
|
|
||||||
|
|
||||||
part 'product_loader_event.dart';
|
|
||||||
part 'product_loader_state.dart';
|
|
||||||
part 'product_loader_bloc.freezed.dart';
|
|
||||||
|
|
||||||
@injectable
|
|
||||||
class ProductLoaderBloc extends Bloc<ProductLoaderEvent, ProductLoaderState> {
|
|
||||||
final IProductRepository _productRepository;
|
|
||||||
|
|
||||||
Timer? _loadMoreDebounce;
|
|
||||||
Timer? _searchDebounce;
|
|
||||||
|
|
||||||
ProductLoaderBloc(this._productRepository)
|
|
||||||
: super(ProductLoaderState.initial()) {
|
|
||||||
on<ProductLoaderEvent>(_onProductLoaderEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onProductLoaderEvent(
|
|
||||||
ProductLoaderEvent event,
|
|
||||||
Emitter<ProductLoaderState> emit,
|
|
||||||
) {
|
|
||||||
return event.map(
|
|
||||||
getProduct: (e) async {
|
|
||||||
emit(state.copyWith(isLoadingMore: true));
|
|
||||||
|
|
||||||
log(
|
|
||||||
'📱 Loading local products - categoryId: ${e.categoryId}, search: ${e.search}',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Pastikan database lokal sudah siap
|
|
||||||
final isReady = await _productRepository.isLocalDatabaseReady();
|
|
||||||
if (!isReady) {
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: optionOf(
|
|
||||||
ProductFailure.dynamicErrorMessage(
|
|
||||||
'Database lokal belum siap. Silakan lakukan sinkronisasi data terlebih dahulu.',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await _productRepository.getProducts(
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
categoryId: e.categoryId,
|
|
||||||
search: e.search,
|
|
||||||
);
|
|
||||||
|
|
||||||
await result.fold(
|
|
||||||
(failure) async {
|
|
||||||
log('❌ Error loading local products: $failure');
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: optionOf(failure),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(response) async {
|
|
||||||
final products = response.products;
|
|
||||||
final totalPages = response.totalPages;
|
|
||||||
final hasReachedMax = products.length < 10 || 1 >= totalPages;
|
|
||||||
|
|
||||||
log(
|
|
||||||
'✅ Local products loaded: ${products.length}, hasReachedMax: $hasReachedMax, totalPages: $totalPages',
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
products: products,
|
|
||||||
page: 1,
|
|
||||||
hasReachedMax: hasReachedMax,
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: none(),
|
|
||||||
categoryId: e.categoryId,
|
|
||||||
searchQuery: e.search,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
},
|
|
||||||
loadMore: (e) async {
|
|
||||||
final currentState = state;
|
|
||||||
|
|
||||||
// Cegah double load
|
|
||||||
if (currentState.isLoadingMore || currentState.hasReachedMax) {
|
|
||||||
log(
|
|
||||||
'⏹️ Load more blocked - isLoadingMore: ${currentState.isLoadingMore}, hasReachedMax: ${currentState.hasReachedMax}',
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(currentState.copyWith(isLoadingMore: true));
|
|
||||||
|
|
||||||
final nextPage = currentState.page + 1;
|
|
||||||
log('📄 Loading more local products - page: $nextPage');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final result = await _productRepository.getProducts(
|
|
||||||
page: nextPage,
|
|
||||||
limit: 10,
|
|
||||||
categoryId: currentState.categoryId,
|
|
||||||
search: currentState.searchQuery,
|
|
||||||
);
|
|
||||||
|
|
||||||
await result.fold(
|
|
||||||
(failure) async {
|
|
||||||
log('❌ Error loading more local products: $failure');
|
|
||||||
emit(
|
|
||||||
currentState.copyWith(
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: optionOf(failure),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(response) async {
|
|
||||||
final newProducts = response.products;
|
|
||||||
final totalPages = response.totalPages;
|
|
||||||
|
|
||||||
// Hindari duplikat produk
|
|
||||||
final currentProductIds = currentState.products
|
|
||||||
.map((p) => p.id)
|
|
||||||
.toSet();
|
|
||||||
final filteredNewProducts = newProducts
|
|
||||||
.where((product) => !currentProductIds.contains(product.id))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
final allProducts = [
|
|
||||||
...currentState.products,
|
|
||||||
...filteredNewProducts,
|
|
||||||
];
|
|
||||||
|
|
||||||
final hasReachedMax =
|
|
||||||
filteredNewProducts.length < 10 || nextPage >= totalPages;
|
|
||||||
|
|
||||||
log(
|
|
||||||
'✅ More local products loaded: ${filteredNewProducts.length} new, total: ${allProducts.length}, hasReachedMax: $hasReachedMax',
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(
|
|
||||||
currentState.copyWith(
|
|
||||||
products: allProducts,
|
|
||||||
page: nextPage,
|
|
||||||
hasReachedMax: hasReachedMax,
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: none(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Exception loading more local products: $e');
|
|
||||||
emit(
|
|
||||||
currentState.copyWith(
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: optionOf(
|
|
||||||
ProductFailure.dynamicErrorMessage(
|
|
||||||
'Gagal memuat produk tambahan: $e',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
refresh: (e) async {
|
|
||||||
final categoryId = state.categoryId;
|
|
||||||
final searchQuery = state.searchQuery;
|
|
||||||
|
|
||||||
_loadMoreDebounce?.cancel();
|
|
||||||
_searchDebounce?.cancel();
|
|
||||||
|
|
||||||
log(
|
|
||||||
'🔄 Refreshing local products - categoryId: $categoryId, search: $searchQuery',
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(state.copyWith(isLoadingMore: true));
|
|
||||||
|
|
||||||
try {
|
|
||||||
_productRepository.clearCache();
|
|
||||||
|
|
||||||
final result = await _productRepository.refreshProducts(
|
|
||||||
categoryId: categoryId,
|
|
||||||
search: searchQuery,
|
|
||||||
);
|
|
||||||
|
|
||||||
await result.fold(
|
|
||||||
(failure) async {
|
|
||||||
log('❌ Failed to refresh local products: $failure');
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: optionOf(failure),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(response) async {
|
|
||||||
final products = response.products;
|
|
||||||
final totalPages = response.totalPages;
|
|
||||||
final hasReachedMax = products.length < 10 || 1 >= totalPages;
|
|
||||||
|
|
||||||
log('✅ Refreshed local products: ${products.length}');
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
products: products,
|
|
||||||
hasReachedMax: hasReachedMax,
|
|
||||||
page: 1,
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: none(),
|
|
||||||
categoryId: categoryId,
|
|
||||||
searchQuery: searchQuery,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Exception refreshing local products: $e');
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: optionOf(
|
|
||||||
ProductFailure.dynamicErrorMessage(e.toString()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} finally {}
|
|
||||||
},
|
|
||||||
searchProduct: (e) async {
|
|
||||||
_searchDebounce?.cancel();
|
|
||||||
|
|
||||||
// Debounce ringan agar UX lebih halus
|
|
||||||
_searchDebounce = Timer(const Duration(milliseconds: 150), () async {
|
|
||||||
emit(state.copyWith(isLoadingMore: true));
|
|
||||||
|
|
||||||
log('🔍 Local search: "${e.query}"');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final result = await _productRepository.getProducts(
|
|
||||||
page: 1,
|
|
||||||
limit: 20, // lebih banyak hasil untuk pencarian
|
|
||||||
categoryId: e.categoryId,
|
|
||||||
search: e.query,
|
|
||||||
);
|
|
||||||
|
|
||||||
await result.fold(
|
|
||||||
(failure) async {
|
|
||||||
log('❌ Local search error: $failure');
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: optionOf(failure),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
(response) async {
|
|
||||||
final products = response.products;
|
|
||||||
final totalPages = response.totalPages;
|
|
||||||
final hasReachedMax = products.length < 20 || 1 >= totalPages;
|
|
||||||
|
|
||||||
log(
|
|
||||||
'✅ Local search results: ${products.length} products found',
|
|
||||||
);
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
products: products,
|
|
||||||
hasReachedMax: hasReachedMax,
|
|
||||||
page: 1,
|
|
||||||
isLoadingMore: false,
|
|
||||||
categoryId: e.categoryId,
|
|
||||||
searchQuery: e.query,
|
|
||||||
failureOptionProduct: none(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Exception during local search: $e');
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isLoadingMore: false,
|
|
||||||
failureOptionProduct: optionOf(
|
|
||||||
ProductFailure.dynamicErrorMessage(e.toString()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
getDatabaseStats: (e) async {
|
|
||||||
log('📊 Getting local database stats...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
final result = await _productRepository.getDatabaseStats();
|
|
||||||
|
|
||||||
await result.fold(
|
|
||||||
(failure) async {
|
|
||||||
log('❌ Failed to get database stats: $failure');
|
|
||||||
emit(state.copyWith(failureOptionProduct: optionOf(failure)));
|
|
||||||
},
|
|
||||||
(stats) async {
|
|
||||||
log('✅ Local database stats retrieved: $stats');
|
|
||||||
// Jika UI kamu perlu tampilkan, bisa simpan ke state, misalnya:
|
|
||||||
// emit(state.copyWith(databaseStats: some(stats)));
|
|
||||||
// Tapi kalau hanya untuk log/debug, tidak perlu ubah state
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Exception while getting database stats: $e',
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
failureOptionProduct: optionOf(
|
|
||||||
ProductFailure.dynamicErrorMessage(e.toString()),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
clearCache: (e) async {
|
|
||||||
log('🧹 Manually clearing local cache');
|
|
||||||
_productRepository.clearCache();
|
|
||||||
|
|
||||||
// Refresh current data after cache clear
|
|
||||||
add(const ProductLoaderEvent.refresh());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,26 +0,0 @@
|
|||||||
part of 'product_loader_bloc.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class ProductLoaderEvent with _$ProductLoaderEvent {
|
|
||||||
const factory ProductLoaderEvent.getProduct({
|
|
||||||
String? categoryId,
|
|
||||||
String? search, // Added search parameter
|
|
||||||
bool? forceRefresh, // Kept for compatibility but ignored
|
|
||||||
}) = _GetProduct;
|
|
||||||
|
|
||||||
const factory ProductLoaderEvent.loadMore({
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
}) = _LoadMore;
|
|
||||||
|
|
||||||
const factory ProductLoaderEvent.refresh() = _Refresh;
|
|
||||||
|
|
||||||
const factory ProductLoaderEvent.searchProduct({
|
|
||||||
String? query,
|
|
||||||
String? categoryId,
|
|
||||||
}) = _SearchProduct;
|
|
||||||
|
|
||||||
const factory ProductLoaderEvent.getDatabaseStats() = _GetDatabaseStats;
|
|
||||||
|
|
||||||
const factory ProductLoaderEvent.clearCache() = _ClearCache;
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
part of 'product_loader_bloc.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class ProductLoaderState with _$ProductLoaderState {
|
|
||||||
factory ProductLoaderState({
|
|
||||||
required List<Product> products,
|
|
||||||
required Option<ProductFailure> failureOptionProduct,
|
|
||||||
@Default(false) bool hasReachedMax,
|
|
||||||
@Default(1) int page,
|
|
||||||
@Default(false) bool isLoadingMore,
|
|
||||||
String? searchQuery,
|
|
||||||
String? categoryId,
|
|
||||||
}) = _ProductLoaderState;
|
|
||||||
|
|
||||||
factory ProductLoaderState.initial() =>
|
|
||||||
ProductLoaderState(products: [], failureOptionProduct: none());
|
|
||||||
}
|
|
||||||
@ -1,323 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:bloc/bloc.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
import '../../domain/category/category.dart';
|
|
||||||
import '../../domain/product/product.dart';
|
|
||||||
|
|
||||||
part 'sync_event.dart';
|
|
||||||
part 'sync_state.dart';
|
|
||||||
part 'sync_bloc.freezed.dart';
|
|
||||||
|
|
||||||
enum SyncStep { categories, products, 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,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@injectable
|
|
||||||
class SyncBloc extends Bloc<SyncEvent, SyncState> {
|
|
||||||
final IProductRepository _productRepository;
|
|
||||||
final ICategoryRepository _categoryRepository;
|
|
||||||
|
|
||||||
Timer? _progressTimer;
|
|
||||||
bool _isCancelled = false;
|
|
||||||
|
|
||||||
SyncBloc(this._productRepository, this._categoryRepository)
|
|
||||||
: super(SyncState.initial()) {
|
|
||||||
on<SyncEvent>(_onSyncEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onSyncEvent(SyncEvent event, Emitter<SyncState> emit) {
|
|
||||||
return event.map(
|
|
||||||
startSync: (e) async {
|
|
||||||
log('🔄 Starting full data sync (categories + products)...');
|
|
||||||
_isCancelled = false;
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isSyncing: true,
|
|
||||||
currentStep: SyncStep.categories,
|
|
||||||
progress: 0.05,
|
|
||||||
message: 'Membersihkan data lama...',
|
|
||||||
errorMessage: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Step 1: Clear existing local data
|
|
||||||
await _productRepository.clearAllProducts();
|
|
||||||
await _categoryRepository.clearAllCategories();
|
|
||||||
|
|
||||||
if (_isCancelled) return;
|
|
||||||
|
|
||||||
// Step 2: Sync categories first
|
|
||||||
await _syncCategories(emit);
|
|
||||||
|
|
||||||
if (_isCancelled) return;
|
|
||||||
|
|
||||||
// Step 3: Sync products
|
|
||||||
await _syncProducts(emit);
|
|
||||||
|
|
||||||
if (_isCancelled) return;
|
|
||||||
|
|
||||||
// Step 4: Final stats
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
currentStep: SyncStep.completed,
|
|
||||||
progress: 0.95,
|
|
||||||
message: 'Menyelesaikan sinkronisasi...',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final stats = await _generateSyncStats();
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isSyncing: false,
|
|
||||||
stats: stats,
|
|
||||||
progress: 1.0,
|
|
||||||
message: 'Sinkronisasi selesai ✅',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
log('✅ Full sync completed successfully');
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Sync failed: $e');
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isSyncing: false,
|
|
||||||
errorMessage: 'Gagal sinkronisasi: $e',
|
|
||||||
message: 'Sinkronisasi gagal ❌',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cancelSync: (e) async {
|
|
||||||
log('⏹️ Cancelling sync...');
|
|
||||||
_isCancelled = true;
|
|
||||||
_progressTimer?.cancel();
|
|
||||||
emit(SyncState.initial());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _syncCategories(Emitter<SyncState> emit) async {
|
|
||||||
log('📁 Syncing categories...');
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isSyncing: true,
|
|
||||||
currentStep: SyncStep.categories,
|
|
||||||
progress: 0.1,
|
|
||||||
message: 'Mengunduh kategori...',
|
|
||||||
errorMessage: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Gunakan CategoryRepository sync method
|
|
||||||
final result = await _categoryRepository.syncAllCategories();
|
|
||||||
|
|
||||||
await result.fold(
|
|
||||||
(failure) async {
|
|
||||||
throw Exception('Gagal sync kategori: $failure');
|
|
||||||
},
|
|
||||||
(successMessage) async {
|
|
||||||
log('✅ Categories sync completed: $successMessage');
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
currentStep: SyncStep.categories,
|
|
||||||
progress: 0.2,
|
|
||||||
message: 'Kategori berhasil diunduh ✅',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Category sync failed: $e');
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isSyncing: false,
|
|
||||||
errorMessage: 'Gagal sync kategori: $e',
|
|
||||||
message: 'Sinkronisasi kategori gagal ❌',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
rethrow; // penting agar _onStartSync tahu kalau gagal
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _syncProducts(Emitter<SyncState> emit) async {
|
|
||||||
log('📦 Syncing products...');
|
|
||||||
|
|
||||||
int page = 1;
|
|
||||||
int totalSynced = 0;
|
|
||||||
int? totalCount;
|
|
||||||
int? totalPages;
|
|
||||||
bool shouldContinue = true;
|
|
||||||
|
|
||||||
while (!_isCancelled && shouldContinue) {
|
|
||||||
// Hitung progress dinamis (kategori 0.0–0.2, produk 0.2–0.9)
|
|
||||||
double progress = 0.2;
|
|
||||||
if (totalCount != null && totalCount! > 0) {
|
|
||||||
progress = 0.2 + (totalSynced / totalCount!) * 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isSyncing: true,
|
|
||||||
currentStep: SyncStep.products,
|
|
||||||
progress: progress,
|
|
||||||
message: totalCount != null
|
|
||||||
? 'Mengunduh produk... ($totalSynced dari $totalCount)'
|
|
||||||
: 'Mengunduh produk... ($totalSynced produk)',
|
|
||||||
errorMessage: null,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
final result = await _productRepository.getProducts(
|
|
||||||
page: page,
|
|
||||||
limit: 50, // ambil batch besar biar cepat
|
|
||||||
);
|
|
||||||
|
|
||||||
await result.fold(
|
|
||||||
(failure) async {
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
isSyncing: false,
|
|
||||||
errorMessage: 'Gagal sync produk: $failure',
|
|
||||||
message: 'Sinkronisasi produk gagal ❌',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
throw Exception(failure);
|
|
||||||
},
|
|
||||||
(response) async {
|
|
||||||
final products = response.products;
|
|
||||||
final responseData = response;
|
|
||||||
|
|
||||||
// Ambil total count & total page dari respons pertama
|
|
||||||
if (page == 1) {
|
|
||||||
totalCount = responseData.totalCount;
|
|
||||||
totalPages = responseData.totalPages;
|
|
||||||
log('📊 Total products to sync: $totalCount ($totalPages pages)');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (products.isEmpty) {
|
|
||||||
shouldContinue = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simpan batch produk ke local DB
|
|
||||||
await _productRepository.saveProductsBatch(products);
|
|
||||||
|
|
||||||
totalSynced += products.length;
|
|
||||||
page++;
|
|
||||||
|
|
||||||
log(
|
|
||||||
'📦 Synced page ${page - 1}: ${products.length} products (Total: $totalSynced)',
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cek apakah sudah selesai sync
|
|
||||||
if (totalPages != null && page > totalPages!) {
|
|
||||||
shouldContinue = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fallback jika pagination info tidak lengkap
|
|
||||||
if (products.length < 50) {
|
|
||||||
shouldContinue = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tambahkan delay kecil agar tidak overload server
|
|
||||||
await Future.delayed(const Duration(milliseconds: 100));
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Selesai sync produk
|
|
||||||
emit(
|
|
||||||
state.copyWith(
|
|
||||||
progress: 0.9,
|
|
||||||
message: 'Sinkronisasi produk selesai ✅ ($totalSynced total)',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SyncStats> _generateSyncStats() async {
|
|
||||||
try {
|
|
||||||
log('📊 Generating sync statistics via repository...');
|
|
||||||
|
|
||||||
// Jalankan kedua query secara paralel untuk efisiensi
|
|
||||||
final results = await Future.wait([
|
|
||||||
_productRepository.getDatabaseStats(),
|
|
||||||
_categoryRepository.getDatabaseStats(),
|
|
||||||
]);
|
|
||||||
|
|
||||||
// Default kosong
|
|
||||||
Map<String, dynamic> productStats = {};
|
|
||||||
Map<String, dynamic> categoryStats = {};
|
|
||||||
|
|
||||||
// Ambil hasil product stats
|
|
||||||
await results[0].fold(
|
|
||||||
(failure) async {
|
|
||||||
log('⚠️ Failed to get product stats: $failure');
|
|
||||||
},
|
|
||||||
(data) async {
|
|
||||||
productStats = data;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ambil hasil category stats
|
|
||||||
await results[1].fold(
|
|
||||||
(failure) async {
|
|
||||||
log('⚠️ Failed to get category stats: $failure');
|
|
||||||
},
|
|
||||||
(data) async {
|
|
||||||
categoryStats = data;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
// Bangun objek SyncStats akhir
|
|
||||||
final stats = SyncStats(
|
|
||||||
totalProducts: productStats['total_products'] ?? 0,
|
|
||||||
totalCategories: categoryStats['total_categories'] ?? 0,
|
|
||||||
totalVariants: productStats['total_variants'] ?? 0,
|
|
||||||
databaseSizeMB:
|
|
||||||
((productStats['database_size_mb'] ?? 0.0) as num).toDouble() +
|
|
||||||
((categoryStats['database_size_mb'] ?? 0.0) as num).toDouble(),
|
|
||||||
);
|
|
||||||
|
|
||||||
log('✅ Sync stats generated: $stats');
|
|
||||||
return stats;
|
|
||||||
} catch (e, stack) {
|
|
||||||
log('❌ Error generating sync stats: $e\n$stack');
|
|
||||||
return SyncStats(
|
|
||||||
totalProducts: 0,
|
|
||||||
totalCategories: 0,
|
|
||||||
totalVariants: 0,
|
|
||||||
databaseSizeMB: 0.0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> close() {
|
|
||||||
_progressTimer?.cancel();
|
|
||||||
return super.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,542 +0,0 @@
|
|||||||
// coverage:ignore-file
|
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
// ignore_for_file: type=lint
|
|
||||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
|
||||||
|
|
||||||
part of 'sync_bloc.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// FreezedGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
T _$identity<T>(T value) => value;
|
|
||||||
|
|
||||||
final _privateConstructorUsedError = UnsupportedError(
|
|
||||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
|
||||||
);
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SyncEvent {
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult when<TResult extends Object?>({
|
|
||||||
required TResult Function() startSync,
|
|
||||||
required TResult Function() cancelSync,
|
|
||||||
}) => throw _privateConstructorUsedError;
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
|
||||||
TResult? Function()? startSync,
|
|
||||||
TResult? Function()? cancelSync,
|
|
||||||
}) => throw _privateConstructorUsedError;
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
|
||||||
TResult Function()? startSync,
|
|
||||||
TResult Function()? cancelSync,
|
|
||||||
required TResult orElse(),
|
|
||||||
}) => throw _privateConstructorUsedError;
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult map<TResult extends Object?>({
|
|
||||||
required TResult Function(_StartSync value) startSync,
|
|
||||||
required TResult Function(_CancelSync value) cancelSync,
|
|
||||||
}) => throw _privateConstructorUsedError;
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult? mapOrNull<TResult extends Object?>({
|
|
||||||
TResult? Function(_StartSync value)? startSync,
|
|
||||||
TResult? Function(_CancelSync value)? cancelSync,
|
|
||||||
}) => throw _privateConstructorUsedError;
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeMap<TResult extends Object?>({
|
|
||||||
TResult Function(_StartSync value)? startSync,
|
|
||||||
TResult Function(_CancelSync value)? cancelSync,
|
|
||||||
required TResult orElse(),
|
|
||||||
}) => throw _privateConstructorUsedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class $SyncEventCopyWith<$Res> {
|
|
||||||
factory $SyncEventCopyWith(SyncEvent value, $Res Function(SyncEvent) then) =
|
|
||||||
_$SyncEventCopyWithImpl<$Res, SyncEvent>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class _$SyncEventCopyWithImpl<$Res, $Val extends SyncEvent>
|
|
||||||
implements $SyncEventCopyWith<$Res> {
|
|
||||||
_$SyncEventCopyWithImpl(this._value, this._then);
|
|
||||||
|
|
||||||
// ignore: unused_field
|
|
||||||
final $Val _value;
|
|
||||||
// ignore: unused_field
|
|
||||||
final $Res Function($Val) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SyncEvent
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class _$$StartSyncImplCopyWith<$Res> {
|
|
||||||
factory _$$StartSyncImplCopyWith(
|
|
||||||
_$StartSyncImpl value,
|
|
||||||
$Res Function(_$StartSyncImpl) then,
|
|
||||||
) = __$$StartSyncImplCopyWithImpl<$Res>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class __$$StartSyncImplCopyWithImpl<$Res>
|
|
||||||
extends _$SyncEventCopyWithImpl<$Res, _$StartSyncImpl>
|
|
||||||
implements _$$StartSyncImplCopyWith<$Res> {
|
|
||||||
__$$StartSyncImplCopyWithImpl(
|
|
||||||
_$StartSyncImpl _value,
|
|
||||||
$Res Function(_$StartSyncImpl) _then,
|
|
||||||
) : super(_value, _then);
|
|
||||||
|
|
||||||
/// Create a copy of SyncEvent
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
|
|
||||||
class _$StartSyncImpl implements _StartSync {
|
|
||||||
const _$StartSyncImpl();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SyncEvent.startSync()';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType && other is _$StartSyncImpl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => runtimeType.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult when<TResult extends Object?>({
|
|
||||||
required TResult Function() startSync,
|
|
||||||
required TResult Function() cancelSync,
|
|
||||||
}) {
|
|
||||||
return startSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
|
||||||
TResult? Function()? startSync,
|
|
||||||
TResult? Function()? cancelSync,
|
|
||||||
}) {
|
|
||||||
return startSync?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
|
||||||
TResult Function()? startSync,
|
|
||||||
TResult Function()? cancelSync,
|
|
||||||
required TResult orElse(),
|
|
||||||
}) {
|
|
||||||
if (startSync != null) {
|
|
||||||
return startSync();
|
|
||||||
}
|
|
||||||
return orElse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult map<TResult extends Object?>({
|
|
||||||
required TResult Function(_StartSync value) startSync,
|
|
||||||
required TResult Function(_CancelSync value) cancelSync,
|
|
||||||
}) {
|
|
||||||
return startSync(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult? mapOrNull<TResult extends Object?>({
|
|
||||||
TResult? Function(_StartSync value)? startSync,
|
|
||||||
TResult? Function(_CancelSync value)? cancelSync,
|
|
||||||
}) {
|
|
||||||
return startSync?.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeMap<TResult extends Object?>({
|
|
||||||
TResult Function(_StartSync value)? startSync,
|
|
||||||
TResult Function(_CancelSync value)? cancelSync,
|
|
||||||
required TResult orElse(),
|
|
||||||
}) {
|
|
||||||
if (startSync != null) {
|
|
||||||
return startSync(this);
|
|
||||||
}
|
|
||||||
return orElse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class _StartSync implements SyncEvent {
|
|
||||||
const factory _StartSync() = _$StartSyncImpl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class _$$CancelSyncImplCopyWith<$Res> {
|
|
||||||
factory _$$CancelSyncImplCopyWith(
|
|
||||||
_$CancelSyncImpl value,
|
|
||||||
$Res Function(_$CancelSyncImpl) then,
|
|
||||||
) = __$$CancelSyncImplCopyWithImpl<$Res>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class __$$CancelSyncImplCopyWithImpl<$Res>
|
|
||||||
extends _$SyncEventCopyWithImpl<$Res, _$CancelSyncImpl>
|
|
||||||
implements _$$CancelSyncImplCopyWith<$Res> {
|
|
||||||
__$$CancelSyncImplCopyWithImpl(
|
|
||||||
_$CancelSyncImpl _value,
|
|
||||||
$Res Function(_$CancelSyncImpl) _then,
|
|
||||||
) : super(_value, _then);
|
|
||||||
|
|
||||||
/// Create a copy of SyncEvent
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
|
|
||||||
class _$CancelSyncImpl implements _CancelSync {
|
|
||||||
const _$CancelSyncImpl();
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SyncEvent.cancelSync()';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType && other is _$CancelSyncImpl);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => runtimeType.hashCode;
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult when<TResult extends Object?>({
|
|
||||||
required TResult Function() startSync,
|
|
||||||
required TResult Function() cancelSync,
|
|
||||||
}) {
|
|
||||||
return cancelSync();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult? whenOrNull<TResult extends Object?>({
|
|
||||||
TResult? Function()? startSync,
|
|
||||||
TResult? Function()? cancelSync,
|
|
||||||
}) {
|
|
||||||
return cancelSync?.call();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeWhen<TResult extends Object?>({
|
|
||||||
TResult Function()? startSync,
|
|
||||||
TResult Function()? cancelSync,
|
|
||||||
required TResult orElse(),
|
|
||||||
}) {
|
|
||||||
if (cancelSync != null) {
|
|
||||||
return cancelSync();
|
|
||||||
}
|
|
||||||
return orElse();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult map<TResult extends Object?>({
|
|
||||||
required TResult Function(_StartSync value) startSync,
|
|
||||||
required TResult Function(_CancelSync value) cancelSync,
|
|
||||||
}) {
|
|
||||||
return cancelSync(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult? mapOrNull<TResult extends Object?>({
|
|
||||||
TResult? Function(_StartSync value)? startSync,
|
|
||||||
TResult? Function(_CancelSync value)? cancelSync,
|
|
||||||
}) {
|
|
||||||
return cancelSync?.call(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
@optionalTypeArgs
|
|
||||||
TResult maybeMap<TResult extends Object?>({
|
|
||||||
TResult Function(_StartSync value)? startSync,
|
|
||||||
TResult Function(_CancelSync value)? cancelSync,
|
|
||||||
required TResult orElse(),
|
|
||||||
}) {
|
|
||||||
if (cancelSync != null) {
|
|
||||||
return cancelSync(this);
|
|
||||||
}
|
|
||||||
return orElse();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class _CancelSync implements SyncEvent {
|
|
||||||
const factory _CancelSync() = _$CancelSyncImpl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
mixin _$SyncState {
|
|
||||||
bool get isSyncing => throw _privateConstructorUsedError;
|
|
||||||
double get progress => throw _privateConstructorUsedError;
|
|
||||||
SyncStep? get currentStep => throw _privateConstructorUsedError;
|
|
||||||
String? get message => throw _privateConstructorUsedError;
|
|
||||||
SyncStats? get stats => throw _privateConstructorUsedError;
|
|
||||||
String? get errorMessage => throw _privateConstructorUsedError;
|
|
||||||
|
|
||||||
/// Create a copy of SyncState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
$SyncStateCopyWith<SyncState> get copyWith =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class $SyncStateCopyWith<$Res> {
|
|
||||||
factory $SyncStateCopyWith(SyncState value, $Res Function(SyncState) then) =
|
|
||||||
_$SyncStateCopyWithImpl<$Res, SyncState>;
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
bool isSyncing,
|
|
||||||
double progress,
|
|
||||||
SyncStep? currentStep,
|
|
||||||
String? message,
|
|
||||||
SyncStats? stats,
|
|
||||||
String? errorMessage,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class _$SyncStateCopyWithImpl<$Res, $Val extends SyncState>
|
|
||||||
implements $SyncStateCopyWith<$Res> {
|
|
||||||
_$SyncStateCopyWithImpl(this._value, this._then);
|
|
||||||
|
|
||||||
// ignore: unused_field
|
|
||||||
final $Val _value;
|
|
||||||
// ignore: unused_field
|
|
||||||
final $Res Function($Val) _then;
|
|
||||||
|
|
||||||
/// Create a copy of SyncState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
@override
|
|
||||||
$Res call({
|
|
||||||
Object? isSyncing = null,
|
|
||||||
Object? progress = null,
|
|
||||||
Object? currentStep = freezed,
|
|
||||||
Object? message = freezed,
|
|
||||||
Object? stats = freezed,
|
|
||||||
Object? errorMessage = freezed,
|
|
||||||
}) {
|
|
||||||
return _then(
|
|
||||||
_value.copyWith(
|
|
||||||
isSyncing: null == isSyncing
|
|
||||||
? _value.isSyncing
|
|
||||||
: isSyncing // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
progress: null == progress
|
|
||||||
? _value.progress
|
|
||||||
: progress // ignore: cast_nullable_to_non_nullable
|
|
||||||
as double,
|
|
||||||
currentStep: freezed == currentStep
|
|
||||||
? _value.currentStep
|
|
||||||
: currentStep // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SyncStep?,
|
|
||||||
message: freezed == message
|
|
||||||
? _value.message
|
|
||||||
: message // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
stats: freezed == stats
|
|
||||||
? _value.stats
|
|
||||||
: stats // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SyncStats?,
|
|
||||||
errorMessage: freezed == errorMessage
|
|
||||||
? _value.errorMessage
|
|
||||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
)
|
|
||||||
as $Val,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
abstract class _$$SyncStateImplCopyWith<$Res>
|
|
||||||
implements $SyncStateCopyWith<$Res> {
|
|
||||||
factory _$$SyncStateImplCopyWith(
|
|
||||||
_$SyncStateImpl value,
|
|
||||||
$Res Function(_$SyncStateImpl) then,
|
|
||||||
) = __$$SyncStateImplCopyWithImpl<$Res>;
|
|
||||||
@override
|
|
||||||
@useResult
|
|
||||||
$Res call({
|
|
||||||
bool isSyncing,
|
|
||||||
double progress,
|
|
||||||
SyncStep? currentStep,
|
|
||||||
String? message,
|
|
||||||
SyncStats? stats,
|
|
||||||
String? errorMessage,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
class __$$SyncStateImplCopyWithImpl<$Res>
|
|
||||||
extends _$SyncStateCopyWithImpl<$Res, _$SyncStateImpl>
|
|
||||||
implements _$$SyncStateImplCopyWith<$Res> {
|
|
||||||
__$$SyncStateImplCopyWithImpl(
|
|
||||||
_$SyncStateImpl _value,
|
|
||||||
$Res Function(_$SyncStateImpl) _then,
|
|
||||||
) : super(_value, _then);
|
|
||||||
|
|
||||||
/// Create a copy of SyncState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
@override
|
|
||||||
$Res call({
|
|
||||||
Object? isSyncing = null,
|
|
||||||
Object? progress = null,
|
|
||||||
Object? currentStep = freezed,
|
|
||||||
Object? message = freezed,
|
|
||||||
Object? stats = freezed,
|
|
||||||
Object? errorMessage = freezed,
|
|
||||||
}) {
|
|
||||||
return _then(
|
|
||||||
_$SyncStateImpl(
|
|
||||||
isSyncing: null == isSyncing
|
|
||||||
? _value.isSyncing
|
|
||||||
: isSyncing // ignore: cast_nullable_to_non_nullable
|
|
||||||
as bool,
|
|
||||||
progress: null == progress
|
|
||||||
? _value.progress
|
|
||||||
: progress // ignore: cast_nullable_to_non_nullable
|
|
||||||
as double,
|
|
||||||
currentStep: freezed == currentStep
|
|
||||||
? _value.currentStep
|
|
||||||
: currentStep // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SyncStep?,
|
|
||||||
message: freezed == message
|
|
||||||
? _value.message
|
|
||||||
: message // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
stats: freezed == stats
|
|
||||||
? _value.stats
|
|
||||||
: stats // ignore: cast_nullable_to_non_nullable
|
|
||||||
as SyncStats?,
|
|
||||||
errorMessage: freezed == errorMessage
|
|
||||||
? _value.errorMessage
|
|
||||||
: errorMessage // ignore: cast_nullable_to_non_nullable
|
|
||||||
as String?,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// @nodoc
|
|
||||||
|
|
||||||
class _$SyncStateImpl implements _SyncState {
|
|
||||||
const _$SyncStateImpl({
|
|
||||||
this.isSyncing = false,
|
|
||||||
this.progress = 0.0,
|
|
||||||
this.currentStep,
|
|
||||||
this.message,
|
|
||||||
this.stats,
|
|
||||||
this.errorMessage,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final bool isSyncing;
|
|
||||||
@override
|
|
||||||
@JsonKey()
|
|
||||||
final double progress;
|
|
||||||
@override
|
|
||||||
final SyncStep? currentStep;
|
|
||||||
@override
|
|
||||||
final String? message;
|
|
||||||
@override
|
|
||||||
final SyncStats? stats;
|
|
||||||
@override
|
|
||||||
final String? errorMessage;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return 'SyncState(isSyncing: $isSyncing, progress: $progress, currentStep: $currentStep, message: $message, stats: $stats, errorMessage: $errorMessage)';
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return identical(this, other) ||
|
|
||||||
(other.runtimeType == runtimeType &&
|
|
||||||
other is _$SyncStateImpl &&
|
|
||||||
(identical(other.isSyncing, isSyncing) ||
|
|
||||||
other.isSyncing == isSyncing) &&
|
|
||||||
(identical(other.progress, progress) ||
|
|
||||||
other.progress == progress) &&
|
|
||||||
(identical(other.currentStep, currentStep) ||
|
|
||||||
other.currentStep == currentStep) &&
|
|
||||||
(identical(other.message, message) || other.message == message) &&
|
|
||||||
(identical(other.stats, stats) || other.stats == stats) &&
|
|
||||||
(identical(other.errorMessage, errorMessage) ||
|
|
||||||
other.errorMessage == errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode => Object.hash(
|
|
||||||
runtimeType,
|
|
||||||
isSyncing,
|
|
||||||
progress,
|
|
||||||
currentStep,
|
|
||||||
message,
|
|
||||||
stats,
|
|
||||||
errorMessage,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// Create a copy of SyncState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
@override
|
|
||||||
@pragma('vm:prefer-inline')
|
|
||||||
_$$SyncStateImplCopyWith<_$SyncStateImpl> get copyWith =>
|
|
||||||
__$$SyncStateImplCopyWithImpl<_$SyncStateImpl>(this, _$identity);
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract class _SyncState implements SyncState {
|
|
||||||
const factory _SyncState({
|
|
||||||
final bool isSyncing,
|
|
||||||
final double progress,
|
|
||||||
final SyncStep? currentStep,
|
|
||||||
final String? message,
|
|
||||||
final SyncStats? stats,
|
|
||||||
final String? errorMessage,
|
|
||||||
}) = _$SyncStateImpl;
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get isSyncing;
|
|
||||||
@override
|
|
||||||
double get progress;
|
|
||||||
@override
|
|
||||||
SyncStep? get currentStep;
|
|
||||||
@override
|
|
||||||
String? get message;
|
|
||||||
@override
|
|
||||||
SyncStats? get stats;
|
|
||||||
@override
|
|
||||||
String? get errorMessage;
|
|
||||||
|
|
||||||
/// Create a copy of SyncState
|
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
|
||||||
@override
|
|
||||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
|
||||||
_$$SyncStateImplCopyWith<_$SyncStateImpl> get copyWith =>
|
|
||||||
throw _privateConstructorUsedError;
|
|
||||||
}
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
part of 'sync_bloc.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class SyncEvent with _$SyncEvent {
|
|
||||||
const factory SyncEvent.startSync() = _StartSync;
|
|
||||||
const factory SyncEvent.cancelSync() = _CancelSync;
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
part of 'sync_bloc.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class SyncState with _$SyncState {
|
|
||||||
const factory SyncState({
|
|
||||||
@Default(false) bool isSyncing,
|
|
||||||
@Default(0.0) double progress,
|
|
||||||
SyncStep? currentStep,
|
|
||||||
String? message,
|
|
||||||
SyncStats? stats,
|
|
||||||
String? errorMessage,
|
|
||||||
}) = _SyncState;
|
|
||||||
|
|
||||||
factory SyncState.initial() => const SyncState();
|
|
||||||
}
|
|
||||||
@ -2,5 +2,4 @@ class ApiPath {
|
|||||||
static const String login = '/api/v1/auth/login';
|
static const String login = '/api/v1/auth/login';
|
||||||
static const String outlets = '/api/v1/outlets';
|
static const String outlets = '/api/v1/outlets';
|
||||||
static const String categories = '/api/v1/categories';
|
static const String categories = '/api/v1/categories';
|
||||||
static const String products = '/api/v1/products';
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,6 +23,4 @@ abstract class ICategoryRepository {
|
|||||||
Future<Either<CategoryFailure, Map<String, dynamic>>> getDatabaseStats();
|
Future<Either<CategoryFailure, Map<String, dynamic>>> getDatabaseStats();
|
||||||
|
|
||||||
void clearCache();
|
void clearCache();
|
||||||
|
|
||||||
Future<void> clearAllCategories();
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,86 +0,0 @@
|
|||||||
part of '../product.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class ListProduct with _$ListProduct {
|
|
||||||
const factory ListProduct({
|
|
||||||
required List<Product> products,
|
|
||||||
required int totalCount,
|
|
||||||
required int page,
|
|
||||||
required int limit,
|
|
||||||
required int totalPages,
|
|
||||||
}) = _ListProduct;
|
|
||||||
|
|
||||||
factory ListProduct.empty() => const ListProduct(
|
|
||||||
products: [],
|
|
||||||
totalCount: 0,
|
|
||||||
page: 0,
|
|
||||||
limit: 0,
|
|
||||||
totalPages: 0,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class Product with _$Product {
|
|
||||||
const factory Product({
|
|
||||||
required String id,
|
|
||||||
required String organizationId,
|
|
||||||
required String categoryId,
|
|
||||||
required String sku,
|
|
||||||
required String name,
|
|
||||||
required String description,
|
|
||||||
required double price,
|
|
||||||
required double cost,
|
|
||||||
required String businessType,
|
|
||||||
required String imageUrl,
|
|
||||||
required String printerType,
|
|
||||||
required Map<String, dynamic> metadata,
|
|
||||||
required bool isActive,
|
|
||||||
required String createdAt,
|
|
||||||
required String updatedAt,
|
|
||||||
required List<ProductVariant> variants,
|
|
||||||
}) = _Product;
|
|
||||||
|
|
||||||
factory Product.empty() => const Product(
|
|
||||||
id: '',
|
|
||||||
organizationId: '',
|
|
||||||
categoryId: '',
|
|
||||||
sku: '',
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
price: 0.0,
|
|
||||||
cost: 0.0,
|
|
||||||
businessType: '',
|
|
||||||
imageUrl: '',
|
|
||||||
printerType: '',
|
|
||||||
metadata: {},
|
|
||||||
isActive: false,
|
|
||||||
createdAt: '',
|
|
||||||
updatedAt: '',
|
|
||||||
variants: [],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class ProductVariant with _$ProductVariant {
|
|
||||||
const factory ProductVariant({
|
|
||||||
required String id,
|
|
||||||
required String productId,
|
|
||||||
required String name,
|
|
||||||
required double priceModifier,
|
|
||||||
required double cost,
|
|
||||||
required Map<String, dynamic> metadata,
|
|
||||||
required String createdAt,
|
|
||||||
required String updatedAt,
|
|
||||||
}) = _ProductVariant;
|
|
||||||
|
|
||||||
factory ProductVariant.empty() => const ProductVariant(
|
|
||||||
id: '',
|
|
||||||
productId: '',
|
|
||||||
name: '',
|
|
||||||
priceModifier: 0.0,
|
|
||||||
cost: 0.0,
|
|
||||||
metadata: {},
|
|
||||||
createdAt: '',
|
|
||||||
updatedAt: '',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
part of '../product.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
sealed class ProductFailure with _$ProductFailure {
|
|
||||||
const factory ProductFailure.serverError(ApiFailure failure) = _ServerError;
|
|
||||||
const factory ProductFailure.unexpectedError() = _UnexpectedError;
|
|
||||||
const factory ProductFailure.empty() = _Empty;
|
|
||||||
const factory ProductFailure.localStorageError(String erroMessage) =
|
|
||||||
_LocalStorageError;
|
|
||||||
const factory ProductFailure.dynamicErrorMessage(String erroMessage) =
|
|
||||||
_DynamicErrorMessage;
|
|
||||||
}
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
|
|
||||||
import '../../common/api/api_failure.dart';
|
|
||||||
|
|
||||||
part 'product.freezed.dart';
|
|
||||||
|
|
||||||
part 'entities/product_entity.dart';
|
|
||||||
part 'failures/product_failure.dart';
|
|
||||||
part 'repositories/i_product_repository.dart';
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,38 +0,0 @@
|
|||||||
part of '../product.dart';
|
|
||||||
|
|
||||||
abstract class IProductRepository {
|
|
||||||
Future<Either<ProductFailure, Unit>> saveProductsBatch(
|
|
||||||
List<Product> products,
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<Either<ProductFailure, ListProduct>> getProducts({
|
|
||||||
int page = 1,
|
|
||||||
int limit = 10,
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
bool forceRefresh = false,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<Either<ProductFailure, List<Product>>> searchProductsOptimized(
|
|
||||||
String query,
|
|
||||||
);
|
|
||||||
|
|
||||||
Future<Either<ProductFailure, String>> syncAllProducts();
|
|
||||||
|
|
||||||
Future<Either<ProductFailure, ListProduct>> refreshProducts({
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
});
|
|
||||||
|
|
||||||
Future<Either<ProductFailure, Product>> getProductById(String id);
|
|
||||||
|
|
||||||
Future<bool> hasLocalProducts();
|
|
||||||
|
|
||||||
Future<Either<ProductFailure, Map<String, dynamic>>> getDatabaseStats();
|
|
||||||
|
|
||||||
void clearCache();
|
|
||||||
|
|
||||||
Future<bool> isLocalDatabaseReady();
|
|
||||||
|
|
||||||
Future<void> clearAllProducts();
|
|
||||||
}
|
|
||||||
@ -349,17 +349,4 @@ class CategoryRepository implements ICategoryRepository {
|
|||||||
log('🧹 Clearing category cache', name: _logName);
|
log('🧹 Clearing category cache', name: _logName);
|
||||||
_localDataProvider.clearCache();
|
_localDataProvider.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> clearAllCategories() async {
|
|
||||||
try {
|
|
||||||
log('🗑️ Clearing all categories from repository...', name: _logName);
|
|
||||||
await _localDataProvider.clearAllCategories();
|
|
||||||
clearCache();
|
|
||||||
log('✅ All categories cleared successfully', name: _logName);
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Error clearing all categories: $e', name: _logName);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,486 +0,0 @@
|
|||||||
import 'dart:developer';
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:data_channel/data_channel.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
import 'package:sqflite/sqflite.dart';
|
|
||||||
import 'package:path/path.dart' as p;
|
|
||||||
|
|
||||||
import '../../../common/constant/app_constant.dart';
|
|
||||||
import '../../../common/database/database_helper.dart';
|
|
||||||
import '../../../domain/product/product.dart';
|
|
||||||
import '../product_dtos.dart';
|
|
||||||
|
|
||||||
@injectable
|
|
||||||
class ProductLocalDataProvider {
|
|
||||||
final DatabaseHelper _databaseHelper;
|
|
||||||
final _logName = 'ProductLocalDataProvider';
|
|
||||||
|
|
||||||
final Map<String, List<ProductDto>> _queryCache = {};
|
|
||||||
final Duration _cacheExpiry = Duration(minutes: AppConstant.cacheExpire);
|
|
||||||
final Map<String, DateTime> _cacheTimestamps = {};
|
|
||||||
|
|
||||||
ProductLocalDataProvider(this._databaseHelper);
|
|
||||||
|
|
||||||
Future<DC<ProductFailure, void>> saveProductsBatch(
|
|
||||||
List<ProductDto> products, {
|
|
||||||
bool clearFirst = false,
|
|
||||||
}) async {
|
|
||||||
final db = await _databaseHelper.database;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.transaction((txn) async {
|
|
||||||
if (clearFirst) {
|
|
||||||
log('🗑️ Clearing existing products...', name: _logName);
|
|
||||||
await txn.delete('product_variants');
|
|
||||||
await txn.delete('products');
|
|
||||||
}
|
|
||||||
|
|
||||||
log('💾 Batch saving ${products.length} products...', name: _logName);
|
|
||||||
|
|
||||||
// ✅ Batch insert products
|
|
||||||
final productBatch = txn.batch();
|
|
||||||
for (final product in products) {
|
|
||||||
productBatch.insert(
|
|
||||||
'products',
|
|
||||||
product.toMap(),
|
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
await productBatch.commit(noResult: true);
|
|
||||||
|
|
||||||
// ✅ Batch insert variants
|
|
||||||
final variantBatch = txn.batch();
|
|
||||||
for (final product in products) {
|
|
||||||
if (product.variants?.isNotEmpty == true) {
|
|
||||||
// Delete existing variants
|
|
||||||
variantBatch.delete(
|
|
||||||
'product_variants',
|
|
||||||
where: 'product_id = ?',
|
|
||||||
whereArgs: [product.id],
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert variants
|
|
||||||
for (final variant in product.variants!) {
|
|
||||||
variantBatch.insert(
|
|
||||||
'product_variants',
|
|
||||||
variant.toMap(),
|
|
||||||
conflictAlgorithm: ConflictAlgorithm.replace,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
await variantBatch.commit(noResult: true);
|
|
||||||
});
|
|
||||||
|
|
||||||
// ✅ Clear cache
|
|
||||||
clearCache();
|
|
||||||
log(
|
|
||||||
'✅ Successfully batch saved ${products.length} products',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return DC.data(null);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error batch saving products',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
|
|
||||||
return DC.error(ProductFailure.dynamicErrorMessage(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DC<ProductFailure, List<ProductDto>>> getCachedProducts({
|
|
||||||
int page = 1,
|
|
||||||
int limit = 10,
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
}) async {
|
|
||||||
final cacheKey = _generateCacheKey(page, limit, categoryId, search);
|
|
||||||
final now = DateTime.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// ✅ CHECK CACHE FIRST
|
|
||||||
if (_queryCache.containsKey(cacheKey) &&
|
|
||||||
_cacheTimestamps.containsKey(cacheKey)) {
|
|
||||||
final cacheTime = _cacheTimestamps[cacheKey]!;
|
|
||||||
if (now.difference(cacheTime) < _cacheExpiry) {
|
|
||||||
final cachedProducts = _queryCache[cacheKey]!;
|
|
||||||
log(
|
|
||||||
'🚀 Cache HIT: $cacheKey (${cachedProducts.length} products)',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
return DC.data(cachedProducts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log('📀 Cache MISS: $cacheKey, querying database...', name: _logName);
|
|
||||||
|
|
||||||
// Cache miss → query database
|
|
||||||
final result = await getProducts(
|
|
||||||
page: page,
|
|
||||||
limit: limit,
|
|
||||||
categoryId: categoryId,
|
|
||||||
search: search,
|
|
||||||
);
|
|
||||||
|
|
||||||
// ✅ Handle data/error dari getProducts()
|
|
||||||
if (result.hasData) {
|
|
||||||
final products = result.data!;
|
|
||||||
|
|
||||||
// Simpan ke cache
|
|
||||||
_queryCache[cacheKey] = products;
|
|
||||||
_cacheTimestamps[cacheKey] = now;
|
|
||||||
|
|
||||||
log(
|
|
||||||
'💾 Cached ${products.length} products for key: $cacheKey',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return DC.data(products);
|
|
||||||
} else {
|
|
||||||
// Kalau error dari getProducts
|
|
||||||
return DC.error(result.error!);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error getting cached products',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
|
|
||||||
return DC.error(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DC<ProductFailure, List<ProductDto>>> getProducts({
|
|
||||||
int page = 1,
|
|
||||||
int limit = 10,
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
}) async {
|
|
||||||
final db = await _databaseHelper.database;
|
|
||||||
|
|
||||||
try {
|
|
||||||
String query = 'SELECT * FROM products WHERE 1=1';
|
|
||||||
List<dynamic> whereArgs = [];
|
|
||||||
|
|
||||||
if (categoryId != null && categoryId.isNotEmpty) {
|
|
||||||
query += ' AND category_id = ?';
|
|
||||||
whereArgs.add(categoryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search != null && search.isNotEmpty) {
|
|
||||||
query += ' AND (name LIKE ? OR sku LIKE ? OR description LIKE ?)';
|
|
||||||
whereArgs.add('%$search%');
|
|
||||||
whereArgs.add('%$search%');
|
|
||||||
whereArgs.add('%$search%');
|
|
||||||
}
|
|
||||||
|
|
||||||
query += ' ORDER BY created_at DESC';
|
|
||||||
|
|
||||||
if (limit > 0) {
|
|
||||||
query += ' LIMIT ?';
|
|
||||||
whereArgs.add(limit);
|
|
||||||
|
|
||||||
if (page > 1) {
|
|
||||||
query += ' OFFSET ?';
|
|
||||||
whereArgs.add((page - 1) * limit);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Map<String, dynamic>> maps = await db.rawQuery(
|
|
||||||
query,
|
|
||||||
whereArgs,
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<ProductDto> products = [];
|
|
||||||
for (final map in maps) {
|
|
||||||
final variants = await _getProductVariants(db, map['id']);
|
|
||||||
final product = ProductDto.fromMap(map, variants);
|
|
||||||
products.add(product);
|
|
||||||
}
|
|
||||||
|
|
||||||
log(
|
|
||||||
'📊 Retrieved ${products.length} products from database',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return DC.data(products);
|
|
||||||
} catch (e, s) {
|
|
||||||
log('❌ Error getting products', name: _logName, error: e, stackTrace: s);
|
|
||||||
return DC.error(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DC<ProductFailure, List<ProductDto>>> searchProductsOptimized(
|
|
||||||
String query,
|
|
||||||
) async {
|
|
||||||
final db = await _databaseHelper.database;
|
|
||||||
|
|
||||||
try {
|
|
||||||
log('🔍 Optimized search for: "$query"', name: _logName);
|
|
||||||
|
|
||||||
// ✅ Smart query with prioritization
|
|
||||||
final List<Map<String, dynamic>> maps = await db.rawQuery(
|
|
||||||
'''
|
|
||||||
SELECT * FROM products
|
|
||||||
WHERE name LIKE ? OR sku LIKE ? OR description LIKE ?
|
|
||||||
ORDER BY
|
|
||||||
CASE
|
|
||||||
WHEN name LIKE ? THEN 1 -- Highest priority: name match
|
|
||||||
WHEN sku LIKE ? THEN 2 -- Second priority: SKU match
|
|
||||||
ELSE 3 -- Lowest priority: description
|
|
||||||
END,
|
|
||||||
name ASC
|
|
||||||
LIMIT 50
|
|
||||||
''',
|
|
||||||
[
|
|
||||||
'%$query%', '%$query%', '%$query%',
|
|
||||||
'$query%', '$query%', // Prioritize results that start with query
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
final List<ProductDto> products = [];
|
|
||||||
for (final map in maps) {
|
|
||||||
final variants = await _getProductVariants(db, map['id']);
|
|
||||||
final product = ProductDto.fromMap(map, variants);
|
|
||||||
products.add(product);
|
|
||||||
}
|
|
||||||
|
|
||||||
log(
|
|
||||||
'🎯 Optimized search found ${products.length} results',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return DC.data(products);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error in optimized search',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
return DC.error(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DC<ProductFailure, ProductDto>> getProductById(String id) async {
|
|
||||||
final db = await _databaseHelper.database;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final List<Map<String, dynamic>> maps = await db.query(
|
|
||||||
'products',
|
|
||||||
where: 'id = ?',
|
|
||||||
whereArgs: [id],
|
|
||||||
);
|
|
||||||
|
|
||||||
if (maps.isEmpty) {
|
|
||||||
log('❌ Product not found: $id', name: _logName);
|
|
||||||
return DC.error(ProductFailure.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
final variants = await _getProductVariants(db, id);
|
|
||||||
final product = ProductDto.fromMap(maps.first, variants);
|
|
||||||
|
|
||||||
log('✅ Product found: ${product.name}', name: _logName);
|
|
||||||
return DC.data(product);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error getting product by ID',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
return DC.error(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<DC<ProductFailure, Map<String, dynamic>>> getDatabaseStats() async {
|
|
||||||
final db = await _databaseHelper.database;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final productCount =
|
|
||||||
Sqflite.firstIntValue(
|
|
||||||
await db.rawQuery('SELECT COUNT(*) FROM products'),
|
|
||||||
) ??
|
|
||||||
0;
|
|
||||||
|
|
||||||
final variantCount =
|
|
||||||
Sqflite.firstIntValue(
|
|
||||||
await db.rawQuery('SELECT COUNT(*) FROM product_variants'),
|
|
||||||
) ??
|
|
||||||
0;
|
|
||||||
|
|
||||||
final categoryCount =
|
|
||||||
Sqflite.firstIntValue(
|
|
||||||
await db.rawQuery(
|
|
||||||
'SELECT COUNT(DISTINCT category_id) FROM products WHERE category_id IS NOT NULL',
|
|
||||||
),
|
|
||||||
) ??
|
|
||||||
0;
|
|
||||||
|
|
||||||
final dbSize = await _getDatabaseSize();
|
|
||||||
|
|
||||||
final stats = {
|
|
||||||
'total_products': productCount,
|
|
||||||
'total_variants': variantCount,
|
|
||||||
'total_categories': categoryCount,
|
|
||||||
'database_size_mb': dbSize,
|
|
||||||
'cache_entries': _queryCache.length,
|
|
||||||
'cache_size_mb': _getCacheSize(),
|
|
||||||
};
|
|
||||||
|
|
||||||
log('📊 Database Stats: $stats', name: _logName);
|
|
||||||
|
|
||||||
return DC.data(stats);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error getting database stats',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
return DC.error(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<int> getTotalCount({String? categoryId, String? search}) async {
|
|
||||||
final db = await _databaseHelper.database;
|
|
||||||
|
|
||||||
try {
|
|
||||||
String query = 'SELECT COUNT(*) FROM products WHERE 1=1';
|
|
||||||
List<dynamic> whereArgs = [];
|
|
||||||
|
|
||||||
if (categoryId != null && categoryId.isNotEmpty) {
|
|
||||||
query += ' AND category_id = ?';
|
|
||||||
whereArgs.add(categoryId);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search != null && search.isNotEmpty) {
|
|
||||||
query += ' AND (name LIKE ? OR sku LIKE ? OR description LIKE ?)';
|
|
||||||
whereArgs.add('%$search%');
|
|
||||||
whereArgs.add('%$search%');
|
|
||||||
whereArgs.add('%$search%');
|
|
||||||
}
|
|
||||||
|
|
||||||
final result = await db.rawQuery(query, whereArgs);
|
|
||||||
final count = Sqflite.firstIntValue(result) ?? 0;
|
|
||||||
log(
|
|
||||||
'📊 Total count: $count (categoryId: $categoryId, search: $search)',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
return count;
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Error getting total count: $e', name: _logName);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> hasProducts() async {
|
|
||||||
final count = await getTotalCount();
|
|
||||||
final hasData = count > 0;
|
|
||||||
log('🔍 Has products: $hasData ($count products)', name: _logName);
|
|
||||||
return hasData;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> clearAllProducts() async {
|
|
||||||
final db = await _databaseHelper.database;
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.transaction((txn) async {
|
|
||||||
await txn.delete('product_variants');
|
|
||||||
await txn.delete('products');
|
|
||||||
});
|
|
||||||
clearCache();
|
|
||||||
log('🗑️ All products cleared from local DB', name: _logName);
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Error clearing products: $e', name: _logName);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<double> _getDatabaseSize() async {
|
|
||||||
try {
|
|
||||||
final dbPath = p.join(await getDatabasesPath(), 'db_pos.db');
|
|
||||||
final file = File(dbPath);
|
|
||||||
if (await file.exists()) {
|
|
||||||
final size = await file.length();
|
|
||||||
return size / (1024 * 1024); // Convert to MB
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
log('Error getting database size: $e', name: _logName);
|
|
||||||
}
|
|
||||||
return 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List<ProductVariantDto>> _getProductVariants(
|
|
||||||
Database db,
|
|
||||||
String productId,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
final List<Map<String, dynamic>> maps = await db.query(
|
|
||||||
'product_variants',
|
|
||||||
where: 'product_id = ?',
|
|
||||||
whereArgs: [productId],
|
|
||||||
orderBy: 'name ASC',
|
|
||||||
);
|
|
||||||
|
|
||||||
return maps.map((map) => ProductVariantDto.fromMap(map)).toList();
|
|
||||||
} catch (e) {
|
|
||||||
log(
|
|
||||||
'❌ Error getting variants for product $productId: $e',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String _generateCacheKey(
|
|
||||||
int page,
|
|
||||||
int limit,
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
) {
|
|
||||||
return 'products_${page}_${limit}_${categoryId ?? 'null'}_${search ?? 'null'}';
|
|
||||||
}
|
|
||||||
|
|
||||||
double _getCacheSize() {
|
|
||||||
double totalSize = 0;
|
|
||||||
_queryCache.forEach((key, products) {
|
|
||||||
totalSize += products.length * 0.001; // Rough estimate in MB
|
|
||||||
});
|
|
||||||
return totalSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearCache() {
|
|
||||||
final count = _queryCache.length;
|
|
||||||
_queryCache.clear();
|
|
||||||
_cacheTimestamps.clear();
|
|
||||||
log('🧹 Cache cleared: $count entries removed', name: _logName);
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearExpiredCache() {
|
|
||||||
final now = DateTime.now();
|
|
||||||
final expiredKeys = <String>[];
|
|
||||||
|
|
||||||
_cacheTimestamps.forEach((key, timestamp) {
|
|
||||||
if (now.difference(timestamp) > _cacheExpiry) {
|
|
||||||
expiredKeys.add(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (final key in expiredKeys) {
|
|
||||||
_queryCache.remove(key);
|
|
||||||
_cacheTimestamps.remove(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (expiredKeys.isNotEmpty) {
|
|
||||||
log('⏰ Expired cache cleared: ${expiredKeys.length} entries');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:data_channel/data_channel.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
import '../../../common/api/api_client.dart';
|
|
||||||
import '../../../common/api/api_failure.dart';
|
|
||||||
import '../../../common/function/app_function.dart';
|
|
||||||
import '../../../common/url/api_path.dart';
|
|
||||||
import '../../../domain/product/product.dart';
|
|
||||||
import '../product_dtos.dart';
|
|
||||||
|
|
||||||
@injectable
|
|
||||||
class ProductRemoteDataProvider {
|
|
||||||
final ApiClient _apiClient;
|
|
||||||
final _logName = 'ProductRemoteDataProvider';
|
|
||||||
|
|
||||||
ProductRemoteDataProvider(this._apiClient);
|
|
||||||
|
|
||||||
Future<DC<ProductFailure, ListProductDto>> fetchProducts({
|
|
||||||
int page = 1,
|
|
||||||
int limit = 10,
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
Map<String, dynamic> queryParameters = {'page': page, 'limit': limit};
|
|
||||||
|
|
||||||
if (categoryId != null) {
|
|
||||||
queryParameters['category_id'] = categoryId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search != null && search.isNotEmpty) {
|
|
||||||
queryParameters['search'] = search;
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await _apiClient.get(
|
|
||||||
ApiPath.products,
|
|
||||||
params: queryParameters,
|
|
||||||
headers: getAuthorizationHeader(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.data['data'] == null) {
|
|
||||||
return DC.error(ProductFailure.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
final categories = ListProductDto.fromJson(
|
|
||||||
response.data['data'] as Map<String, dynamic>,
|
|
||||||
);
|
|
||||||
|
|
||||||
return DC.data(categories);
|
|
||||||
} on ApiFailure catch (e, s) {
|
|
||||||
log('fetchProductError', name: _logName, error: e, stackTrace: s);
|
|
||||||
return DC.error(ProductFailure.serverError(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,206 +0,0 @@
|
|||||||
part of '../product_dtos.dart';
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class ListProductDto with _$ListProductDto {
|
|
||||||
const ListProductDto._();
|
|
||||||
|
|
||||||
const factory ListProductDto({
|
|
||||||
@JsonKey(name: "products") required List<ProductDto> products,
|
|
||||||
@JsonKey(name: "total_count") required int totalCount,
|
|
||||||
@JsonKey(name: "page") required int page,
|
|
||||||
@JsonKey(name: "limit") required int limit,
|
|
||||||
@JsonKey(name: "total_pages") required int totalPages,
|
|
||||||
}) = _ListProductDto;
|
|
||||||
|
|
||||||
factory ListProductDto.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$ListProductDtoFromJson(json);
|
|
||||||
|
|
||||||
ListProduct toDomain() => ListProduct(
|
|
||||||
products: products.map((dto) => dto.toDomain()).toList(),
|
|
||||||
totalCount: totalCount,
|
|
||||||
page: page,
|
|
||||||
limit: limit,
|
|
||||||
totalPages: totalPages,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class ProductDto with _$ProductDto {
|
|
||||||
const ProductDto._();
|
|
||||||
|
|
||||||
const factory ProductDto({
|
|
||||||
@JsonKey(name: "id") String? id,
|
|
||||||
@JsonKey(name: "organization_id") String? organizationId,
|
|
||||||
@JsonKey(name: "category_id") String? categoryId,
|
|
||||||
@JsonKey(name: "sku") String? sku,
|
|
||||||
@JsonKey(name: "name") String? name,
|
|
||||||
@JsonKey(name: "description") String? description,
|
|
||||||
@JsonKey(name: "price") double? price,
|
|
||||||
@JsonKey(name: "cost") double? cost,
|
|
||||||
@JsonKey(name: "business_type") String? businessType,
|
|
||||||
@JsonKey(name: "image_url") String? imageUrl,
|
|
||||||
@JsonKey(name: "printer_type") String? printerType,
|
|
||||||
@JsonKey(name: "metadata") Map<String, dynamic>? metadata,
|
|
||||||
@JsonKey(name: "is_active") bool? isActive,
|
|
||||||
@JsonKey(name: "created_at") String? createdAt,
|
|
||||||
@JsonKey(name: "updated_at") String? updatedAt,
|
|
||||||
@JsonKey(name: "variants") List<ProductVariantDto>? variants,
|
|
||||||
}) = _ProductDto;
|
|
||||||
|
|
||||||
factory ProductDto.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$ProductDtoFromJson(json);
|
|
||||||
|
|
||||||
/// Mapping ke domain
|
|
||||||
Product toDomain() => Product(
|
|
||||||
id: id ?? '',
|
|
||||||
organizationId: organizationId ?? '',
|
|
||||||
categoryId: categoryId ?? '',
|
|
||||||
sku: sku ?? '',
|
|
||||||
name: name ?? '',
|
|
||||||
description: description ?? '',
|
|
||||||
price: price ?? 0.0,
|
|
||||||
cost: cost ?? 0.0,
|
|
||||||
businessType: businessType ?? '',
|
|
||||||
imageUrl: imageUrl ?? '',
|
|
||||||
printerType: printerType ?? '',
|
|
||||||
metadata: metadata ?? {},
|
|
||||||
isActive: isActive ?? false,
|
|
||||||
createdAt: createdAt ?? '',
|
|
||||||
updatedAt: updatedAt ?? '',
|
|
||||||
variants: variants?.map((v) => v.toDomain()).toList() ?? [],
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'id': id,
|
|
||||||
'organization_id': organizationId,
|
|
||||||
'category_id': categoryId,
|
|
||||||
'sku': sku,
|
|
||||||
'name': name,
|
|
||||||
'description': description,
|
|
||||||
'price': price,
|
|
||||||
'cost': cost,
|
|
||||||
'business_type': businessType,
|
|
||||||
'image_url': imageUrl,
|
|
||||||
'printer_type': printerType,
|
|
||||||
'metadata': metadata != null ? jsonEncode(metadata) : null,
|
|
||||||
'is_active': isActive == true ? 1 : 0,
|
|
||||||
'created_at': createdAt,
|
|
||||||
'updated_at': updatedAt,
|
|
||||||
};
|
|
||||||
|
|
||||||
factory ProductDto.fromMap(
|
|
||||||
Map<String, dynamic> map,
|
|
||||||
List<ProductVariantDto> variants,
|
|
||||||
) => ProductDto(
|
|
||||||
id: map['id'] as String?,
|
|
||||||
organizationId: map['organization_id'] as String?,
|
|
||||||
categoryId: map['category_id'] as String?,
|
|
||||||
sku: map['sku'] as String?,
|
|
||||||
name: map['name'] as String?,
|
|
||||||
description: map['description'] as String?,
|
|
||||||
price: map['price'] != null ? (map['price'] as num).toDouble() : null,
|
|
||||||
cost: map['cost'] != null ? (map['cost'] as num).toDouble() : null,
|
|
||||||
businessType: map['business_type'] as String?,
|
|
||||||
imageUrl: map['image_url'] as String?,
|
|
||||||
printerType: map['printer_type'] as String?,
|
|
||||||
metadata: map['metadata'] != null
|
|
||||||
? jsonDecode(map['metadata'] as String) as Map<String, dynamic>
|
|
||||||
: null,
|
|
||||||
isActive: map['is_active'] != null ? (map['is_active'] as int) == 1 : null,
|
|
||||||
|
|
||||||
createdAt: map['created_at'] as String?,
|
|
||||||
updatedAt: map['updated_at'] as String?,
|
|
||||||
variants: variants,
|
|
||||||
);
|
|
||||||
|
|
||||||
factory ProductDto.fromDomain(Product product) => ProductDto(
|
|
||||||
id: product.id,
|
|
||||||
organizationId: product.organizationId,
|
|
||||||
categoryId: product.categoryId,
|
|
||||||
sku: product.sku,
|
|
||||||
name: product.name,
|
|
||||||
description: product.description,
|
|
||||||
price: product.price,
|
|
||||||
cost: product.cost,
|
|
||||||
businessType: product.businessType,
|
|
||||||
imageUrl: product.imageUrl,
|
|
||||||
printerType: product.printerType,
|
|
||||||
metadata: product.metadata,
|
|
||||||
isActive: product.isActive,
|
|
||||||
createdAt: product.createdAt,
|
|
||||||
updatedAt: product.updatedAt,
|
|
||||||
variants: product.variants
|
|
||||||
.map((v) => ProductVariantDto.fromDomain(v))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@freezed
|
|
||||||
class ProductVariantDto with _$ProductVariantDto {
|
|
||||||
const ProductVariantDto._();
|
|
||||||
|
|
||||||
const factory ProductVariantDto({
|
|
||||||
@JsonKey(name: "id") String? id,
|
|
||||||
@JsonKey(name: "product_id") String? productId,
|
|
||||||
@JsonKey(name: "name") String? name,
|
|
||||||
@JsonKey(name: "price_modifier") double? priceModifier,
|
|
||||||
@JsonKey(name: "cost") double? cost,
|
|
||||||
@JsonKey(name: "metadata") Map<String, dynamic>? metadata,
|
|
||||||
@JsonKey(name: "created_at") String? createdAt,
|
|
||||||
@JsonKey(name: "updated_at") String? updatedAt,
|
|
||||||
}) = _ProductVariantDto;
|
|
||||||
|
|
||||||
factory ProductVariantDto.fromJson(Map<String, dynamic> json) =>
|
|
||||||
_$ProductVariantDtoFromJson(json);
|
|
||||||
|
|
||||||
/// Mapping ke domain
|
|
||||||
ProductVariant toDomain() => ProductVariant(
|
|
||||||
id: id ?? '',
|
|
||||||
productId: productId ?? '',
|
|
||||||
name: name ?? '',
|
|
||||||
priceModifier: priceModifier ?? 0.0,
|
|
||||||
cost: cost ?? 0.0,
|
|
||||||
metadata: metadata ?? {},
|
|
||||||
createdAt: createdAt ?? '',
|
|
||||||
updatedAt: updatedAt ?? '',
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() => {
|
|
||||||
'id': id,
|
|
||||||
'product_id': productId,
|
|
||||||
'name': name,
|
|
||||||
'price_modifier': priceModifier,
|
|
||||||
'cost': cost,
|
|
||||||
'metadata': metadata != null ? jsonEncode(metadata) : null,
|
|
||||||
'created_at': createdAt,
|
|
||||||
'updated_at': updatedAt,
|
|
||||||
};
|
|
||||||
|
|
||||||
factory ProductVariantDto.fromMap(Map<String, dynamic> map) =>
|
|
||||||
ProductVariantDto(
|
|
||||||
id: map['id'] as String?,
|
|
||||||
productId: map['product_id'] as String?,
|
|
||||||
name: map['name'] as String?,
|
|
||||||
priceModifier: map['price_modifier'] != null
|
|
||||||
? (map['price_modifier'] as num).toDouble()
|
|
||||||
: null,
|
|
||||||
cost: map['cost'] != null ? (map['cost'] as num).toDouble() : null,
|
|
||||||
metadata: map['metadata'] != null
|
|
||||||
? jsonDecode(map['metadata'] as String) as Map<String, dynamic>
|
|
||||||
: null,
|
|
||||||
createdAt: map['created_at'] as String?,
|
|
||||||
updatedAt: map['updated_at'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
factory ProductVariantDto.fromDomain(ProductVariant variant) =>
|
|
||||||
ProductVariantDto(
|
|
||||||
id: variant.id,
|
|
||||||
productId: variant.productId,
|
|
||||||
name: variant.name,
|
|
||||||
priceModifier: variant.priceModifier,
|
|
||||||
cost: variant.cost,
|
|
||||||
metadata: variant.metadata,
|
|
||||||
createdAt: variant.createdAt,
|
|
||||||
updatedAt: variant.updatedAt,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
||||||
|
|
||||||
import '../../domain/product/product.dart';
|
|
||||||
|
|
||||||
part 'product_dtos.freezed.dart';
|
|
||||||
part 'product_dtos.g.dart';
|
|
||||||
|
|
||||||
part 'dtos/product_dto.dart';
|
|
||||||
File diff suppressed because it is too large
Load Diff
@ -1,96 +0,0 @@
|
|||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
|
||||||
|
|
||||||
part of 'product_dtos.dart';
|
|
||||||
|
|
||||||
// **************************************************************************
|
|
||||||
// JsonSerializableGenerator
|
|
||||||
// **************************************************************************
|
|
||||||
|
|
||||||
_$ListProductDtoImpl _$$ListProductDtoImplFromJson(Map<String, dynamic> json) =>
|
|
||||||
_$ListProductDtoImpl(
|
|
||||||
products: (json['products'] as List<dynamic>)
|
|
||||||
.map((e) => ProductDto.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList(),
|
|
||||||
totalCount: (json['total_count'] as num).toInt(),
|
|
||||||
page: (json['page'] as num).toInt(),
|
|
||||||
limit: (json['limit'] as num).toInt(),
|
|
||||||
totalPages: (json['total_pages'] as num).toInt(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$ListProductDtoImplToJson(
|
|
||||||
_$ListProductDtoImpl instance,
|
|
||||||
) => <String, dynamic>{
|
|
||||||
'products': instance.products,
|
|
||||||
'total_count': instance.totalCount,
|
|
||||||
'page': instance.page,
|
|
||||||
'limit': instance.limit,
|
|
||||||
'total_pages': instance.totalPages,
|
|
||||||
};
|
|
||||||
|
|
||||||
_$ProductDtoImpl _$$ProductDtoImplFromJson(Map<String, dynamic> json) =>
|
|
||||||
_$ProductDtoImpl(
|
|
||||||
id: json['id'] as String?,
|
|
||||||
organizationId: json['organization_id'] as String?,
|
|
||||||
categoryId: json['category_id'] as String?,
|
|
||||||
sku: json['sku'] as String?,
|
|
||||||
name: json['name'] as String?,
|
|
||||||
description: json['description'] as String?,
|
|
||||||
price: (json['price'] as num?)?.toDouble(),
|
|
||||||
cost: (json['cost'] as num?)?.toDouble(),
|
|
||||||
businessType: json['business_type'] as String?,
|
|
||||||
imageUrl: json['image_url'] as String?,
|
|
||||||
printerType: json['printer_type'] as String?,
|
|
||||||
metadata: json['metadata'] as Map<String, dynamic>?,
|
|
||||||
isActive: json['is_active'] as bool?,
|
|
||||||
createdAt: json['created_at'] as String?,
|
|
||||||
updatedAt: json['updated_at'] as String?,
|
|
||||||
variants: (json['variants'] as List<dynamic>?)
|
|
||||||
?.map((e) => ProductVariantDto.fromJson(e as Map<String, dynamic>))
|
|
||||||
.toList(),
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$ProductDtoImplToJson(_$ProductDtoImpl instance) =>
|
|
||||||
<String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'organization_id': instance.organizationId,
|
|
||||||
'category_id': instance.categoryId,
|
|
||||||
'sku': instance.sku,
|
|
||||||
'name': instance.name,
|
|
||||||
'description': instance.description,
|
|
||||||
'price': instance.price,
|
|
||||||
'cost': instance.cost,
|
|
||||||
'business_type': instance.businessType,
|
|
||||||
'image_url': instance.imageUrl,
|
|
||||||
'printer_type': instance.printerType,
|
|
||||||
'metadata': instance.metadata,
|
|
||||||
'is_active': instance.isActive,
|
|
||||||
'created_at': instance.createdAt,
|
|
||||||
'updated_at': instance.updatedAt,
|
|
||||||
'variants': instance.variants,
|
|
||||||
};
|
|
||||||
|
|
||||||
_$ProductVariantDtoImpl _$$ProductVariantDtoImplFromJson(
|
|
||||||
Map<String, dynamic> json,
|
|
||||||
) => _$ProductVariantDtoImpl(
|
|
||||||
id: json['id'] as String?,
|
|
||||||
productId: json['product_id'] as String?,
|
|
||||||
name: json['name'] as String?,
|
|
||||||
priceModifier: (json['price_modifier'] as num?)?.toDouble(),
|
|
||||||
cost: (json['cost'] as num?)?.toDouble(),
|
|
||||||
metadata: json['metadata'] as Map<String, dynamic>?,
|
|
||||||
createdAt: json['created_at'] as String?,
|
|
||||||
updatedAt: json['updated_at'] as String?,
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, dynamic> _$$ProductVariantDtoImplToJson(
|
|
||||||
_$ProductVariantDtoImpl instance,
|
|
||||||
) => <String, dynamic>{
|
|
||||||
'id': instance.id,
|
|
||||||
'product_id': instance.productId,
|
|
||||||
'name': instance.name,
|
|
||||||
'price_modifier': instance.priceModifier,
|
|
||||||
'cost': instance.cost,
|
|
||||||
'metadata': instance.metadata,
|
|
||||||
'created_at': instance.createdAt,
|
|
||||||
'updated_at': instance.updatedAt,
|
|
||||||
};
|
|
||||||
@ -1,350 +0,0 @@
|
|||||||
import 'dart:developer';
|
|
||||||
|
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
import 'package:injectable/injectable.dart';
|
|
||||||
|
|
||||||
import '../../../domain/product/product.dart';
|
|
||||||
import '../datasources/local_data_provider.dart';
|
|
||||||
import '../datasources/remote_data_provider.dart';
|
|
||||||
import '../product_dtos.dart';
|
|
||||||
|
|
||||||
@Injectable(as: IProductRepository)
|
|
||||||
class ProductRepository implements IProductRepository {
|
|
||||||
final ProductRemoteDataProvider _remoteDataProvider;
|
|
||||||
final ProductLocalDataProvider _localDataProvider;
|
|
||||||
|
|
||||||
final _logName = 'ProductRepository';
|
|
||||||
|
|
||||||
ProductRepository(this._remoteDataProvider, this._localDataProvider);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<ProductFailure, Product>> getProductById(String id) async {
|
|
||||||
try {
|
|
||||||
log('🔍 Getting product by ID from local: $id', name: _logName);
|
|
||||||
|
|
||||||
final product = await _localDataProvider.getProductById(id);
|
|
||||||
|
|
||||||
if (product.hasData) {
|
|
||||||
log('❌ Product not found: $id', name: _logName);
|
|
||||||
return Left(
|
|
||||||
ProductFailure.dynamicErrorMessage(
|
|
||||||
'Produk dengan ID $id tidak ditemukan',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final productDomain = product.data!.toDomain();
|
|
||||||
|
|
||||||
log('✅ Product loaded: ${productDomain.name}', name: _logName);
|
|
||||||
return Right(productDomain);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error getting product by ID',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
return Left(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<ProductFailure, ListProduct>> getProducts({
|
|
||||||
int page = 1,
|
|
||||||
int limit = 10,
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
bool forceRefresh = false,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
log(
|
|
||||||
'📦 Fetching products from local DB - page: $page, categoryId: $categoryId, search: $search',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 🧹 Bersihkan cache kedaluwarsa
|
|
||||||
_localDataProvider.clearExpiredCache();
|
|
||||||
|
|
||||||
// ⚡ Ambil data dari cache lokal
|
|
||||||
final cachedProducts = await _localDataProvider.getCachedProducts(
|
|
||||||
page: page,
|
|
||||||
limit: limit,
|
|
||||||
categoryId: categoryId,
|
|
||||||
search: search,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cachedProducts.hasError) {
|
|
||||||
return left(cachedProducts.error!);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 📊 Hitung total item (untuk pagination)
|
|
||||||
final totalCount = await _localDataProvider.getTotalCount(
|
|
||||||
categoryId: categoryId,
|
|
||||||
search: search,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 🧱 Bangun entity domain ListProduct
|
|
||||||
final result = ListProduct(
|
|
||||||
products: cachedProducts.data!.map((p) => p.toDomain()).toList(),
|
|
||||||
totalCount: totalCount,
|
|
||||||
page: page,
|
|
||||||
limit: limit,
|
|
||||||
totalPages: totalCount > 0 ? (totalCount / limit).ceil() : 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
log(
|
|
||||||
'✅ Returned ${cachedProducts.data!.length} local products ($totalCount total)',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Right(result);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error getting local products',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
return Left(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<ProductFailure, ListProduct>> refreshProducts({
|
|
||||||
String? categoryId,
|
|
||||||
String? search,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
log('🔄 Refreshing local products...', name: _logName);
|
|
||||||
|
|
||||||
// Bersihkan cache agar hasil baru diambil dari database
|
|
||||||
_localDataProvider.clearCache();
|
|
||||||
|
|
||||||
// Ambil ulang data produk dari lokal database
|
|
||||||
final cachedProducts = await _localDataProvider.getCachedProducts(
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
categoryId: categoryId,
|
|
||||||
search: search,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (cachedProducts.hasError) {
|
|
||||||
return left(cachedProducts.error!);
|
|
||||||
}
|
|
||||||
|
|
||||||
final products = cachedProducts.data!.map((p) => p.toDomain()).toList();
|
|
||||||
|
|
||||||
final totalCount = await _localDataProvider.getTotalCount(
|
|
||||||
categoryId: categoryId,
|
|
||||||
search: search,
|
|
||||||
);
|
|
||||||
|
|
||||||
// 🧱 Bangun entity domain ListProduct
|
|
||||||
final result = ListProduct(
|
|
||||||
products: products,
|
|
||||||
totalCount: totalCount,
|
|
||||||
page: 1,
|
|
||||||
limit: 10,
|
|
||||||
totalPages: totalCount > 0 ? (totalCount / 10).ceil() : 0,
|
|
||||||
);
|
|
||||||
|
|
||||||
log(
|
|
||||||
'✅ Refreshed ${cachedProducts.data!.length} local products',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Right(result);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error refreshing local products',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
return Left(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<ProductFailure, List<Product>>> searchProductsOptimized(
|
|
||||||
String query,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
log('🔍 Local optimized search for: "$query"', name: _logName);
|
|
||||||
|
|
||||||
// 🔎 Cari dari local database
|
|
||||||
final results = await _localDataProvider.searchProductsOptimized(query);
|
|
||||||
|
|
||||||
if (results.hasError) {
|
|
||||||
return left(results.error!);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ✅ Mapping ke domain entity (kalau hasilnya masih berupa DTO)
|
|
||||||
final products = results.data!.map((p) => p.toDomain()).toList();
|
|
||||||
|
|
||||||
log(
|
|
||||||
'✅ Local search completed: ${products.length} results',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
|
|
||||||
return Right(products);
|
|
||||||
} catch (e, s) {
|
|
||||||
log('❌ Error in local search', name: _logName, error: e, stackTrace: s);
|
|
||||||
return Left(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<ProductFailure, String>> syncAllProducts() async {
|
|
||||||
try {
|
|
||||||
log('🔄 Starting manual sync of all products...', name: _logName);
|
|
||||||
|
|
||||||
int page = 1;
|
|
||||||
const limit = 50;
|
|
||||||
bool hasMore = true;
|
|
||||||
int totalSynced = 0;
|
|
||||||
|
|
||||||
// Clear local DB before fresh sync
|
|
||||||
await _localDataProvider.clearAllProducts();
|
|
||||||
|
|
||||||
while (hasMore) {
|
|
||||||
log('📄 Syncing page $page...', name: _logName);
|
|
||||||
|
|
||||||
// NOTE: _remoteDatasource.getProducts() returns DC<..., ProductResponseModel>
|
|
||||||
final remoteResult = await _remoteDataProvider.fetchProducts(
|
|
||||||
page: page,
|
|
||||||
limit: limit,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Handle DC result manually (no fold)
|
|
||||||
if (!remoteResult.hasData) {
|
|
||||||
// remote returned an error/failure
|
|
||||||
final remoteFailure = remoteResult.error;
|
|
||||||
log('❌ Sync failed at page $page: $remoteFailure', name: _logName);
|
|
||||||
return Left(
|
|
||||||
ProductFailure.dynamicErrorMessage(remoteFailure.toString()),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = remoteResult.data!;
|
|
||||||
final products = response.products;
|
|
||||||
|
|
||||||
if (products.isNotEmpty) {
|
|
||||||
// Save page to local DB
|
|
||||||
await _localDataProvider.saveProductsBatch(
|
|
||||||
products,
|
|
||||||
clearFirst: false, // don't clear on subsequent pages
|
|
||||||
);
|
|
||||||
|
|
||||||
totalSynced += products.length;
|
|
||||||
|
|
||||||
// Determine if more pages exist
|
|
||||||
hasMore = page < (response.totalPages);
|
|
||||||
page++;
|
|
||||||
|
|
||||||
log(
|
|
||||||
'📦 Page ${page - 1} synced: ${products.length} products',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
hasMore = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final message = 'Berhasil sinkronisasi $totalSynced produk';
|
|
||||||
log('✅ $message', name: _logName);
|
|
||||||
return Right(message);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Gagal sinkronisasi produk',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
return Left(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void clearCache() {
|
|
||||||
log('🧹 Clearing local cache', name: _logName);
|
|
||||||
_localDataProvider.clearCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<ProductFailure, Map<String, dynamic>>>
|
|
||||||
getDatabaseStats() async {
|
|
||||||
try {
|
|
||||||
log('📊 Getting local database stats...', name: _logName);
|
|
||||||
|
|
||||||
final stats = await _localDataProvider.getDatabaseStats();
|
|
||||||
|
|
||||||
log('✅ Database stats loaded successfully: $stats', name: _logName);
|
|
||||||
|
|
||||||
return Right(stats.data!);
|
|
||||||
} catch (e, s) {
|
|
||||||
log(
|
|
||||||
'❌ Error getting database stats',
|
|
||||||
name: _logName,
|
|
||||||
error: e,
|
|
||||||
stackTrace: s,
|
|
||||||
);
|
|
||||||
return Left(ProductFailure.localStorageError(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> hasLocalProducts() async {
|
|
||||||
final hasProducts = await _localDataProvider.hasProducts();
|
|
||||||
log('📊 Has local products: $hasProducts', name: _logName);
|
|
||||||
return hasProducts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<bool> isLocalDatabaseReady() async {
|
|
||||||
try {
|
|
||||||
final stats = await _localDataProvider.getDatabaseStats();
|
|
||||||
final productCount = stats.data!['total_products'] ?? 0;
|
|
||||||
final isReady = productCount > 0;
|
|
||||||
log(
|
|
||||||
'🔍 Local database ready: $isReady ($productCount products)',
|
|
||||||
name: _logName,
|
|
||||||
);
|
|
||||||
return isReady;
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Error checking database readiness: $e', name: _logName);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> clearAllProducts() async {
|
|
||||||
try {
|
|
||||||
log('🗑️ Clearing all products from repository...', name: _logName);
|
|
||||||
await _localDataProvider.clearAllProducts();
|
|
||||||
clearCache();
|
|
||||||
log('✅ All products cleared successfully', name: _logName);
|
|
||||||
} catch (e) {
|
|
||||||
log('❌ Error clearing all products: $e', name: _logName);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<Either<ProductFailure, Unit>> saveProductsBatch(
|
|
||||||
List<Product> products,
|
|
||||||
) async {
|
|
||||||
try {
|
|
||||||
final productDtos = products.map(ProductDto.fromDomain).toList();
|
|
||||||
|
|
||||||
// Simpan batch ke local DB
|
|
||||||
await _localDataProvider.saveProductsBatch(productDtos);
|
|
||||||
|
|
||||||
log('💾 Saved ${products.length} products to local DB', name: _logName);
|
|
||||||
return right(unit);
|
|
||||||
} catch (e, stack) {
|
|
||||||
log('❌ Failed to save products batch: $e\n$stack', name: _logName);
|
|
||||||
return left(ProductFailure.dynamicErrorMessage(e.toString()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -14,9 +14,6 @@ import 'package:apskel_pos_flutter_v2/application/auth/login_form/login_form_blo
|
|||||||
as _i46;
|
as _i46;
|
||||||
import 'package:apskel_pos_flutter_v2/application/outlet/outlet_loader/outlet_loader_bloc.dart'
|
import 'package:apskel_pos_flutter_v2/application/outlet/outlet_loader/outlet_loader_bloc.dart'
|
||||||
as _i76;
|
as _i76;
|
||||||
import 'package:apskel_pos_flutter_v2/application/product/product_loader/product_loader_bloc.dart'
|
|
||||||
as _i13;
|
|
||||||
import 'package:apskel_pos_flutter_v2/application/sync/sync_bloc.dart' as _i741;
|
|
||||||
import 'package:apskel_pos_flutter_v2/common/api/api_client.dart' as _i457;
|
import 'package:apskel_pos_flutter_v2/common/api/api_client.dart' as _i457;
|
||||||
import 'package:apskel_pos_flutter_v2/common/database/database_helper.dart'
|
import 'package:apskel_pos_flutter_v2/common/database/database_helper.dart'
|
||||||
as _i487;
|
as _i487;
|
||||||
@ -31,7 +28,6 @@ import 'package:apskel_pos_flutter_v2/common/network/network_client.dart'
|
|||||||
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
|
import 'package:apskel_pos_flutter_v2/domain/auth/auth.dart' as _i776;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502;
|
import 'package:apskel_pos_flutter_v2/domain/category/category.dart' as _i502;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/outlet/outlet.dart' as _i552;
|
import 'package:apskel_pos_flutter_v2/domain/outlet/outlet.dart' as _i552;
|
||||||
import 'package:apskel_pos_flutter_v2/domain/product/product.dart' as _i44;
|
|
||||||
import 'package:apskel_pos_flutter_v2/env.dart' as _i923;
|
import 'package:apskel_pos_flutter_v2/env.dart' as _i923;
|
||||||
import 'package:apskel_pos_flutter_v2/infrastructure/auth/datasources/local_data_provider.dart'
|
import 'package:apskel_pos_flutter_v2/infrastructure/auth/datasources/local_data_provider.dart'
|
||||||
as _i204;
|
as _i204;
|
||||||
@ -51,12 +47,6 @@ import 'package:apskel_pos_flutter_v2/infrastructure/outlet/datasources/remote_d
|
|||||||
as _i132;
|
as _i132;
|
||||||
import 'package:apskel_pos_flutter_v2/infrastructure/outlet/repositories/outlet_repository.dart'
|
import 'package:apskel_pos_flutter_v2/infrastructure/outlet/repositories/outlet_repository.dart'
|
||||||
as _i845;
|
as _i845;
|
||||||
import 'package:apskel_pos_flutter_v2/infrastructure/product/datasources/local_data_provider.dart'
|
|
||||||
as _i464;
|
|
||||||
import 'package:apskel_pos_flutter_v2/infrastructure/product/datasources/remote_data_provider.dart'
|
|
||||||
as _i707;
|
|
||||||
import 'package:apskel_pos_flutter_v2/infrastructure/product/repositories/product_repository.dart'
|
|
||||||
as _i763;
|
|
||||||
import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart'
|
import 'package:apskel_pos_flutter_v2/presentation/router/app_router.dart'
|
||||||
as _i800;
|
as _i800;
|
||||||
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
|
import 'package:connectivity_plus/connectivity_plus.dart' as _i895;
|
||||||
@ -95,9 +85,6 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i708.CategoryLocalDataProvider>(
|
gh.factory<_i708.CategoryLocalDataProvider>(
|
||||||
() => _i708.CategoryLocalDataProvider(gh<_i487.DatabaseHelper>()),
|
() => _i708.CategoryLocalDataProvider(gh<_i487.DatabaseHelper>()),
|
||||||
);
|
);
|
||||||
gh.factory<_i464.ProductLocalDataProvider>(
|
|
||||||
() => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()),
|
|
||||||
);
|
|
||||||
gh.factory<_i204.AuthLocalDataProvider>(
|
gh.factory<_i204.AuthLocalDataProvider>(
|
||||||
() => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
|
() => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()),
|
||||||
);
|
);
|
||||||
@ -117,9 +104,6 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i132.OutletRemoteDataProvider>(
|
gh.factory<_i132.OutletRemoteDataProvider>(
|
||||||
() => _i132.OutletRemoteDataProvider(gh<_i457.ApiClient>()),
|
() => _i132.OutletRemoteDataProvider(gh<_i457.ApiClient>()),
|
||||||
);
|
);
|
||||||
gh.factory<_i707.ProductRemoteDataProvider>(
|
|
||||||
() => _i707.ProductRemoteDataProvider(gh<_i457.ApiClient>()),
|
|
||||||
);
|
|
||||||
gh.factory<_i776.IAuthRepository>(
|
gh.factory<_i776.IAuthRepository>(
|
||||||
() => _i941.AuthRepository(
|
() => _i941.AuthRepository(
|
||||||
gh<_i370.AuthRemoteDataProvider>(),
|
gh<_i370.AuthRemoteDataProvider>(),
|
||||||
@ -132,12 +116,6 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh<_i708.CategoryLocalDataProvider>(),
|
gh<_i708.CategoryLocalDataProvider>(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
gh.factory<_i44.IProductRepository>(
|
|
||||||
() => _i763.ProductRepository(
|
|
||||||
gh<_i707.ProductRemoteDataProvider>(),
|
|
||||||
gh<_i464.ProductLocalDataProvider>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
gh.factory<_i552.IOutletRepository>(
|
gh.factory<_i552.IOutletRepository>(
|
||||||
() => _i845.OutletRepository(
|
() => _i845.OutletRepository(
|
||||||
gh<_i132.OutletRemoteDataProvider>(),
|
gh<_i132.OutletRemoteDataProvider>(),
|
||||||
@ -156,15 +134,6 @@ extension GetItInjectableX on _i174.GetIt {
|
|||||||
gh.factory<_i76.OutletLoaderBloc>(
|
gh.factory<_i76.OutletLoaderBloc>(
|
||||||
() => _i76.OutletLoaderBloc(gh<_i552.IOutletRepository>()),
|
() => _i76.OutletLoaderBloc(gh<_i552.IOutletRepository>()),
|
||||||
);
|
);
|
||||||
gh.factory<_i13.ProductLoaderBloc>(
|
|
||||||
() => _i13.ProductLoaderBloc(gh<_i44.IProductRepository>()),
|
|
||||||
);
|
|
||||||
gh.factory<_i741.SyncBloc>(
|
|
||||||
() => _i741.SyncBloc(
|
|
||||||
gh<_i44.IProductRepository>(),
|
|
||||||
gh<_i502.ICategoryRepository>(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user