From 4fdd1e44f8472a6f700976be5dd8c7de4c08e381 Mon Sep 17 00:00:00 2001 From: efrilm Date: Fri, 24 Oct 2025 22:03:35 +0700 Subject: [PATCH] product repo --- .../product_loader/product_loader_bloc.dart | 345 +++ .../product_loader_bloc.freezed.dart | 1379 ++++++++++++ .../product_loader/product_loader_event.dart | 26 + .../product_loader/product_loader_state.dart | 17 + lib/common/url/api_path.dart | 1 + .../product/entities/product_entity.dart | 86 + .../product/failures/product_failure.dart | 12 + lib/domain/product/product.dart | 11 + lib/domain/product/product.freezed.dart | 1964 +++++++++++++++++ .../repositories/i_product_repository.dart | 34 + .../datasources/local_data_provider.dart | 486 ++++ .../datasources/remote_data_provider.dart | 57 + .../product/dtos/product_dto.dart | 173 ++ lib/infrastructure/product/product_dtos.dart | 10 + .../product/product_dtos.freezed.dart | 1204 ++++++++++ .../product/product_dtos.g.dart | 96 + .../repositories/product_repository.dart | 331 +++ lib/injection.config.dart | 24 + 18 files changed, 6256 insertions(+) create mode 100644 lib/application/product/product_loader/product_loader_bloc.dart create mode 100644 lib/application/product/product_loader/product_loader_bloc.freezed.dart create mode 100644 lib/application/product/product_loader/product_loader_event.dart create mode 100644 lib/application/product/product_loader/product_loader_state.dart create mode 100644 lib/domain/product/entities/product_entity.dart create mode 100644 lib/domain/product/failures/product_failure.dart create mode 100644 lib/domain/product/product.dart create mode 100644 lib/domain/product/product.freezed.dart create mode 100644 lib/domain/product/repositories/i_product_repository.dart create mode 100644 lib/infrastructure/product/datasources/local_data_provider.dart create mode 100644 lib/infrastructure/product/datasources/remote_data_provider.dart create mode 100644 lib/infrastructure/product/dtos/product_dto.dart create mode 100644 lib/infrastructure/product/product_dtos.dart create mode 100644 lib/infrastructure/product/product_dtos.freezed.dart create mode 100644 lib/infrastructure/product/product_dtos.g.dart create mode 100644 lib/infrastructure/product/repositories/product_repository.dart diff --git a/lib/application/product/product_loader/product_loader_bloc.dart b/lib/application/product/product_loader/product_loader_bloc.dart new file mode 100644 index 0000000..b46eb92 --- /dev/null +++ b/lib/application/product/product_loader/product_loader_bloc.dart @@ -0,0 +1,345 @@ +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 { + final IProductRepository _productRepository; + + Timer? _loadMoreDebounce; + Timer? _searchDebounce; + + ProductLoaderBloc(this._productRepository) + : super(ProductLoaderState.initial()) { + on(_onProductLoaderEvent); + } + + Future _onProductLoaderEvent( + ProductLoaderEvent event, + Emitter 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()); + }, + ); + } +} diff --git a/lib/application/product/product_loader/product_loader_bloc.freezed.dart b/lib/application/product/product_loader/product_loader_bloc.freezed.dart new file mode 100644 index 0000000..37fc465 --- /dev/null +++ b/lib/application/product/product_loader/product_loader_bloc.freezed.dart @@ -0,0 +1,1379 @@ +// 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 'product_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$ProductLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function( + String? categoryId, + String? search, + bool? forceRefresh, + ) + getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + required TResult Function(String? query, String? categoryId) searchProduct, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + TResult? Function(String? query, String? categoryId)? searchProduct, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + TResult Function(String? query, String? categoryId)? searchProduct, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_SearchProduct value) searchProduct, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_SearchProduct value)? searchProduct, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_SearchProduct value)? searchProduct, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductLoaderEventCopyWith<$Res> { + factory $ProductLoaderEventCopyWith( + ProductLoaderEvent value, + $Res Function(ProductLoaderEvent) then, + ) = _$ProductLoaderEventCopyWithImpl<$Res, ProductLoaderEvent>; +} + +/// @nodoc +class _$ProductLoaderEventCopyWithImpl<$Res, $Val extends ProductLoaderEvent> + implements $ProductLoaderEventCopyWith<$Res> { + _$ProductLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetProductImplCopyWith<$Res> { + factory _$$GetProductImplCopyWith( + _$GetProductImpl value, + $Res Function(_$GetProductImpl) then, + ) = __$$GetProductImplCopyWithImpl<$Res>; + @useResult + $Res call({String? categoryId, String? search, bool? forceRefresh}); +} + +/// @nodoc +class __$$GetProductImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$GetProductImpl> + implements _$$GetProductImplCopyWith<$Res> { + __$$GetProductImplCopyWithImpl( + _$GetProductImpl _value, + $Res Function(_$GetProductImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categoryId = freezed, + Object? search = freezed, + Object? forceRefresh = freezed, + }) { + return _then( + _$GetProductImpl( + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + forceRefresh: freezed == forceRefresh + ? _value.forceRefresh + : forceRefresh // ignore: cast_nullable_to_non_nullable + as bool?, + ), + ); + } +} + +/// @nodoc + +class _$GetProductImpl implements _GetProduct { + const _$GetProductImpl({this.categoryId, this.search, this.forceRefresh}); + + @override + final String? categoryId; + @override + final String? search; + // Added search parameter + @override + final bool? forceRefresh; + + @override + String toString() { + return 'ProductLoaderEvent.getProduct(categoryId: $categoryId, search: $search, forceRefresh: $forceRefresh)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GetProductImpl && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.search, search) || other.search == search) && + (identical(other.forceRefresh, forceRefresh) || + other.forceRefresh == forceRefresh)); + } + + @override + int get hashCode => + Object.hash(runtimeType, categoryId, search, forceRefresh); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GetProductImplCopyWith<_$GetProductImpl> get copyWith => + __$$GetProductImplCopyWithImpl<_$GetProductImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String? categoryId, + String? search, + bool? forceRefresh, + ) + getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + required TResult Function(String? query, String? categoryId) searchProduct, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return getProduct(categoryId, search, forceRefresh); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + TResult? Function(String? query, String? categoryId)? searchProduct, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return getProduct?.call(categoryId, search, forceRefresh); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + TResult Function(String? query, String? categoryId)? searchProduct, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (getProduct != null) { + return getProduct(categoryId, search, forceRefresh); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_SearchProduct value) searchProduct, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return getProduct(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_SearchProduct value)? searchProduct, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return getProduct?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_SearchProduct value)? searchProduct, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (getProduct != null) { + return getProduct(this); + } + return orElse(); + } +} + +abstract class _GetProduct implements ProductLoaderEvent { + const factory _GetProduct({ + final String? categoryId, + final String? search, + final bool? forceRefresh, + }) = _$GetProductImpl; + + String? get categoryId; + String? get search; // Added search parameter + bool? get forceRefresh; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GetProductImplCopyWith<_$GetProductImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$LoadMoreImplCopyWith<$Res> { + factory _$$LoadMoreImplCopyWith( + _$LoadMoreImpl value, + $Res Function(_$LoadMoreImpl) then, + ) = __$$LoadMoreImplCopyWithImpl<$Res>; + @useResult + $Res call({String? categoryId, String? search}); +} + +/// @nodoc +class __$$LoadMoreImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$LoadMoreImpl> + implements _$$LoadMoreImplCopyWith<$Res> { + __$$LoadMoreImplCopyWithImpl( + _$LoadMoreImpl _value, + $Res Function(_$LoadMoreImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? categoryId = freezed, Object? search = freezed}) { + return _then( + _$LoadMoreImpl( + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$LoadMoreImpl implements _LoadMore { + const _$LoadMoreImpl({this.categoryId, this.search}); + + @override + final String? categoryId; + @override + final String? search; + + @override + String toString() { + return 'ProductLoaderEvent.loadMore(categoryId: $categoryId, search: $search)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadMoreImpl && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.search, search) || other.search == search)); + } + + @override + int get hashCode => Object.hash(runtimeType, categoryId, search); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + __$$LoadMoreImplCopyWithImpl<_$LoadMoreImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String? categoryId, + String? search, + bool? forceRefresh, + ) + getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + required TResult Function(String? query, String? categoryId) searchProduct, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return loadMore(categoryId, search); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + TResult? Function(String? query, String? categoryId)? searchProduct, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return loadMore?.call(categoryId, search); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + TResult Function(String? query, String? categoryId)? searchProduct, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(categoryId, search); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_SearchProduct value) searchProduct, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return loadMore(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_SearchProduct value)? searchProduct, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return loadMore?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_SearchProduct value)? searchProduct, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(this); + } + return orElse(); + } +} + +abstract class _LoadMore implements ProductLoaderEvent { + const factory _LoadMore({final String? categoryId, final String? search}) = + _$LoadMoreImpl; + + String? get categoryId; + String? get search; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$RefreshImplCopyWith<$Res> { + factory _$$RefreshImplCopyWith( + _$RefreshImpl value, + $Res Function(_$RefreshImpl) then, + ) = __$$RefreshImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RefreshImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$RefreshImpl> + implements _$$RefreshImplCopyWith<$Res> { + __$$RefreshImplCopyWithImpl( + _$RefreshImpl _value, + $Res Function(_$RefreshImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RefreshImpl implements _Refresh { + const _$RefreshImpl(); + + @override + String toString() { + return 'ProductLoaderEvent.refresh()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$RefreshImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String? categoryId, + String? search, + bool? forceRefresh, + ) + getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + required TResult Function(String? query, String? categoryId) searchProduct, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return refresh(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + TResult? Function(String? query, String? categoryId)? searchProduct, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return refresh?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + TResult Function(String? query, String? categoryId)? searchProduct, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_SearchProduct value) searchProduct, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return refresh(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_SearchProduct value)? searchProduct, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return refresh?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_SearchProduct value)? searchProduct, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(this); + } + return orElse(); + } +} + +abstract class _Refresh implements ProductLoaderEvent { + const factory _Refresh() = _$RefreshImpl; +} + +/// @nodoc +abstract class _$$SearchProductImplCopyWith<$Res> { + factory _$$SearchProductImplCopyWith( + _$SearchProductImpl value, + $Res Function(_$SearchProductImpl) then, + ) = __$$SearchProductImplCopyWithImpl<$Res>; + @useResult + $Res call({String? query, String? categoryId}); +} + +/// @nodoc +class __$$SearchProductImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$SearchProductImpl> + implements _$$SearchProductImplCopyWith<$Res> { + __$$SearchProductImplCopyWithImpl( + _$SearchProductImpl _value, + $Res Function(_$SearchProductImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? query = freezed, Object? categoryId = freezed}) { + return _then( + _$SearchProductImpl( + query: freezed == query + ? _value.query + : query // ignore: cast_nullable_to_non_nullable + as String?, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$SearchProductImpl implements _SearchProduct { + const _$SearchProductImpl({this.query, this.categoryId}); + + @override + final String? query; + @override + final String? categoryId; + + @override + String toString() { + return 'ProductLoaderEvent.searchProduct(query: $query, categoryId: $categoryId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SearchProductImpl && + (identical(other.query, query) || other.query == query) && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId)); + } + + @override + int get hashCode => Object.hash(runtimeType, query, categoryId); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SearchProductImplCopyWith<_$SearchProductImpl> get copyWith => + __$$SearchProductImplCopyWithImpl<_$SearchProductImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String? categoryId, + String? search, + bool? forceRefresh, + ) + getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + required TResult Function(String? query, String? categoryId) searchProduct, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return searchProduct(query, categoryId); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + TResult? Function(String? query, String? categoryId)? searchProduct, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return searchProduct?.call(query, categoryId); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + TResult Function(String? query, String? categoryId)? searchProduct, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (searchProduct != null) { + return searchProduct(query, categoryId); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_SearchProduct value) searchProduct, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return searchProduct(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_SearchProduct value)? searchProduct, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return searchProduct?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_SearchProduct value)? searchProduct, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (searchProduct != null) { + return searchProduct(this); + } + return orElse(); + } +} + +abstract class _SearchProduct implements ProductLoaderEvent { + const factory _SearchProduct({ + final String? query, + final String? categoryId, + }) = _$SearchProductImpl; + + String? get query; + String? get categoryId; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SearchProductImplCopyWith<_$SearchProductImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$GetDatabaseStatsImplCopyWith<$Res> { + factory _$$GetDatabaseStatsImplCopyWith( + _$GetDatabaseStatsImpl value, + $Res Function(_$GetDatabaseStatsImpl) then, + ) = __$$GetDatabaseStatsImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetDatabaseStatsImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$GetDatabaseStatsImpl> + implements _$$GetDatabaseStatsImplCopyWith<$Res> { + __$$GetDatabaseStatsImplCopyWithImpl( + _$GetDatabaseStatsImpl _value, + $Res Function(_$GetDatabaseStatsImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetDatabaseStatsImpl implements _GetDatabaseStats { + const _$GetDatabaseStatsImpl(); + + @override + String toString() { + return 'ProductLoaderEvent.getDatabaseStats()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetDatabaseStatsImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String? categoryId, + String? search, + bool? forceRefresh, + ) + getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + required TResult Function(String? query, String? categoryId) searchProduct, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return getDatabaseStats(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + TResult? Function(String? query, String? categoryId)? searchProduct, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return getDatabaseStats?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + TResult Function(String? query, String? categoryId)? searchProduct, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (getDatabaseStats != null) { + return getDatabaseStats(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_SearchProduct value) searchProduct, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return getDatabaseStats(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_SearchProduct value)? searchProduct, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return getDatabaseStats?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_SearchProduct value)? searchProduct, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (getDatabaseStats != null) { + return getDatabaseStats(this); + } + return orElse(); + } +} + +abstract class _GetDatabaseStats implements ProductLoaderEvent { + const factory _GetDatabaseStats() = _$GetDatabaseStatsImpl; +} + +/// @nodoc +abstract class _$$ClearCacheImplCopyWith<$Res> { + factory _$$ClearCacheImplCopyWith( + _$ClearCacheImpl value, + $Res Function(_$ClearCacheImpl) then, + ) = __$$ClearCacheImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ClearCacheImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$ClearCacheImpl> + implements _$$ClearCacheImplCopyWith<$Res> { + __$$ClearCacheImplCopyWithImpl( + _$ClearCacheImpl _value, + $Res Function(_$ClearCacheImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$ClearCacheImpl implements _ClearCache { + const _$ClearCacheImpl(); + + @override + String toString() { + return 'ProductLoaderEvent.clearCache()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$ClearCacheImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String? categoryId, + String? search, + bool? forceRefresh, + ) + getProduct, + required TResult Function(String? categoryId, String? search) loadMore, + required TResult Function() refresh, + required TResult Function(String? query, String? categoryId) searchProduct, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return clearCache(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult? Function(String? categoryId, String? search)? loadMore, + TResult? Function()? refresh, + TResult? Function(String? query, String? categoryId)? searchProduct, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return clearCache?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String? categoryId, String? search, bool? forceRefresh)? + getProduct, + TResult Function(String? categoryId, String? search)? loadMore, + TResult Function()? refresh, + TResult Function(String? query, String? categoryId)? searchProduct, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (clearCache != null) { + return clearCache(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetProduct value) getProduct, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_SearchProduct value) searchProduct, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return clearCache(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetProduct value)? getProduct, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_SearchProduct value)? searchProduct, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return clearCache?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetProduct value)? getProduct, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_SearchProduct value)? searchProduct, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (clearCache != null) { + return clearCache(this); + } + return orElse(); + } +} + +abstract class _ClearCache implements ProductLoaderEvent { + const factory _ClearCache() = _$ClearCacheImpl; +} + +/// @nodoc +mixin _$ProductLoaderState { + List get products => throw _privateConstructorUsedError; + Option get failureOptionProduct => + throw _privateConstructorUsedError; + bool get hasReachedMax => throw _privateConstructorUsedError; + int get page => throw _privateConstructorUsedError; + bool get isLoadingMore => throw _privateConstructorUsedError; + String? get searchQuery => throw _privateConstructorUsedError; + String? get categoryId => throw _privateConstructorUsedError; + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductLoaderStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductLoaderStateCopyWith<$Res> { + factory $ProductLoaderStateCopyWith( + ProductLoaderState value, + $Res Function(ProductLoaderState) then, + ) = _$ProductLoaderStateCopyWithImpl<$Res, ProductLoaderState>; + @useResult + $Res call({ + List products, + Option failureOptionProduct, + bool hasReachedMax, + int page, + bool isLoadingMore, + String? searchQuery, + String? categoryId, + }); +} + +/// @nodoc +class _$ProductLoaderStateCopyWithImpl<$Res, $Val extends ProductLoaderState> + implements $ProductLoaderStateCopyWith<$Res> { + _$ProductLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? failureOptionProduct = null, + Object? hasReachedMax = null, + Object? page = null, + Object? isLoadingMore = null, + Object? searchQuery = freezed, + Object? categoryId = freezed, + }) { + return _then( + _value.copyWith( + products: null == products + ? _value.products + : products // ignore: cast_nullable_to_non_nullable + as List, + failureOptionProduct: null == failureOptionProduct + ? _value.failureOptionProduct + : failureOptionProduct // ignore: cast_nullable_to_non_nullable + as Option, + hasReachedMax: null == hasReachedMax + ? _value.hasReachedMax + : hasReachedMax // ignore: cast_nullable_to_non_nullable + as bool, + page: null == page + ? _value.page + : page // ignore: cast_nullable_to_non_nullable + as int, + isLoadingMore: null == isLoadingMore + ? _value.isLoadingMore + : isLoadingMore // ignore: cast_nullable_to_non_nullable + as bool, + searchQuery: freezed == searchQuery + ? _value.searchQuery + : searchQuery // ignore: cast_nullable_to_non_nullable + as String?, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductLoaderStateImplCopyWith<$Res> + implements $ProductLoaderStateCopyWith<$Res> { + factory _$$ProductLoaderStateImplCopyWith( + _$ProductLoaderStateImpl value, + $Res Function(_$ProductLoaderStateImpl) then, + ) = __$$ProductLoaderStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List products, + Option failureOptionProduct, + bool hasReachedMax, + int page, + bool isLoadingMore, + String? searchQuery, + String? categoryId, + }); +} + +/// @nodoc +class __$$ProductLoaderStateImplCopyWithImpl<$Res> + extends _$ProductLoaderStateCopyWithImpl<$Res, _$ProductLoaderStateImpl> + implements _$$ProductLoaderStateImplCopyWith<$Res> { + __$$ProductLoaderStateImplCopyWithImpl( + _$ProductLoaderStateImpl _value, + $Res Function(_$ProductLoaderStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? failureOptionProduct = null, + Object? hasReachedMax = null, + Object? page = null, + Object? isLoadingMore = null, + Object? searchQuery = freezed, + Object? categoryId = freezed, + }) { + return _then( + _$ProductLoaderStateImpl( + products: null == products + ? _value._products + : products // ignore: cast_nullable_to_non_nullable + as List, + failureOptionProduct: null == failureOptionProduct + ? _value.failureOptionProduct + : failureOptionProduct // ignore: cast_nullable_to_non_nullable + as Option, + hasReachedMax: null == hasReachedMax + ? _value.hasReachedMax + : hasReachedMax // ignore: cast_nullable_to_non_nullable + as bool, + page: null == page + ? _value.page + : page // ignore: cast_nullable_to_non_nullable + as int, + isLoadingMore: null == isLoadingMore + ? _value.isLoadingMore + : isLoadingMore // ignore: cast_nullable_to_non_nullable + as bool, + searchQuery: freezed == searchQuery + ? _value.searchQuery + : searchQuery // ignore: cast_nullable_to_non_nullable + as String?, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc + +class _$ProductLoaderStateImpl implements _ProductLoaderState { + _$ProductLoaderStateImpl({ + required final List products, + required this.failureOptionProduct, + this.hasReachedMax = false, + this.page = 1, + this.isLoadingMore = false, + this.searchQuery, + this.categoryId, + }) : _products = products; + + final List _products; + @override + List get products { + if (_products is EqualUnmodifiableListView) return _products; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_products); + } + + @override + final Option failureOptionProduct; + @override + @JsonKey() + final bool hasReachedMax; + @override + @JsonKey() + final int page; + @override + @JsonKey() + final bool isLoadingMore; + @override + final String? searchQuery; + @override + final String? categoryId; + + @override + String toString() { + return 'ProductLoaderState(products: $products, failureOptionProduct: $failureOptionProduct, hasReachedMax: $hasReachedMax, page: $page, isLoadingMore: $isLoadingMore, searchQuery: $searchQuery, categoryId: $categoryId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductLoaderStateImpl && + const DeepCollectionEquality().equals(other._products, _products) && + (identical(other.failureOptionProduct, failureOptionProduct) || + other.failureOptionProduct == failureOptionProduct) && + (identical(other.hasReachedMax, hasReachedMax) || + other.hasReachedMax == hasReachedMax) && + (identical(other.page, page) || other.page == page) && + (identical(other.isLoadingMore, isLoadingMore) || + other.isLoadingMore == isLoadingMore) && + (identical(other.searchQuery, searchQuery) || + other.searchQuery == searchQuery) && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_products), + failureOptionProduct, + hasReachedMax, + page, + isLoadingMore, + searchQuery, + categoryId, + ); + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductLoaderStateImplCopyWith<_$ProductLoaderStateImpl> get copyWith => + __$$ProductLoaderStateImplCopyWithImpl<_$ProductLoaderStateImpl>( + this, + _$identity, + ); +} + +abstract class _ProductLoaderState implements ProductLoaderState { + factory _ProductLoaderState({ + required final List products, + required final Option failureOptionProduct, + final bool hasReachedMax, + final int page, + final bool isLoadingMore, + final String? searchQuery, + final String? categoryId, + }) = _$ProductLoaderStateImpl; + + @override + List get products; + @override + Option get failureOptionProduct; + @override + bool get hasReachedMax; + @override + int get page; + @override + bool get isLoadingMore; + @override + String? get searchQuery; + @override + String? get categoryId; + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductLoaderStateImplCopyWith<_$ProductLoaderStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/product/product_loader/product_loader_event.dart b/lib/application/product/product_loader/product_loader_event.dart new file mode 100644 index 0000000..9545eb1 --- /dev/null +++ b/lib/application/product/product_loader/product_loader_event.dart @@ -0,0 +1,26 @@ +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; +} diff --git a/lib/application/product/product_loader/product_loader_state.dart b/lib/application/product/product_loader/product_loader_state.dart new file mode 100644 index 0000000..31fc802 --- /dev/null +++ b/lib/application/product/product_loader/product_loader_state.dart @@ -0,0 +1,17 @@ +part of 'product_loader_bloc.dart'; + +@freezed +class ProductLoaderState with _$ProductLoaderState { + factory ProductLoaderState({ + required List products, + required Option 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()); +} diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart index cbba024..c4357a8 100644 --- a/lib/common/url/api_path.dart +++ b/lib/common/url/api_path.dart @@ -2,4 +2,5 @@ class ApiPath { static const String login = '/api/v1/auth/login'; static const String outlets = '/api/v1/outlets'; static const String categories = '/api/v1/categories'; + static const String products = '/api/v1/products'; } diff --git a/lib/domain/product/entities/product_entity.dart b/lib/domain/product/entities/product_entity.dart new file mode 100644 index 0000000..3869c73 --- /dev/null +++ b/lib/domain/product/entities/product_entity.dart @@ -0,0 +1,86 @@ +part of '../product.dart'; + +@freezed +class ListProduct with _$ListProduct { + const factory ListProduct({ + required List 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 metadata, + required bool isActive, + required String createdAt, + required String updatedAt, + required List 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 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: '', + ); +} diff --git a/lib/domain/product/failures/product_failure.dart b/lib/domain/product/failures/product_failure.dart new file mode 100644 index 0000000..8996a8b --- /dev/null +++ b/lib/domain/product/failures/product_failure.dart @@ -0,0 +1,12 @@ +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; +} diff --git a/lib/domain/product/product.dart b/lib/domain/product/product.dart new file mode 100644 index 0000000..54dfec3 --- /dev/null +++ b/lib/domain/product/product.dart @@ -0,0 +1,11 @@ +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'; diff --git a/lib/domain/product/product.freezed.dart b/lib/domain/product/product.freezed.dart new file mode 100644 index 0000000..850a898 --- /dev/null +++ b/lib/domain/product/product.freezed.dart @@ -0,0 +1,1964 @@ +// 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 'product.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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 _$ListProduct { + List get products => throw _privateConstructorUsedError; + int get totalCount => throw _privateConstructorUsedError; + int get page => throw _privateConstructorUsedError; + int get limit => throw _privateConstructorUsedError; + int get totalPages => throw _privateConstructorUsedError; + + /// Create a copy of ListProduct + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ListProductCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ListProductCopyWith<$Res> { + factory $ListProductCopyWith( + ListProduct value, + $Res Function(ListProduct) then, + ) = _$ListProductCopyWithImpl<$Res, ListProduct>; + @useResult + $Res call({ + List products, + int totalCount, + int page, + int limit, + int totalPages, + }); +} + +/// @nodoc +class _$ListProductCopyWithImpl<$Res, $Val extends ListProduct> + implements $ListProductCopyWith<$Res> { + _$ListProductCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ListProduct + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? totalCount = null, + Object? page = null, + Object? limit = null, + Object? totalPages = null, + }) { + return _then( + _value.copyWith( + products: null == products + ? _value.products + : products // ignore: cast_nullable_to_non_nullable + as List, + totalCount: null == totalCount + ? _value.totalCount + : totalCount // ignore: cast_nullable_to_non_nullable + as int, + page: null == page + ? _value.page + : page // ignore: cast_nullable_to_non_nullable + as int, + limit: null == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as int, + totalPages: null == totalPages + ? _value.totalPages + : totalPages // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ListProductImplCopyWith<$Res> + implements $ListProductCopyWith<$Res> { + factory _$$ListProductImplCopyWith( + _$ListProductImpl value, + $Res Function(_$ListProductImpl) then, + ) = __$$ListProductImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List products, + int totalCount, + int page, + int limit, + int totalPages, + }); +} + +/// @nodoc +class __$$ListProductImplCopyWithImpl<$Res> + extends _$ListProductCopyWithImpl<$Res, _$ListProductImpl> + implements _$$ListProductImplCopyWith<$Res> { + __$$ListProductImplCopyWithImpl( + _$ListProductImpl _value, + $Res Function(_$ListProductImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ListProduct + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? totalCount = null, + Object? page = null, + Object? limit = null, + Object? totalPages = null, + }) { + return _then( + _$ListProductImpl( + products: null == products + ? _value._products + : products // ignore: cast_nullable_to_non_nullable + as List, + totalCount: null == totalCount + ? _value.totalCount + : totalCount // ignore: cast_nullable_to_non_nullable + as int, + page: null == page + ? _value.page + : page // ignore: cast_nullable_to_non_nullable + as int, + limit: null == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as int, + totalPages: null == totalPages + ? _value.totalPages + : totalPages // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc + +class _$ListProductImpl with DiagnosticableTreeMixin implements _ListProduct { + const _$ListProductImpl({ + required final List products, + required this.totalCount, + required this.page, + required this.limit, + required this.totalPages, + }) : _products = products; + + final List _products; + @override + List get products { + if (_products is EqualUnmodifiableListView) return _products; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_products); + } + + @override + final int totalCount; + @override + final int page; + @override + final int limit; + @override + final int totalPages; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'ListProduct(products: $products, totalCount: $totalCount, page: $page, limit: $limit, totalPages: $totalPages)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'ListProduct')) + ..add(DiagnosticsProperty('products', products)) + ..add(DiagnosticsProperty('totalCount', totalCount)) + ..add(DiagnosticsProperty('page', page)) + ..add(DiagnosticsProperty('limit', limit)) + ..add(DiagnosticsProperty('totalPages', totalPages)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ListProductImpl && + const DeepCollectionEquality().equals(other._products, _products) && + (identical(other.totalCount, totalCount) || + other.totalCount == totalCount) && + (identical(other.page, page) || other.page == page) && + (identical(other.limit, limit) || other.limit == limit) && + (identical(other.totalPages, totalPages) || + other.totalPages == totalPages)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_products), + totalCount, + page, + limit, + totalPages, + ); + + /// Create a copy of ListProduct + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ListProductImplCopyWith<_$ListProductImpl> get copyWith => + __$$ListProductImplCopyWithImpl<_$ListProductImpl>(this, _$identity); +} + +abstract class _ListProduct implements ListProduct { + const factory _ListProduct({ + required final List products, + required final int totalCount, + required final int page, + required final int limit, + required final int totalPages, + }) = _$ListProductImpl; + + @override + List get products; + @override + int get totalCount; + @override + int get page; + @override + int get limit; + @override + int get totalPages; + + /// Create a copy of ListProduct + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ListProductImplCopyWith<_$ListProductImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$Product { + String get id => throw _privateConstructorUsedError; + String get organizationId => throw _privateConstructorUsedError; + String get categoryId => throw _privateConstructorUsedError; + String get sku => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + double get price => throw _privateConstructorUsedError; + double get cost => throw _privateConstructorUsedError; + String get businessType => throw _privateConstructorUsedError; + String get imageUrl => throw _privateConstructorUsedError; + String get printerType => throw _privateConstructorUsedError; + Map get metadata => throw _privateConstructorUsedError; + bool get isActive => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + String get updatedAt => throw _privateConstructorUsedError; + List get variants => throw _privateConstructorUsedError; + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductCopyWith<$Res> { + factory $ProductCopyWith(Product value, $Res Function(Product) then) = + _$ProductCopyWithImpl<$Res, Product>; + @useResult + $Res call({ + String id, + String organizationId, + String categoryId, + String sku, + String name, + String description, + double price, + double cost, + String businessType, + String imageUrl, + String printerType, + Map metadata, + bool isActive, + String createdAt, + String updatedAt, + List variants, + }); +} + +/// @nodoc +class _$ProductCopyWithImpl<$Res, $Val extends Product> + implements $ProductCopyWith<$Res> { + _$ProductCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? organizationId = null, + Object? categoryId = null, + Object? sku = null, + Object? name = null, + Object? description = null, + Object? price = null, + Object? cost = null, + Object? businessType = null, + Object? imageUrl = null, + Object? printerType = null, + Object? metadata = null, + Object? isActive = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? variants = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + organizationId: null == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String, + categoryId: null == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String, + sku: null == sku + ? _value.sku + : sku // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + cost: null == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as double, + businessType: null == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + printerType: null == printerType + ? _value.printerType + : printerType // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + variants: null == variants + ? _value.variants + : variants // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductImplCopyWith<$Res> implements $ProductCopyWith<$Res> { + factory _$$ProductImplCopyWith( + _$ProductImpl value, + $Res Function(_$ProductImpl) then, + ) = __$$ProductImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String organizationId, + String categoryId, + String sku, + String name, + String description, + double price, + double cost, + String businessType, + String imageUrl, + String printerType, + Map metadata, + bool isActive, + String createdAt, + String updatedAt, + List variants, + }); +} + +/// @nodoc +class __$$ProductImplCopyWithImpl<$Res> + extends _$ProductCopyWithImpl<$Res, _$ProductImpl> + implements _$$ProductImplCopyWith<$Res> { + __$$ProductImplCopyWithImpl( + _$ProductImpl _value, + $Res Function(_$ProductImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? organizationId = null, + Object? categoryId = null, + Object? sku = null, + Object? name = null, + Object? description = null, + Object? price = null, + Object? cost = null, + Object? businessType = null, + Object? imageUrl = null, + Object? printerType = null, + Object? metadata = null, + Object? isActive = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? variants = null, + }) { + return _then( + _$ProductImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + organizationId: null == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String, + categoryId: null == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String, + sku: null == sku + ? _value.sku + : sku // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double, + cost: null == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as double, + businessType: null == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + printerType: null == printerType + ? _value.printerType + : printerType // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + variants: null == variants + ? _value._variants + : variants // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$ProductImpl with DiagnosticableTreeMixin implements _Product { + const _$ProductImpl({ + required this.id, + required this.organizationId, + required this.categoryId, + required this.sku, + required this.name, + required this.description, + required this.price, + required this.cost, + required this.businessType, + required this.imageUrl, + required this.printerType, + required final Map metadata, + required this.isActive, + required this.createdAt, + required this.updatedAt, + required final List variants, + }) : _metadata = metadata, + _variants = variants; + + @override + final String id; + @override + final String organizationId; + @override + final String categoryId; + @override + final String sku; + @override + final String name; + @override + final String description; + @override + final double price; + @override + final double cost; + @override + final String businessType; + @override + final String imageUrl; + @override + final String printerType; + final Map _metadata; + @override + Map get metadata { + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_metadata); + } + + @override + final bool isActive; + @override + final String createdAt; + @override + final String updatedAt; + final List _variants; + @override + List get variants { + if (_variants is EqualUnmodifiableListView) return _variants; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_variants); + } + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'Product(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'Product')) + ..add(DiagnosticsProperty('id', id)) + ..add(DiagnosticsProperty('organizationId', organizationId)) + ..add(DiagnosticsProperty('categoryId', categoryId)) + ..add(DiagnosticsProperty('sku', sku)) + ..add(DiagnosticsProperty('name', name)) + ..add(DiagnosticsProperty('description', description)) + ..add(DiagnosticsProperty('price', price)) + ..add(DiagnosticsProperty('cost', cost)) + ..add(DiagnosticsProperty('businessType', businessType)) + ..add(DiagnosticsProperty('imageUrl', imageUrl)) + ..add(DiagnosticsProperty('printerType', printerType)) + ..add(DiagnosticsProperty('metadata', metadata)) + ..add(DiagnosticsProperty('isActive', isActive)) + ..add(DiagnosticsProperty('createdAt', createdAt)) + ..add(DiagnosticsProperty('updatedAt', updatedAt)) + ..add(DiagnosticsProperty('variants', variants)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.organizationId, organizationId) || + other.organizationId == organizationId) && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.sku, sku) || other.sku == sku) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.price, price) || other.price == price) && + (identical(other.cost, cost) || other.cost == cost) && + (identical(other.businessType, businessType) || + other.businessType == businessType) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl) && + (identical(other.printerType, printerType) || + other.printerType == printerType) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + const DeepCollectionEquality().equals(other._variants, _variants)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + organizationId, + categoryId, + sku, + name, + description, + price, + cost, + businessType, + imageUrl, + printerType, + const DeepCollectionEquality().hash(_metadata), + isActive, + createdAt, + updatedAt, + const DeepCollectionEquality().hash(_variants), + ); + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductImplCopyWith<_$ProductImpl> get copyWith => + __$$ProductImplCopyWithImpl<_$ProductImpl>(this, _$identity); +} + +abstract class _Product implements Product { + const factory _Product({ + required final String id, + required final String organizationId, + required final String categoryId, + required final String sku, + required final String name, + required final String description, + required final double price, + required final double cost, + required final String businessType, + required final String imageUrl, + required final String printerType, + required final Map metadata, + required final bool isActive, + required final String createdAt, + required final String updatedAt, + required final List variants, + }) = _$ProductImpl; + + @override + String get id; + @override + String get organizationId; + @override + String get categoryId; + @override + String get sku; + @override + String get name; + @override + String get description; + @override + double get price; + @override + double get cost; + @override + String get businessType; + @override + String get imageUrl; + @override + String get printerType; + @override + Map get metadata; + @override + bool get isActive; + @override + String get createdAt; + @override + String get updatedAt; + @override + List get variants; + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductImplCopyWith<_$ProductImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProductVariant { + String get id => throw _privateConstructorUsedError; + String get productId => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + double get priceModifier => throw _privateConstructorUsedError; + double get cost => throw _privateConstructorUsedError; + Map get metadata => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + String get updatedAt => throw _privateConstructorUsedError; + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductVariantCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductVariantCopyWith<$Res> { + factory $ProductVariantCopyWith( + ProductVariant value, + $Res Function(ProductVariant) then, + ) = _$ProductVariantCopyWithImpl<$Res, ProductVariant>; + @useResult + $Res call({ + String id, + String productId, + String name, + double priceModifier, + double cost, + Map metadata, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class _$ProductVariantCopyWithImpl<$Res, $Val extends ProductVariant> + implements $ProductVariantCopyWith<$Res> { + _$ProductVariantCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? productId = null, + Object? name = null, + Object? priceModifier = null, + Object? cost = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + productId: null == productId + ? _value.productId + : productId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + priceModifier: null == priceModifier + ? _value.priceModifier + : priceModifier // ignore: cast_nullable_to_non_nullable + as double, + cost: null == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as double, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductVariantImplCopyWith<$Res> + implements $ProductVariantCopyWith<$Res> { + factory _$$ProductVariantImplCopyWith( + _$ProductVariantImpl value, + $Res Function(_$ProductVariantImpl) then, + ) = __$$ProductVariantImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String productId, + String name, + double priceModifier, + double cost, + Map metadata, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class __$$ProductVariantImplCopyWithImpl<$Res> + extends _$ProductVariantCopyWithImpl<$Res, _$ProductVariantImpl> + implements _$$ProductVariantImplCopyWith<$Res> { + __$$ProductVariantImplCopyWithImpl( + _$ProductVariantImpl _value, + $Res Function(_$ProductVariantImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? productId = null, + Object? name = null, + Object? priceModifier = null, + Object? cost = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _$ProductVariantImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + productId: null == productId + ? _value.productId + : productId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + priceModifier: null == priceModifier + ? _value.priceModifier + : priceModifier // ignore: cast_nullable_to_non_nullable + as double, + cost: null == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as double, + metadata: null == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$ProductVariantImpl + with DiagnosticableTreeMixin + implements _ProductVariant { + const _$ProductVariantImpl({ + required this.id, + required this.productId, + required this.name, + required this.priceModifier, + required this.cost, + required final Map metadata, + required this.createdAt, + required this.updatedAt, + }) : _metadata = metadata; + + @override + final String id; + @override + final String productId; + @override + final String name; + @override + final double priceModifier; + @override + final double cost; + final Map _metadata; + @override + Map get metadata { + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_metadata); + } + + @override + final String createdAt; + @override + final String updatedAt; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'ProductVariant(id: $id, productId: $productId, name: $name, priceModifier: $priceModifier, cost: $cost, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'ProductVariant')) + ..add(DiagnosticsProperty('id', id)) + ..add(DiagnosticsProperty('productId', productId)) + ..add(DiagnosticsProperty('name', name)) + ..add(DiagnosticsProperty('priceModifier', priceModifier)) + ..add(DiagnosticsProperty('cost', cost)) + ..add(DiagnosticsProperty('metadata', metadata)) + ..add(DiagnosticsProperty('createdAt', createdAt)) + ..add(DiagnosticsProperty('updatedAt', updatedAt)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductVariantImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.productId, productId) || + other.productId == productId) && + (identical(other.name, name) || other.name == name) && + (identical(other.priceModifier, priceModifier) || + other.priceModifier == priceModifier) && + (identical(other.cost, cost) || other.cost == cost) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + productId, + name, + priceModifier, + cost, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductVariantImplCopyWith<_$ProductVariantImpl> get copyWith => + __$$ProductVariantImplCopyWithImpl<_$ProductVariantImpl>( + this, + _$identity, + ); +} + +abstract class _ProductVariant implements ProductVariant { + const factory _ProductVariant({ + required final String id, + required final String productId, + required final String name, + required final double priceModifier, + required final double cost, + required final Map metadata, + required final String createdAt, + required final String updatedAt, + }) = _$ProductVariantImpl; + + @override + String get id; + @override + String get productId; + @override + String get name; + @override + double get priceModifier; + @override + double get cost; + @override + Map get metadata; + @override + String get createdAt; + @override + String get updatedAt; + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductVariantImplCopyWith<_$ProductVariantImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProductFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductFailureCopyWith<$Res> { + factory $ProductFailureCopyWith( + ProductFailure value, + $Res Function(ProductFailure) then, + ) = _$ProductFailureCopyWithImpl<$Res, ProductFailure>; +} + +/// @nodoc +class _$ProductFailureCopyWithImpl<$Res, $Val extends ProductFailure> + implements $ProductFailureCopyWith<$Res> { + _$ProductFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({ApiFailure failure}); + + $ApiFailureCopyWith<$Res> get failure; +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failure = null}) { + return _then( + _$ServerErrorImpl( + null == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as ApiFailure, + ), + ); + } + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ApiFailureCopyWith<$Res> get failure { + return $ApiFailureCopyWith<$Res>(_value.failure, (value) { + return _then(_value.copyWith(failure: value)); + }); + } +} + +/// @nodoc + +class _$ServerErrorImpl with DiagnosticableTreeMixin implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'ProductFailure.serverError(failure: $failure)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'ProductFailure.serverError')) + ..add(DiagnosticsProperty('failure', failure)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ServerErrorImpl && + (identical(other.failure, failure) || other.failure == failure)); + } + + @override + int get hashCode => Object.hash(runtimeType, failure); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return serverError(failure); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return serverError?.call(failure); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(failure); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return serverError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return serverError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError implements ProductFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl + with DiagnosticableTreeMixin + implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'ProductFailure.unexpectedError()'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'ProductFailure.unexpectedError')); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnexpectedErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return unexpectedError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return unexpectedError?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return unexpectedError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return unexpectedError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError implements ProductFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$EmptyImplCopyWith<$Res> { + factory _$$EmptyImplCopyWith( + _$EmptyImpl value, + $Res Function(_$EmptyImpl) then, + ) = __$$EmptyImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$EmptyImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$EmptyImpl> + implements _$$EmptyImplCopyWith<$Res> { + __$$EmptyImplCopyWithImpl( + _$EmptyImpl _value, + $Res Function(_$EmptyImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$EmptyImpl with DiagnosticableTreeMixin implements _Empty { + const _$EmptyImpl(); + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'ProductFailure.empty()'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties..add(DiagnosticsProperty('type', 'ProductFailure.empty')); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$EmptyImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return empty(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return empty?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (empty != null) { + return empty(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return empty(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return empty?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (empty != null) { + return empty(this); + } + return orElse(); + } +} + +abstract class _Empty implements ProductFailure { + const factory _Empty() = _$EmptyImpl; +} + +/// @nodoc +abstract class _$$LocalStorageErrorImplCopyWith<$Res> { + factory _$$LocalStorageErrorImplCopyWith( + _$LocalStorageErrorImpl value, + $Res Function(_$LocalStorageErrorImpl) then, + ) = __$$LocalStorageErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$LocalStorageErrorImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$LocalStorageErrorImpl> + implements _$$LocalStorageErrorImplCopyWith<$Res> { + __$$LocalStorageErrorImplCopyWithImpl( + _$LocalStorageErrorImpl _value, + $Res Function(_$LocalStorageErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$LocalStorageErrorImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$LocalStorageErrorImpl + with DiagnosticableTreeMixin + implements _LocalStorageError { + const _$LocalStorageErrorImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'ProductFailure.localStorageError(erroMessage: $erroMessage)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'ProductFailure.localStorageError')) + ..add(DiagnosticsProperty('erroMessage', erroMessage)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LocalStorageErrorImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LocalStorageErrorImplCopyWith<_$LocalStorageErrorImpl> get copyWith => + __$$LocalStorageErrorImplCopyWithImpl<_$LocalStorageErrorImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return localStorageError(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return localStorageError?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (localStorageError != null) { + return localStorageError(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return localStorageError(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return localStorageError?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (localStorageError != null) { + return localStorageError(this); + } + return orElse(); + } +} + +abstract class _LocalStorageError implements ProductFailure { + const factory _LocalStorageError(final String erroMessage) = + _$LocalStorageErrorImpl; + + String get erroMessage; + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LocalStorageErrorImplCopyWith<_$LocalStorageErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$DynamicErrorMessageImplCopyWith<$Res> { + factory _$$DynamicErrorMessageImplCopyWith( + _$DynamicErrorMessageImpl value, + $Res Function(_$DynamicErrorMessageImpl) then, + ) = __$$DynamicErrorMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$DynamicErrorMessageImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$DynamicErrorMessageImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$DynamicErrorMessageImpl + with DiagnosticableTreeMixin + implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) { + return 'ProductFailure.dynamicErrorMessage(erroMessage: $erroMessage)'; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + ..add(DiagnosticsProperty('type', 'ProductFailure.dynamicErrorMessage')) + ..add(DiagnosticsProperty('erroMessage', erroMessage)); + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$DynamicErrorMessageImpl && + (identical(other.erroMessage, erroMessage) || + other.erroMessage == erroMessage)); + } + + @override + int get hashCode => Object.hash(runtimeType, erroMessage); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + __$$DynamicErrorMessageImplCopyWithImpl<_$DynamicErrorMessageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) localStorageError, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return dynamicErrorMessage(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + TResult? Function(String erroMessage)? localStorageError, + TResult? Function(String erroMessage)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(erroMessage); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(ApiFailure failure)? serverError, + TResult Function()? unexpectedError, + TResult Function()? empty, + TResult Function(String erroMessage)? localStorageError, + TResult Function(String erroMessage)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(erroMessage); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_ServerError value) serverError, + required TResult Function(_UnexpectedError value) unexpectedError, + required TResult Function(_Empty value) empty, + required TResult Function(_LocalStorageError value) localStorageError, + required TResult Function(_DynamicErrorMessage value) dynamicErrorMessage, + }) { + return dynamicErrorMessage(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_ServerError value)? serverError, + TResult? Function(_UnexpectedError value)? unexpectedError, + TResult? Function(_Empty value)? empty, + TResult? Function(_LocalStorageError value)? localStorageError, + TResult? Function(_DynamicErrorMessage value)? dynamicErrorMessage, + }) { + return dynamicErrorMessage?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_ServerError value)? serverError, + TResult Function(_UnexpectedError value)? unexpectedError, + TResult Function(_Empty value)? empty, + TResult Function(_LocalStorageError value)? localStorageError, + TResult Function(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(this); + } + return orElse(); + } +} + +abstract class _DynamicErrorMessage implements ProductFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/product/repositories/i_product_repository.dart b/lib/domain/product/repositories/i_product_repository.dart new file mode 100644 index 0000000..6407650 --- /dev/null +++ b/lib/domain/product/repositories/i_product_repository.dart @@ -0,0 +1,34 @@ +part of '../product.dart'; + +abstract class IProductRepository { + Future> getProducts({ + int page = 1, + int limit = 10, + String? categoryId, + String? search, + bool forceRefresh = false, + }); + + Future>> searchProductsOptimized( + String query, + ); + + Future> syncAllProducts(); + + Future> refreshProducts({ + String? categoryId, + String? search, + }); + + Future> getProductById(String id); + + Future hasLocalProducts(); + + Future>> getDatabaseStats(); + + void clearCache(); + + Future isLocalDatabaseReady(); + + Future clearAllProducts(); +} diff --git a/lib/infrastructure/product/datasources/local_data_provider.dart b/lib/infrastructure/product/datasources/local_data_provider.dart new file mode 100644 index 0000000..82e6fe3 --- /dev/null +++ b/lib/infrastructure/product/datasources/local_data_provider.dart @@ -0,0 +1,486 @@ +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> _queryCache = {}; + final Duration _cacheExpiry = Duration(minutes: AppConstant.cacheExpire); + final Map _cacheTimestamps = {}; + + ProductLocalDataProvider(this._databaseHelper); + + Future> saveProductsBatch( + List 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>> 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>> 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 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> maps = await db.rawQuery( + query, + whereArgs, + ); + + final List 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>> searchProductsOptimized( + String query, + ) async { + final db = await _databaseHelper.database; + + try { + log('๐Ÿ” Optimized search for: "$query"', name: _logName); + + // โœ… Smart query with prioritization + final List> 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 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> getProductById(String id) async { + final db = await _databaseHelper.database; + + try { + final List> 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>> 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 getTotalCount({String? categoryId, String? search}) async { + final db = await _databaseHelper.database; + + try { + String query = 'SELECT COUNT(*) FROM products WHERE 1=1'; + List 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 hasProducts() async { + final count = await getTotalCount(); + final hasData = count > 0; + log('๐Ÿ” Has products: $hasData ($count products)', name: _logName); + return hasData; + } + + Future 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 _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> _getProductVariants( + Database db, + String productId, + ) async { + try { + final List> 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 = []; + + _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'); + } + } +} diff --git a/lib/infrastructure/product/datasources/remote_data_provider.dart b/lib/infrastructure/product/datasources/remote_data_provider.dart new file mode 100644 index 0000000..1f9316b --- /dev/null +++ b/lib/infrastructure/product/datasources/remote_data_provider.dart @@ -0,0 +1,57 @@ +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> fetchProducts({ + int page = 1, + int limit = 10, + String? categoryId, + String? search, + }) async { + try { + Map 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, + ); + + return DC.data(categories); + } on ApiFailure catch (e, s) { + log('fetchProductError', name: _logName, error: e, stackTrace: s); + return DC.error(ProductFailure.serverError(e)); + } + } +} diff --git a/lib/infrastructure/product/dtos/product_dto.dart b/lib/infrastructure/product/dtos/product_dto.dart new file mode 100644 index 0000000..b6a4b05 --- /dev/null +++ b/lib/infrastructure/product/dtos/product_dto.dart @@ -0,0 +1,173 @@ +part of '../product_dtos.dart'; + +@freezed +class ListProductDto with _$ListProductDto { + const ListProductDto._(); + + const factory ListProductDto({ + @JsonKey(name: "products") required List 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 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? metadata, + @JsonKey(name: "is_active") bool? isActive, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + @JsonKey(name: "variants") List? variants, + }) = _ProductDto; + + factory ProductDto.fromJson(Map 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 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 map, + List 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 + : 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, + ); +} + +@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? metadata, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + }) = _ProductVariantDto; + + factory ProductVariantDto.fromJson(Map 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 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 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 + : null, + createdAt: map['created_at'] as String?, + updatedAt: map['updated_at'] as String?, + ); +} diff --git a/lib/infrastructure/product/product_dtos.dart b/lib/infrastructure/product/product_dtos.dart new file mode 100644 index 0000000..7008875 --- /dev/null +++ b/lib/infrastructure/product/product_dtos.dart @@ -0,0 +1,10 @@ +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'; diff --git a/lib/infrastructure/product/product_dtos.freezed.dart b/lib/infrastructure/product/product_dtos.freezed.dart new file mode 100644 index 0000000..55271d3 --- /dev/null +++ b/lib/infrastructure/product/product_dtos.freezed.dart @@ -0,0 +1,1204 @@ +// 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 'product_dtos.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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', +); + +ListProductDto _$ListProductDtoFromJson(Map json) { + return _ListProductDto.fromJson(json); +} + +/// @nodoc +mixin _$ListProductDto { + @JsonKey(name: "products") + List get products => throw _privateConstructorUsedError; + @JsonKey(name: "total_count") + int get totalCount => throw _privateConstructorUsedError; + @JsonKey(name: "page") + int get page => throw _privateConstructorUsedError; + @JsonKey(name: "limit") + int get limit => throw _privateConstructorUsedError; + @JsonKey(name: "total_pages") + int get totalPages => throw _privateConstructorUsedError; + + /// Serializes this ListProductDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ListProductDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ListProductDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ListProductDtoCopyWith<$Res> { + factory $ListProductDtoCopyWith( + ListProductDto value, + $Res Function(ListProductDto) then, + ) = _$ListProductDtoCopyWithImpl<$Res, ListProductDto>; + @useResult + $Res call({ + @JsonKey(name: "products") List products, + @JsonKey(name: "total_count") int totalCount, + @JsonKey(name: "page") int page, + @JsonKey(name: "limit") int limit, + @JsonKey(name: "total_pages") int totalPages, + }); +} + +/// @nodoc +class _$ListProductDtoCopyWithImpl<$Res, $Val extends ListProductDto> + implements $ListProductDtoCopyWith<$Res> { + _$ListProductDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ListProductDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? totalCount = null, + Object? page = null, + Object? limit = null, + Object? totalPages = null, + }) { + return _then( + _value.copyWith( + products: null == products + ? _value.products + : products // ignore: cast_nullable_to_non_nullable + as List, + totalCount: null == totalCount + ? _value.totalCount + : totalCount // ignore: cast_nullable_to_non_nullable + as int, + page: null == page + ? _value.page + : page // ignore: cast_nullable_to_non_nullable + as int, + limit: null == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as int, + totalPages: null == totalPages + ? _value.totalPages + : totalPages // ignore: cast_nullable_to_non_nullable + as int, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ListProductDtoImplCopyWith<$Res> + implements $ListProductDtoCopyWith<$Res> { + factory _$$ListProductDtoImplCopyWith( + _$ListProductDtoImpl value, + $Res Function(_$ListProductDtoImpl) then, + ) = __$$ListProductDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: "products") List products, + @JsonKey(name: "total_count") int totalCount, + @JsonKey(name: "page") int page, + @JsonKey(name: "limit") int limit, + @JsonKey(name: "total_pages") int totalPages, + }); +} + +/// @nodoc +class __$$ListProductDtoImplCopyWithImpl<$Res> + extends _$ListProductDtoCopyWithImpl<$Res, _$ListProductDtoImpl> + implements _$$ListProductDtoImplCopyWith<$Res> { + __$$ListProductDtoImplCopyWithImpl( + _$ListProductDtoImpl _value, + $Res Function(_$ListProductDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ListProductDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? totalCount = null, + Object? page = null, + Object? limit = null, + Object? totalPages = null, + }) { + return _then( + _$ListProductDtoImpl( + products: null == products + ? _value._products + : products // ignore: cast_nullable_to_non_nullable + as List, + totalCount: null == totalCount + ? _value.totalCount + : totalCount // ignore: cast_nullable_to_non_nullable + as int, + page: null == page + ? _value.page + : page // ignore: cast_nullable_to_non_nullable + as int, + limit: null == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as int, + totalPages: null == totalPages + ? _value.totalPages + : totalPages // ignore: cast_nullable_to_non_nullable + as int, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ListProductDtoImpl extends _ListProductDto { + const _$ListProductDtoImpl({ + @JsonKey(name: "products") required final List products, + @JsonKey(name: "total_count") required this.totalCount, + @JsonKey(name: "page") required this.page, + @JsonKey(name: "limit") required this.limit, + @JsonKey(name: "total_pages") required this.totalPages, + }) : _products = products, + super._(); + + factory _$ListProductDtoImpl.fromJson(Map json) => + _$$ListProductDtoImplFromJson(json); + + final List _products; + @override + @JsonKey(name: "products") + List get products { + if (_products is EqualUnmodifiableListView) return _products; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_products); + } + + @override + @JsonKey(name: "total_count") + final int totalCount; + @override + @JsonKey(name: "page") + final int page; + @override + @JsonKey(name: "limit") + final int limit; + @override + @JsonKey(name: "total_pages") + final int totalPages; + + @override + String toString() { + return 'ListProductDto(products: $products, totalCount: $totalCount, page: $page, limit: $limit, totalPages: $totalPages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ListProductDtoImpl && + const DeepCollectionEquality().equals(other._products, _products) && + (identical(other.totalCount, totalCount) || + other.totalCount == totalCount) && + (identical(other.page, page) || other.page == page) && + (identical(other.limit, limit) || other.limit == limit) && + (identical(other.totalPages, totalPages) || + other.totalPages == totalPages)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_products), + totalCount, + page, + limit, + totalPages, + ); + + /// Create a copy of ListProductDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ListProductDtoImplCopyWith<_$ListProductDtoImpl> get copyWith => + __$$ListProductDtoImplCopyWithImpl<_$ListProductDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$ListProductDtoImplToJson(this); + } +} + +abstract class _ListProductDto extends ListProductDto { + const factory _ListProductDto({ + @JsonKey(name: "products") required final List products, + @JsonKey(name: "total_count") required final int totalCount, + @JsonKey(name: "page") required final int page, + @JsonKey(name: "limit") required final int limit, + @JsonKey(name: "total_pages") required final int totalPages, + }) = _$ListProductDtoImpl; + const _ListProductDto._() : super._(); + + factory _ListProductDto.fromJson(Map json) = + _$ListProductDtoImpl.fromJson; + + @override + @JsonKey(name: "products") + List get products; + @override + @JsonKey(name: "total_count") + int get totalCount; + @override + @JsonKey(name: "page") + int get page; + @override + @JsonKey(name: "limit") + int get limit; + @override + @JsonKey(name: "total_pages") + int get totalPages; + + /// Create a copy of ListProductDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ListProductDtoImplCopyWith<_$ListProductDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ProductDto _$ProductDtoFromJson(Map json) { + return _ProductDto.fromJson(json); +} + +/// @nodoc +mixin _$ProductDto { + @JsonKey(name: "id") + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: "organization_id") + String? get organizationId => throw _privateConstructorUsedError; + @JsonKey(name: "category_id") + String? get categoryId => throw _privateConstructorUsedError; + @JsonKey(name: "sku") + String? get sku => throw _privateConstructorUsedError; + @JsonKey(name: "name") + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: "description") + String? get description => throw _privateConstructorUsedError; + @JsonKey(name: "price") + double? get price => throw _privateConstructorUsedError; + @JsonKey(name: "cost") + double? get cost => throw _privateConstructorUsedError; + @JsonKey(name: "business_type") + String? get businessType => throw _privateConstructorUsedError; + @JsonKey(name: "image_url") + String? get imageUrl => throw _privateConstructorUsedError; + @JsonKey(name: "printer_type") + String? get printerType => throw _privateConstructorUsedError; + @JsonKey(name: "metadata") + Map? get metadata => throw _privateConstructorUsedError; + @JsonKey(name: "is_active") + bool? get isActive => throw _privateConstructorUsedError; + @JsonKey(name: "created_at") + String? get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: "updated_at") + String? get updatedAt => throw _privateConstructorUsedError; + @JsonKey(name: "variants") + List? get variants => throw _privateConstructorUsedError; + + /// Serializes this ProductDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductDtoCopyWith<$Res> { + factory $ProductDtoCopyWith( + ProductDto value, + $Res Function(ProductDto) then, + ) = _$ProductDtoCopyWithImpl<$Res, ProductDto>; + @useResult + $Res call({ + @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? metadata, + @JsonKey(name: "is_active") bool? isActive, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + @JsonKey(name: "variants") List? variants, + }); +} + +/// @nodoc +class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto> + implements $ProductDtoCopyWith<$Res> { + _$ProductDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? organizationId = freezed, + Object? categoryId = freezed, + Object? sku = freezed, + Object? name = freezed, + Object? description = freezed, + Object? price = freezed, + Object? cost = freezed, + Object? businessType = freezed, + Object? imageUrl = freezed, + Object? printerType = freezed, + Object? metadata = freezed, + Object? isActive = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + Object? variants = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + organizationId: freezed == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String?, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + sku: freezed == sku + ? _value.sku + : sku // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + price: freezed == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double?, + cost: freezed == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as double?, + businessType: freezed == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String?, + imageUrl: freezed == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String?, + printerType: freezed == printerType + ? _value.printerType + : printerType // ignore: cast_nullable_to_non_nullable + as String?, + metadata: freezed == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + variants: freezed == variants + ? _value.variants + : variants // ignore: cast_nullable_to_non_nullable + as List?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductDtoImplCopyWith<$Res> + implements $ProductDtoCopyWith<$Res> { + factory _$$ProductDtoImplCopyWith( + _$ProductDtoImpl value, + $Res Function(_$ProductDtoImpl) then, + ) = __$$ProductDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @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? metadata, + @JsonKey(name: "is_active") bool? isActive, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + @JsonKey(name: "variants") List? variants, + }); +} + +/// @nodoc +class __$$ProductDtoImplCopyWithImpl<$Res> + extends _$ProductDtoCopyWithImpl<$Res, _$ProductDtoImpl> + implements _$$ProductDtoImplCopyWith<$Res> { + __$$ProductDtoImplCopyWithImpl( + _$ProductDtoImpl _value, + $Res Function(_$ProductDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? organizationId = freezed, + Object? categoryId = freezed, + Object? sku = freezed, + Object? name = freezed, + Object? description = freezed, + Object? price = freezed, + Object? cost = freezed, + Object? businessType = freezed, + Object? imageUrl = freezed, + Object? printerType = freezed, + Object? metadata = freezed, + Object? isActive = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + Object? variants = freezed, + }) { + return _then( + _$ProductDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + organizationId: freezed == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String?, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + sku: freezed == sku + ? _value.sku + : sku // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + price: freezed == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as double?, + cost: freezed == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as double?, + businessType: freezed == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String?, + imageUrl: freezed == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String?, + printerType: freezed == printerType + ? _value.printerType + : printerType // ignore: cast_nullable_to_non_nullable + as String?, + metadata: freezed == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + variants: freezed == variants + ? _value._variants + : variants // ignore: cast_nullable_to_non_nullable + as List?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProductDtoImpl extends _ProductDto { + const _$ProductDtoImpl({ + @JsonKey(name: "id") this.id, + @JsonKey(name: "organization_id") this.organizationId, + @JsonKey(name: "category_id") this.categoryId, + @JsonKey(name: "sku") this.sku, + @JsonKey(name: "name") this.name, + @JsonKey(name: "description") this.description, + @JsonKey(name: "price") this.price, + @JsonKey(name: "cost") this.cost, + @JsonKey(name: "business_type") this.businessType, + @JsonKey(name: "image_url") this.imageUrl, + @JsonKey(name: "printer_type") this.printerType, + @JsonKey(name: "metadata") final Map? metadata, + @JsonKey(name: "is_active") this.isActive, + @JsonKey(name: "created_at") this.createdAt, + @JsonKey(name: "updated_at") this.updatedAt, + @JsonKey(name: "variants") final List? variants, + }) : _metadata = metadata, + _variants = variants, + super._(); + + factory _$ProductDtoImpl.fromJson(Map json) => + _$$ProductDtoImplFromJson(json); + + @override + @JsonKey(name: "id") + final String? id; + @override + @JsonKey(name: "organization_id") + final String? organizationId; + @override + @JsonKey(name: "category_id") + final String? categoryId; + @override + @JsonKey(name: "sku") + final String? sku; + @override + @JsonKey(name: "name") + final String? name; + @override + @JsonKey(name: "description") + final String? description; + @override + @JsonKey(name: "price") + final double? price; + @override + @JsonKey(name: "cost") + final double? cost; + @override + @JsonKey(name: "business_type") + final String? businessType; + @override + @JsonKey(name: "image_url") + final String? imageUrl; + @override + @JsonKey(name: "printer_type") + final String? printerType; + final Map? _metadata; + @override + @JsonKey(name: "metadata") + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey(name: "is_active") + final bool? isActive; + @override + @JsonKey(name: "created_at") + final String? createdAt; + @override + @JsonKey(name: "updated_at") + final String? updatedAt; + final List? _variants; + @override + @JsonKey(name: "variants") + List? get variants { + final value = _variants; + if (value == null) return null; + if (_variants is EqualUnmodifiableListView) return _variants; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'ProductDto(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.organizationId, organizationId) || + other.organizationId == organizationId) && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.sku, sku) || other.sku == sku) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.price, price) || other.price == price) && + (identical(other.cost, cost) || other.cost == cost) && + (identical(other.businessType, businessType) || + other.businessType == businessType) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl) && + (identical(other.printerType, printerType) || + other.printerType == printerType) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + const DeepCollectionEquality().equals(other._variants, _variants)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + organizationId, + categoryId, + sku, + name, + description, + price, + cost, + businessType, + imageUrl, + printerType, + const DeepCollectionEquality().hash(_metadata), + isActive, + createdAt, + updatedAt, + const DeepCollectionEquality().hash(_variants), + ); + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductDtoImplCopyWith<_$ProductDtoImpl> get copyWith => + __$$ProductDtoImplCopyWithImpl<_$ProductDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ProductDtoImplToJson(this); + } +} + +abstract class _ProductDto extends ProductDto { + const factory _ProductDto({ + @JsonKey(name: "id") final String? id, + @JsonKey(name: "organization_id") final String? organizationId, + @JsonKey(name: "category_id") final String? categoryId, + @JsonKey(name: "sku") final String? sku, + @JsonKey(name: "name") final String? name, + @JsonKey(name: "description") final String? description, + @JsonKey(name: "price") final double? price, + @JsonKey(name: "cost") final double? cost, + @JsonKey(name: "business_type") final String? businessType, + @JsonKey(name: "image_url") final String? imageUrl, + @JsonKey(name: "printer_type") final String? printerType, + @JsonKey(name: "metadata") final Map? metadata, + @JsonKey(name: "is_active") final bool? isActive, + @JsonKey(name: "created_at") final String? createdAt, + @JsonKey(name: "updated_at") final String? updatedAt, + @JsonKey(name: "variants") final List? variants, + }) = _$ProductDtoImpl; + const _ProductDto._() : super._(); + + factory _ProductDto.fromJson(Map json) = + _$ProductDtoImpl.fromJson; + + @override + @JsonKey(name: "id") + String? get id; + @override + @JsonKey(name: "organization_id") + String? get organizationId; + @override + @JsonKey(name: "category_id") + String? get categoryId; + @override + @JsonKey(name: "sku") + String? get sku; + @override + @JsonKey(name: "name") + String? get name; + @override + @JsonKey(name: "description") + String? get description; + @override + @JsonKey(name: "price") + double? get price; + @override + @JsonKey(name: "cost") + double? get cost; + @override + @JsonKey(name: "business_type") + String? get businessType; + @override + @JsonKey(name: "image_url") + String? get imageUrl; + @override + @JsonKey(name: "printer_type") + String? get printerType; + @override + @JsonKey(name: "metadata") + Map? get metadata; + @override + @JsonKey(name: "is_active") + bool? get isActive; + @override + @JsonKey(name: "created_at") + String? get createdAt; + @override + @JsonKey(name: "updated_at") + String? get updatedAt; + @override + @JsonKey(name: "variants") + List? get variants; + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductDtoImplCopyWith<_$ProductDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ProductVariantDto _$ProductVariantDtoFromJson(Map json) { + return _ProductVariantDto.fromJson(json); +} + +/// @nodoc +mixin _$ProductVariantDto { + @JsonKey(name: "id") + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: "product_id") + String? get productId => throw _privateConstructorUsedError; + @JsonKey(name: "name") + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: "price_modifier") + double? get priceModifier => throw _privateConstructorUsedError; + @JsonKey(name: "cost") + double? get cost => throw _privateConstructorUsedError; + @JsonKey(name: "metadata") + Map? get metadata => throw _privateConstructorUsedError; + @JsonKey(name: "created_at") + String? get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: "updated_at") + String? get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this ProductVariantDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductVariantDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductVariantDtoCopyWith<$Res> { + factory $ProductVariantDtoCopyWith( + ProductVariantDto value, + $Res Function(ProductVariantDto) then, + ) = _$ProductVariantDtoCopyWithImpl<$Res, ProductVariantDto>; + @useResult + $Res call({ + @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? metadata, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + }); +} + +/// @nodoc +class _$ProductVariantDtoCopyWithImpl<$Res, $Val extends ProductVariantDto> + implements $ProductVariantDtoCopyWith<$Res> { + _$ProductVariantDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? productId = freezed, + Object? name = freezed, + Object? priceModifier = freezed, + Object? cost = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + productId: freezed == productId + ? _value.productId + : productId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + priceModifier: freezed == priceModifier + ? _value.priceModifier + : priceModifier // ignore: cast_nullable_to_non_nullable + as double?, + cost: freezed == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as double?, + metadata: freezed == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductVariantDtoImplCopyWith<$Res> + implements $ProductVariantDtoCopyWith<$Res> { + factory _$$ProductVariantDtoImplCopyWith( + _$ProductVariantDtoImpl value, + $Res Function(_$ProductVariantDtoImpl) then, + ) = __$$ProductVariantDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @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? metadata, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + }); +} + +/// @nodoc +class __$$ProductVariantDtoImplCopyWithImpl<$Res> + extends _$ProductVariantDtoCopyWithImpl<$Res, _$ProductVariantDtoImpl> + implements _$$ProductVariantDtoImplCopyWith<$Res> { + __$$ProductVariantDtoImplCopyWithImpl( + _$ProductVariantDtoImpl _value, + $Res Function(_$ProductVariantDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? productId = freezed, + Object? name = freezed, + Object? priceModifier = freezed, + Object? cost = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _$ProductVariantDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + productId: freezed == productId + ? _value.productId + : productId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + priceModifier: freezed == priceModifier + ? _value.priceModifier + : priceModifier // ignore: cast_nullable_to_non_nullable + as double?, + cost: freezed == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as double?, + metadata: freezed == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as String?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as String?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProductVariantDtoImpl extends _ProductVariantDto { + const _$ProductVariantDtoImpl({ + @JsonKey(name: "id") this.id, + @JsonKey(name: "product_id") this.productId, + @JsonKey(name: "name") this.name, + @JsonKey(name: "price_modifier") this.priceModifier, + @JsonKey(name: "cost") this.cost, + @JsonKey(name: "metadata") final Map? metadata, + @JsonKey(name: "created_at") this.createdAt, + @JsonKey(name: "updated_at") this.updatedAt, + }) : _metadata = metadata, + super._(); + + factory _$ProductVariantDtoImpl.fromJson(Map json) => + _$$ProductVariantDtoImplFromJson(json); + + @override + @JsonKey(name: "id") + final String? id; + @override + @JsonKey(name: "product_id") + final String? productId; + @override + @JsonKey(name: "name") + final String? name; + @override + @JsonKey(name: "price_modifier") + final double? priceModifier; + @override + @JsonKey(name: "cost") + final double? cost; + final Map? _metadata; + @override + @JsonKey(name: "metadata") + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey(name: "created_at") + final String? createdAt; + @override + @JsonKey(name: "updated_at") + final String? updatedAt; + + @override + String toString() { + return 'ProductVariantDto(id: $id, productId: $productId, name: $name, priceModifier: $priceModifier, cost: $cost, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductVariantDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.productId, productId) || + other.productId == productId) && + (identical(other.name, name) || other.name == name) && + (identical(other.priceModifier, priceModifier) || + other.priceModifier == priceModifier) && + (identical(other.cost, cost) || other.cost == cost) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + productId, + name, + priceModifier, + cost, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductVariantDtoImplCopyWith<_$ProductVariantDtoImpl> get copyWith => + __$$ProductVariantDtoImplCopyWithImpl<_$ProductVariantDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$ProductVariantDtoImplToJson(this); + } +} + +abstract class _ProductVariantDto extends ProductVariantDto { + const factory _ProductVariantDto({ + @JsonKey(name: "id") final String? id, + @JsonKey(name: "product_id") final String? productId, + @JsonKey(name: "name") final String? name, + @JsonKey(name: "price_modifier") final double? priceModifier, + @JsonKey(name: "cost") final double? cost, + @JsonKey(name: "metadata") final Map? metadata, + @JsonKey(name: "created_at") final String? createdAt, + @JsonKey(name: "updated_at") final String? updatedAt, + }) = _$ProductVariantDtoImpl; + const _ProductVariantDto._() : super._(); + + factory _ProductVariantDto.fromJson(Map json) = + _$ProductVariantDtoImpl.fromJson; + + @override + @JsonKey(name: "id") + String? get id; + @override + @JsonKey(name: "product_id") + String? get productId; + @override + @JsonKey(name: "name") + String? get name; + @override + @JsonKey(name: "price_modifier") + double? get priceModifier; + @override + @JsonKey(name: "cost") + double? get cost; + @override + @JsonKey(name: "metadata") + Map? get metadata; + @override + @JsonKey(name: "created_at") + String? get createdAt; + @override + @JsonKey(name: "updated_at") + String? get updatedAt; + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductVariantDtoImplCopyWith<_$ProductVariantDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/product/product_dtos.g.dart b/lib/infrastructure/product/product_dtos.g.dart new file mode 100644 index 0000000..6cb8f4a --- /dev/null +++ b/lib/infrastructure/product/product_dtos.g.dart @@ -0,0 +1,96 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'product_dtos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ListProductDtoImpl _$$ListProductDtoImplFromJson(Map json) => + _$ListProductDtoImpl( + products: (json['products'] as List) + .map((e) => ProductDto.fromJson(e as Map)) + .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 _$$ListProductDtoImplToJson( + _$ListProductDtoImpl instance, +) => { + 'products': instance.products, + 'total_count': instance.totalCount, + 'page': instance.page, + 'limit': instance.limit, + 'total_pages': instance.totalPages, +}; + +_$ProductDtoImpl _$$ProductDtoImplFromJson(Map 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?, + isActive: json['is_active'] as bool?, + createdAt: json['created_at'] as String?, + updatedAt: json['updated_at'] as String?, + variants: (json['variants'] as List?) + ?.map((e) => ProductVariantDto.fromJson(e as Map)) + .toList(), + ); + +Map _$$ProductDtoImplToJson(_$ProductDtoImpl instance) => + { + '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 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?, + createdAt: json['created_at'] as String?, + updatedAt: json['updated_at'] as String?, +); + +Map _$$ProductVariantDtoImplToJson( + _$ProductVariantDtoImpl instance, +) => { + '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, +}; diff --git a/lib/infrastructure/product/repositories/product_repository.dart b/lib/infrastructure/product/repositories/product_repository.dart new file mode 100644 index 0000000..363444d --- /dev/null +++ b/lib/infrastructure/product/repositories/product_repository.dart @@ -0,0 +1,331 @@ +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'; + +@Injectable(as: IProductRepository) +class ProductRepository implements IProductRepository { + final ProductRemoteDataProvider _remoteDataProvider; + final ProductLocalDataProvider _localDataProvider; + + final _logName = 'ProductRepository'; + + ProductRepository(this._remoteDataProvider, this._localDataProvider); + + @override + Future> 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> 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> 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>> 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> 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>> + 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 hasLocalProducts() async { + final hasProducts = await _localDataProvider.hasProducts(); + log('๐Ÿ“Š Has local products: $hasProducts', name: _logName); + return hasProducts; + } + + @override + Future 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 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; + } + } +} diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 8e76678..663cc86 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -14,6 +14,8 @@ import 'package:apskel_pos_flutter_v2/application/auth/login_form/login_form_blo as _i46; import 'package:apskel_pos_flutter_v2/application/outlet/outlet_loader/outlet_loader_bloc.dart' as _i76; +import 'package:apskel_pos_flutter_v2/application/product/product_loader/product_loader_bloc.dart' + as _i13; import 'package:apskel_pos_flutter_v2/common/api/api_client.dart' as _i457; import 'package:apskel_pos_flutter_v2/common/database/database_helper.dart' as _i487; @@ -28,6 +30,7 @@ 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/category/category.dart' as _i502; 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/infrastructure/auth/datasources/local_data_provider.dart' as _i204; @@ -47,6 +50,12 @@ import 'package:apskel_pos_flutter_v2/infrastructure/outlet/datasources/remote_d as _i132; import 'package:apskel_pos_flutter_v2/infrastructure/outlet/repositories/outlet_repository.dart' 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' as _i800; import 'package:connectivity_plus/connectivity_plus.dart' as _i895; @@ -85,6 +94,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i708.CategoryLocalDataProvider>( () => _i708.CategoryLocalDataProvider(gh<_i487.DatabaseHelper>()), ); + gh.factory<_i464.ProductLocalDataProvider>( + () => _i464.ProductLocalDataProvider(gh<_i487.DatabaseHelper>()), + ); gh.factory<_i204.AuthLocalDataProvider>( () => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()), ); @@ -104,6 +116,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i132.OutletRemoteDataProvider>( () => _i132.OutletRemoteDataProvider(gh<_i457.ApiClient>()), ); + gh.factory<_i707.ProductRemoteDataProvider>( + () => _i707.ProductRemoteDataProvider(gh<_i457.ApiClient>()), + ); gh.factory<_i776.IAuthRepository>( () => _i941.AuthRepository( gh<_i370.AuthRemoteDataProvider>(), @@ -116,6 +131,12 @@ extension GetItInjectableX on _i174.GetIt { gh<_i708.CategoryLocalDataProvider>(), ), ); + gh.factory<_i44.IProductRepository>( + () => _i763.ProductRepository( + gh<_i707.ProductRemoteDataProvider>(), + gh<_i464.ProductLocalDataProvider>(), + ), + ); gh.factory<_i552.IOutletRepository>( () => _i845.OutletRepository( gh<_i132.OutletRemoteDataProvider>(), @@ -134,6 +155,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i76.OutletLoaderBloc>( () => _i76.OutletLoaderBloc(gh<_i552.IOutletRepository>()), ); + gh.factory<_i13.ProductLoaderBloc>( + () => _i13.ProductLoaderBloc(gh<_i44.IProductRepository>()), + ); return this; } }