diff --git a/lib/application/category/category_loader/category_loader_bloc.dart b/lib/application/category/category_loader/category_loader_bloc.dart new file mode 100644 index 0000000..38d4e8c --- /dev/null +++ b/lib/application/category/category_loader/category_loader_bloc.dart @@ -0,0 +1,312 @@ +import 'dart:async'; +import 'dart:developer'; + +import 'package:bloc/bloc.dart'; +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../../domain/category/category.dart'; + +part 'category_loader_event.dart'; +part 'category_loader_state.dart'; +part 'category_loader_bloc.freezed.dart'; + +class CategoryLoaderBloc + extends Bloc { + final ICategoryRepository _categoryRepository; + + Timer? _searchDebounce; + bool _isLoadingMore = false; + + CategoryLoaderBloc(this._categoryRepository) + : super(CategoryLoaderState.initial()) { + on(_onCategoryLoaderEvent); + } + + Future _onCategoryLoaderEvent( + CategoryLoaderEvent event, + Emitter emit, + ) { + return event.map( + getCategories: (e) async { + emit(state.copyWith(isLoadingMore: true)); + + log( + '๐Ÿ“ฑ Loading categories - isActive: ${e.isActive}, forceRemote: ${e.forceRemote}', + ); + + final result = await _categoryRepository.getCategories( + page: 1, + limit: 50, + isActive: e.isActive, + search: e.search, + forceRemote: e.forceRemote, + ); + + await result.fold( + (failure) async { + emit( + state.copyWith( + isLoadingMore: false, + failureOptionCategory: optionOf(failure), + ), + ); + }, + (response) async { + final categories = [Category.all(), ...response.categories]; + + final totalPages = response.totalPages; + final hasReachedMax = categories.length < 50 || 1 >= totalPages; + + log( + 'โœ… Categories loaded: ${categories.length}, hasReachedMax: $hasReachedMax', + ); + + emit( + state.copyWith( + categories: categories, + page: 1, + hasReachedMax: hasReachedMax, + isLoadingMore: false, + failureOptionCategory: none(), + ), + ); + }, + ); + }, + loadMore: (e) async { + final currentState = state; + + // โŒ HAPUS pengecekan is! _Loaded karena state cuma 1 class doang + if (currentState.hasReachedMax || + _isLoadingMore || + currentState.isLoadingMore) { + log( + 'โน๏ธ Load more blocked - hasReachedMax: ${currentState.hasReachedMax}, isLoadingMore: $_isLoadingMore', + ); + return; + } + + _isLoadingMore = true; + emit(currentState.copyWith(isLoadingMore: true)); + + final nextPage = currentState.page + 1; // โœ… Ganti currentPage jadi page + log('๐Ÿ“„ Loading more categories - page: $nextPage'); + + try { + final result = await _categoryRepository.getCategories( + page: nextPage, + limit: 10, + isActive: true, + search: currentState.searchQuery, + ); + + await result.fold( + (failure) async { + log('โŒ Error loading more categories: $failure'); + emit(currentState.copyWith(isLoadingMore: false)); + }, + (response) async { + final newCategories = response.categories; + final totalPages = response.totalPages; + + // Prevent duplicate categories + final currentCategoryIds = currentState.categories + .map((c) => c.id) + .toSet(); + final filteredNewCategories = newCategories + .where( + (category) => !currentCategoryIds.contains(category.id), + ) + .toList(); + + final allCategories = List.from(currentState.categories) + ..addAll(filteredNewCategories); + + final hasReachedMax = + newCategories.length < 10 || nextPage >= totalPages; + + log( + 'โœ… More categories loaded: ${filteredNewCategories.length} new, total: ${allCategories.length}', + ); + + emit( + currentState.copyWith( + categories: allCategories, + hasReachedMax: hasReachedMax, + page: nextPage, // โœ… Update page + isLoadingMore: false, + ), + ); + }, + ); + } catch (e) { + log('โŒ Exception loading more categories: $e'); + emit(currentState.copyWith(isLoadingMore: false)); + } finally { + _isLoadingMore = false; + } + }, + refresh: (e) async { + final currentState = state; + bool isActive = true; + String? searchQuery = currentState.searchQuery; + + _isLoadingMore = false; + _searchDebounce?.cancel(); + + log('๐Ÿ”„ Refreshing categories'); + + // Clear local cache + _categoryRepository.clearCache(); + + add( + CategoryLoaderEvent.getCategories( + isActive: isActive, + search: searchQuery, + forceRemote: true, // Force remote refresh + ), + ); + }, + search: (e) async { + // Cancel previous search + _searchDebounce?.cancel(); + + // Debounce search for better UX + _searchDebounce = Timer(Duration(milliseconds: 300), () async { + emit(state.copyWith(isLoadingMore: true)); + _isLoadingMore = false; + + log('๐Ÿ” Searching categories: "${e.query}"'); + + final result = await _categoryRepository.getCategories( + page: 1, + limit: 20, // More results for search + isActive: e.isActive, + search: e.query, + ); + + await result.fold( + (failure) async { + log('โŒ Search error: $failure'); + emit( + state.copyWith( + isLoadingMore: false, + failureOptionCategory: optionOf(failure), + ), + ); + }, + (response) async { + final categories = [Category.all(), ...response.categories]; + final totalPages = response.totalPages; + final hasReachedMax = categories.length < 20 || 1 >= totalPages; + + log('โœ… Search results: ${categories.length} categories found'); + + emit( + state.copyWith( + categories: categories, + hasReachedMax: hasReachedMax, + page: 1, + isLoadingMore: false, + failureOptionCategory: none(), + searchQuery: e.query, + ), + ); + }, + ); + }); + }, + syncAll: (e) async { + emit(state.copyWith(isLoadingMore: true)); + + log('๐Ÿ”„ Starting full category sync...'); + + final result = await _categoryRepository.syncAllCategories(); + + await result.fold( + (failure) async { + log('โŒ Sync failed: $failure'); + emit( + state.copyWith( + isLoadingMore: false, + failureOptionCategory: optionOf(failure), + ), + ); + + // After sync error, try to load local data + Timer(Duration(seconds: 2), () { + add(const CategoryLoaderEvent.getCategories()); + }); + }, + (successMessage) async { + log('โœ… Sync completed: $successMessage'); + emit( + state.copyWith( + isLoadingMore: false, + failureOptionCategory: none(), + ), + ); + + // After successful sync, load the updated data + Timer(Duration(seconds: 1), () { + add(const CategoryLoaderEvent.getCategories()); + }); + }, + ); + }, + getAllCategories: (e) async { + try { + log('๐Ÿ“‹ Loading all categories for dropdown...'); + + // final categories = await _categoryRepository.getAllCategories(); + + // emit( + // state.copyWith( + // categories: categories, + // isLoadingMore: false, + // failureOptionCategory: none(), + // ), + // ); + // log('โœ… All categories loaded: ${categories.length}'); + } catch (e) { + log('โŒ Error loading all categories: $e'); + emit( + state.copyWith( + isLoadingMore: false, + failureOptionCategory: optionOf( + CategoryFailure.dynamicErrorMessage( + 'Gagal memuat semua kategori: $e', + ), + ), + ), + ); + } + }, + getDatabaseStats: (e) async { + try { + final stats = await _categoryRepository.getDatabaseStats(); + log('๐Ÿ“Š Category database stats retrieved: $stats'); + + // You can emit a special state here if needed for UI updates + // For now, just log the stats + } catch (e) { + log('โŒ Error getting category database stats: $e'); + } + }, + clearCache: (e) async { + log('๐Ÿงน Manually clearing category cache'); + _categoryRepository.clearCache(); + + // Refresh current data after cache clear + add(const CategoryLoaderEvent.refresh()); + }, + ); + } + + @override + Future close() { + _searchDebounce?.cancel(); + return super.close(); + } +} diff --git a/lib/application/category/category_loader/category_loader_bloc.freezed.dart b/lib/application/category/category_loader/category_loader_bloc.freezed.dart new file mode 100644 index 0000000..613dbae --- /dev/null +++ b/lib/application/category/category_loader/category_loader_bloc.freezed.dart @@ -0,0 +1,1666 @@ +// 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 'category_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 _$CategoryLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CategoryLoaderEventCopyWith<$Res> { + factory $CategoryLoaderEventCopyWith( + CategoryLoaderEvent value, + $Res Function(CategoryLoaderEvent) then, + ) = _$CategoryLoaderEventCopyWithImpl<$Res, CategoryLoaderEvent>; +} + +/// @nodoc +class _$CategoryLoaderEventCopyWithImpl<$Res, $Val extends CategoryLoaderEvent> + implements $CategoryLoaderEventCopyWith<$Res> { + _$CategoryLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetCategoriesImplCopyWith<$Res> { + factory _$$GetCategoriesImplCopyWith( + _$GetCategoriesImpl value, + $Res Function(_$GetCategoriesImpl) then, + ) = __$$GetCategoriesImplCopyWithImpl<$Res>; + @useResult + $Res call({bool isActive, String? search, bool forceRemote}); +} + +/// @nodoc +class __$$GetCategoriesImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$GetCategoriesImpl> + implements _$$GetCategoriesImplCopyWith<$Res> { + __$$GetCategoriesImplCopyWithImpl( + _$GetCategoriesImpl _value, + $Res Function(_$GetCategoriesImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? isActive = null, + Object? search = freezed, + Object? forceRemote = null, + }) { + return _then( + _$GetCategoriesImpl( + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + forceRemote: null == forceRemote + ? _value.forceRemote + : forceRemote // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$GetCategoriesImpl implements _GetCategories { + const _$GetCategoriesImpl({ + this.isActive = true, + this.search, + this.forceRemote = false, + }); + + @override + @JsonKey() + final bool isActive; + @override + final String? search; + @override + @JsonKey() + final bool forceRemote; + + @override + String toString() { + return 'CategoryLoaderEvent.getCategories(isActive: $isActive, search: $search, forceRemote: $forceRemote)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$GetCategoriesImpl && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + (identical(other.search, search) || other.search == search) && + (identical(other.forceRemote, forceRemote) || + other.forceRemote == forceRemote)); + } + + @override + int get hashCode => Object.hash(runtimeType, isActive, search, forceRemote); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$GetCategoriesImplCopyWith<_$GetCategoriesImpl> get copyWith => + __$$GetCategoriesImplCopyWithImpl<_$GetCategoriesImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return getCategories(isActive, this.search, forceRemote); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return getCategories?.call(isActive, this.search, forceRemote); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (getCategories != null) { + return getCategories(isActive, this.search, forceRemote); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return getCategories(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return getCategories?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (getCategories != null) { + return getCategories(this); + } + return orElse(); + } +} + +abstract class _GetCategories implements CategoryLoaderEvent { + const factory _GetCategories({ + final bool isActive, + final String? search, + final bool forceRemote, + }) = _$GetCategoriesImpl; + + bool get isActive; + String? get search; + bool get forceRemote; + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GetCategoriesImplCopyWith<_$GetCategoriesImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$LoadMoreImplCopyWith<$Res> { + factory _$$LoadMoreImplCopyWith( + _$LoadMoreImpl value, + $Res Function(_$LoadMoreImpl) then, + ) = __$$LoadMoreImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadMoreImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$LoadMoreImpl> + implements _$$LoadMoreImplCopyWith<$Res> { + __$$LoadMoreImplCopyWithImpl( + _$LoadMoreImpl _value, + $Res Function(_$LoadMoreImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadMoreImpl implements _LoadMore { + const _$LoadMoreImpl(); + + @override + String toString() { + return 'CategoryLoaderEvent.loadMore()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$LoadMoreImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return loadMore(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return loadMore?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return loadMore(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return loadMore?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + 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 CategoryLoaderEvent { + const factory _LoadMore() = _$LoadMoreImpl; +} + +/// @nodoc +abstract class _$$RefreshImplCopyWith<$Res> { + factory _$$RefreshImplCopyWith( + _$RefreshImpl value, + $Res Function(_$RefreshImpl) then, + ) = __$$RefreshImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$RefreshImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$RefreshImpl> + implements _$$RefreshImplCopyWith<$Res> { + __$$RefreshImplCopyWithImpl( + _$RefreshImpl _value, + $Res Function(_$RefreshImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$RefreshImpl implements _Refresh { + const _$RefreshImpl(); + + @override + String toString() { + return 'CategoryLoaderEvent.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(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return refresh(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return refresh?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return refresh(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return refresh?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + 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 CategoryLoaderEvent { + const factory _Refresh() = _$RefreshImpl; +} + +/// @nodoc +abstract class _$$SearchImplCopyWith<$Res> { + factory _$$SearchImplCopyWith( + _$SearchImpl value, + $Res Function(_$SearchImpl) then, + ) = __$$SearchImplCopyWithImpl<$Res>; + @useResult + $Res call({String query, bool isActive}); +} + +/// @nodoc +class __$$SearchImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$SearchImpl> + implements _$$SearchImplCopyWith<$Res> { + __$$SearchImplCopyWithImpl( + _$SearchImpl _value, + $Res Function(_$SearchImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? query = null, Object? isActive = null}) { + return _then( + _$SearchImpl( + query: null == query + ? _value.query + : query // ignore: cast_nullable_to_non_nullable + as String, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$SearchImpl implements _Search { + const _$SearchImpl({required this.query, this.isActive = true}); + + @override + final String query; + @override + @JsonKey() + final bool isActive; + + @override + String toString() { + return 'CategoryLoaderEvent.search(query: $query, isActive: $isActive)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SearchImpl && + (identical(other.query, query) || other.query == query) && + (identical(other.isActive, isActive) || + other.isActive == isActive)); + } + + @override + int get hashCode => Object.hash(runtimeType, query, isActive); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SearchImplCopyWith<_$SearchImpl> get copyWith => + __$$SearchImplCopyWithImpl<_$SearchImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return search(query, isActive); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return search?.call(query, isActive); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (search != null) { + return search(query, isActive); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return search(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return search?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (search != null) { + return search(this); + } + return orElse(); + } +} + +abstract class _Search implements CategoryLoaderEvent { + const factory _Search({required final String query, final bool isActive}) = + _$SearchImpl; + + String get query; + bool get isActive; + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SearchImplCopyWith<_$SearchImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SyncAllImplCopyWith<$Res> { + factory _$$SyncAllImplCopyWith( + _$SyncAllImpl value, + $Res Function(_$SyncAllImpl) then, + ) = __$$SyncAllImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$SyncAllImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$SyncAllImpl> + implements _$$SyncAllImplCopyWith<$Res> { + __$$SyncAllImplCopyWithImpl( + _$SyncAllImpl _value, + $Res Function(_$SyncAllImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$SyncAllImpl implements _SyncAll { + const _$SyncAllImpl(); + + @override + String toString() { + return 'CategoryLoaderEvent.syncAll()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$SyncAllImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return syncAll(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return syncAll?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (syncAll != null) { + return syncAll(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return syncAll(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return syncAll?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (syncAll != null) { + return syncAll(this); + } + return orElse(); + } +} + +abstract class _SyncAll implements CategoryLoaderEvent { + const factory _SyncAll() = _$SyncAllImpl; +} + +/// @nodoc +abstract class _$$GetAllCategoriesImplCopyWith<$Res> { + factory _$$GetAllCategoriesImplCopyWith( + _$GetAllCategoriesImpl value, + $Res Function(_$GetAllCategoriesImpl) then, + ) = __$$GetAllCategoriesImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetAllCategoriesImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$GetAllCategoriesImpl> + implements _$$GetAllCategoriesImplCopyWith<$Res> { + __$$GetAllCategoriesImplCopyWithImpl( + _$GetAllCategoriesImpl _value, + $Res Function(_$GetAllCategoriesImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetAllCategoriesImpl implements _GetAllCategories { + const _$GetAllCategoriesImpl(); + + @override + String toString() { + return 'CategoryLoaderEvent.getAllCategories()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetAllCategoriesImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return getAllCategories(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return getAllCategories?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (getAllCategories != null) { + return getAllCategories(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return getAllCategories(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return getAllCategories?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + TResult Function(_GetDatabaseStats value)? getDatabaseStats, + TResult Function(_ClearCache value)? clearCache, + required TResult orElse(), + }) { + if (getAllCategories != null) { + return getAllCategories(this); + } + return orElse(); + } +} + +abstract class _GetAllCategories implements CategoryLoaderEvent { + const factory _GetAllCategories() = _$GetAllCategoriesImpl; +} + +/// @nodoc +abstract class _$$GetDatabaseStatsImplCopyWith<$Res> { + factory _$$GetDatabaseStatsImplCopyWith( + _$GetDatabaseStatsImpl value, + $Res Function(_$GetDatabaseStatsImpl) then, + ) = __$$GetDatabaseStatsImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetDatabaseStatsImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$GetDatabaseStatsImpl> + implements _$$GetDatabaseStatsImplCopyWith<$Res> { + __$$GetDatabaseStatsImplCopyWithImpl( + _$GetDatabaseStatsImpl _value, + $Res Function(_$GetDatabaseStatsImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetDatabaseStatsImpl implements _GetDatabaseStats { + const _$GetDatabaseStatsImpl(); + + @override + String toString() { + return 'CategoryLoaderEvent.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(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return getDatabaseStats(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return getDatabaseStats?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (getDatabaseStats != null) { + return getDatabaseStats(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return getDatabaseStats(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return getDatabaseStats?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + 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 CategoryLoaderEvent { + const factory _GetDatabaseStats() = _$GetDatabaseStatsImpl; +} + +/// @nodoc +abstract class _$$ClearCacheImplCopyWith<$Res> { + factory _$$ClearCacheImplCopyWith( + _$ClearCacheImpl value, + $Res Function(_$ClearCacheImpl) then, + ) = __$$ClearCacheImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$ClearCacheImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$ClearCacheImpl> + implements _$$ClearCacheImplCopyWith<$Res> { + __$$ClearCacheImplCopyWithImpl( + _$ClearCacheImpl _value, + $Res Function(_$ClearCacheImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$ClearCacheImpl implements _ClearCache { + const _$ClearCacheImpl(); + + @override + String toString() { + return 'CategoryLoaderEvent.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(bool isActive, String? search, bool forceRemote) + getCategories, + required TResult Function() loadMore, + required TResult Function() refresh, + required TResult Function(String query, bool isActive) search, + required TResult Function() syncAll, + required TResult Function() getAllCategories, + required TResult Function() getDatabaseStats, + required TResult Function() clearCache, + }) { + return clearCache(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult? Function()? loadMore, + TResult? Function()? refresh, + TResult? Function(String query, bool isActive)? search, + TResult? Function()? syncAll, + TResult? Function()? getAllCategories, + TResult? Function()? getDatabaseStats, + TResult? Function()? clearCache, + }) { + return clearCache?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(bool isActive, String? search, bool forceRemote)? + getCategories, + TResult Function()? loadMore, + TResult Function()? refresh, + TResult Function(String query, bool isActive)? search, + TResult Function()? syncAll, + TResult Function()? getAllCategories, + TResult Function()? getDatabaseStats, + TResult Function()? clearCache, + required TResult orElse(), + }) { + if (clearCache != null) { + return clearCache(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetCategories value) getCategories, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + required TResult Function(_Search value) search, + required TResult Function(_SyncAll value) syncAll, + required TResult Function(_GetAllCategories value) getAllCategories, + required TResult Function(_GetDatabaseStats value) getDatabaseStats, + required TResult Function(_ClearCache value) clearCache, + }) { + return clearCache(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetCategories value)? getCategories, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + TResult? Function(_Search value)? search, + TResult? Function(_SyncAll value)? syncAll, + TResult? Function(_GetAllCategories value)? getAllCategories, + TResult? Function(_GetDatabaseStats value)? getDatabaseStats, + TResult? Function(_ClearCache value)? clearCache, + }) { + return clearCache?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetCategories value)? getCategories, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + TResult Function(_Search value)? search, + TResult Function(_SyncAll value)? syncAll, + TResult Function(_GetAllCategories value)? getAllCategories, + 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 CategoryLoaderEvent { + const factory _ClearCache() = _$ClearCacheImpl; +} + +/// @nodoc +mixin _$CategoryLoaderState { + List get categories => throw _privateConstructorUsedError; + Option get failureOptionCategory => + throw _privateConstructorUsedError; + bool get hasReachedMax => throw _privateConstructorUsedError; + int get page => throw _privateConstructorUsedError; + bool get isLoadingMore => throw _privateConstructorUsedError; + String? get searchQuery => throw _privateConstructorUsedError; + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CategoryLoaderStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CategoryLoaderStateCopyWith<$Res> { + factory $CategoryLoaderStateCopyWith( + CategoryLoaderState value, + $Res Function(CategoryLoaderState) then, + ) = _$CategoryLoaderStateCopyWithImpl<$Res, CategoryLoaderState>; + @useResult + $Res call({ + List categories, + Option failureOptionCategory, + bool hasReachedMax, + int page, + bool isLoadingMore, + String? searchQuery, + }); +} + +/// @nodoc +class _$CategoryLoaderStateCopyWithImpl<$Res, $Val extends CategoryLoaderState> + implements $CategoryLoaderStateCopyWith<$Res> { + _$CategoryLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categories = null, + Object? failureOptionCategory = null, + Object? hasReachedMax = null, + Object? page = null, + Object? isLoadingMore = null, + Object? searchQuery = freezed, + }) { + return _then( + _value.copyWith( + categories: null == categories + ? _value.categories + : categories // ignore: cast_nullable_to_non_nullable + as List, + failureOptionCategory: null == failureOptionCategory + ? _value.failureOptionCategory + : failureOptionCategory // 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?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$CategoryLoaderStateImplCopyWith<$Res> + implements $CategoryLoaderStateCopyWith<$Res> { + factory _$$CategoryLoaderStateImplCopyWith( + _$CategoryLoaderStateImpl value, + $Res Function(_$CategoryLoaderStateImpl) then, + ) = __$$CategoryLoaderStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List categories, + Option failureOptionCategory, + bool hasReachedMax, + int page, + bool isLoadingMore, + String? searchQuery, + }); +} + +/// @nodoc +class __$$CategoryLoaderStateImplCopyWithImpl<$Res> + extends _$CategoryLoaderStateCopyWithImpl<$Res, _$CategoryLoaderStateImpl> + implements _$$CategoryLoaderStateImplCopyWith<$Res> { + __$$CategoryLoaderStateImplCopyWithImpl( + _$CategoryLoaderStateImpl _value, + $Res Function(_$CategoryLoaderStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categories = null, + Object? failureOptionCategory = null, + Object? hasReachedMax = null, + Object? page = null, + Object? isLoadingMore = null, + Object? searchQuery = freezed, + }) { + return _then( + _$CategoryLoaderStateImpl( + categories: null == categories + ? _value._categories + : categories // ignore: cast_nullable_to_non_nullable + as List, + failureOptionCategory: null == failureOptionCategory + ? _value.failureOptionCategory + : failureOptionCategory // 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?, + ), + ); + } +} + +/// @nodoc + +class _$CategoryLoaderStateImpl implements _CategoryLoaderState { + _$CategoryLoaderStateImpl({ + required final List categories, + required this.failureOptionCategory, + this.hasReachedMax = false, + this.page = 1, + this.isLoadingMore = false, + this.searchQuery, + }) : _categories = categories; + + final List _categories; + @override + List get categories { + if (_categories is EqualUnmodifiableListView) return _categories; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_categories); + } + + @override + final Option failureOptionCategory; + @override + @JsonKey() + final bool hasReachedMax; + @override + @JsonKey() + final int page; + @override + @JsonKey() + final bool isLoadingMore; + @override + final String? searchQuery; + + @override + String toString() { + return 'CategoryLoaderState(categories: $categories, failureOptionCategory: $failureOptionCategory, hasReachedMax: $hasReachedMax, page: $page, isLoadingMore: $isLoadingMore, searchQuery: $searchQuery)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CategoryLoaderStateImpl && + const DeepCollectionEquality().equals( + other._categories, + _categories, + ) && + (identical(other.failureOptionCategory, failureOptionCategory) || + other.failureOptionCategory == failureOptionCategory) && + (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)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_categories), + failureOptionCategory, + hasReachedMax, + page, + isLoadingMore, + searchQuery, + ); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CategoryLoaderStateImplCopyWith<_$CategoryLoaderStateImpl> get copyWith => + __$$CategoryLoaderStateImplCopyWithImpl<_$CategoryLoaderStateImpl>( + this, + _$identity, + ); +} + +abstract class _CategoryLoaderState implements CategoryLoaderState { + factory _CategoryLoaderState({ + required final List categories, + required final Option failureOptionCategory, + final bool hasReachedMax, + final int page, + final bool isLoadingMore, + final String? searchQuery, + }) = _$CategoryLoaderStateImpl; + + @override + List get categories; + @override + Option get failureOptionCategory; + @override + bool get hasReachedMax; + @override + int get page; + @override + bool get isLoadingMore; + @override + String? get searchQuery; + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CategoryLoaderStateImplCopyWith<_$CategoryLoaderStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/category/category_loader/category_loader_event.dart b/lib/application/category/category_loader/category_loader_event.dart new file mode 100644 index 0000000..88f84dd --- /dev/null +++ b/lib/application/category/category_loader/category_loader_event.dart @@ -0,0 +1,27 @@ +part of 'category_loader_bloc.dart'; + +@freezed +class CategoryLoaderEvent with _$CategoryLoaderEvent { + const factory CategoryLoaderEvent.getCategories({ + @Default(true) bool isActive, + String? search, + @Default(false) bool forceRemote, + }) = _GetCategories; + + const factory CategoryLoaderEvent.loadMore() = _LoadMore; + + const factory CategoryLoaderEvent.refresh() = _Refresh; + + const factory CategoryLoaderEvent.search({ + required String query, + @Default(true) bool isActive, + }) = _Search; + + const factory CategoryLoaderEvent.syncAll() = _SyncAll; + + const factory CategoryLoaderEvent.getAllCategories() = _GetAllCategories; + + const factory CategoryLoaderEvent.getDatabaseStats() = _GetDatabaseStats; + + const factory CategoryLoaderEvent.clearCache() = _ClearCache; +} diff --git a/lib/application/category/category_loader/category_loader_state.dart b/lib/application/category/category_loader/category_loader_state.dart new file mode 100644 index 0000000..55edfad --- /dev/null +++ b/lib/application/category/category_loader/category_loader_state.dart @@ -0,0 +1,16 @@ +part of 'category_loader_bloc.dart'; + +@freezed +class CategoryLoaderState with _$CategoryLoaderState { + factory CategoryLoaderState({ + required List categories, + required Option failureOptionCategory, + @Default(false) bool hasReachedMax, + @Default(1) int page, + @Default(false) bool isLoadingMore, + String? searchQuery, + }) = _CategoryLoaderState; + + factory CategoryLoaderState.initial() => + CategoryLoaderState(categories: [], failureOptionCategory: none()); +} diff --git a/lib/common/constant/app_constant.dart b/lib/common/constant/app_constant.dart index 8705613..3e05aae 100644 --- a/lib/common/constant/app_constant.dart +++ b/lib/common/constant/app_constant.dart @@ -1,3 +1,5 @@ class AppConstant { static const String appName = "Apskel POS"; + + static const int cacheExpire = 10; // in minutes } diff --git a/lib/common/database/database_helper.dart b/lib/common/database/database_helper.dart new file mode 100644 index 0000000..45bc846 --- /dev/null +++ b/lib/common/database/database_helper.dart @@ -0,0 +1,158 @@ +import 'dart:async'; +import 'package:sqflite/sqflite.dart'; +import 'package:path/path.dart'; + +class DatabaseHelper { + static Database? _database; + + Future get database async { + _database ??= await _initDatabase(); + return _database!; + } + + Future _initDatabase() async { + String path = join(await getDatabasesPath(), 'db_pos.db'); + + return await openDatabase( + path, + version: 1, // Updated version for categories table + onCreate: _onCreate, + onUpgrade: _onUpgrade, + ); + } + + Future _onCreate(Database db, int version) async { + // Products table + await db.execute(''' + CREATE TABLE products ( + id TEXT PRIMARY KEY, + organization_id TEXT, + category_id TEXT, + sku TEXT, + name TEXT, + description TEXT, + price INTEGER, + cost INTEGER, + business_type TEXT, + image_url TEXT, + printer_type TEXT, + metadata TEXT, + is_active INTEGER, + created_at TEXT, + updated_at TEXT + ) + '''); + + // Product Variants table + await db.execute(''' + CREATE TABLE product_variants ( + id TEXT PRIMARY KEY, + product_id TEXT, + name TEXT, + price_modifier INTEGER, + cost INTEGER, + metadata TEXT, + created_at TEXT, + updated_at TEXT, + FOREIGN KEY (product_id) REFERENCES products (id) ON DELETE CASCADE + ) + '''); + + // Categories table - NEW + await db.execute(''' + CREATE TABLE categories ( + id TEXT PRIMARY KEY, + organization_id TEXT, + name TEXT NOT NULL, + description TEXT, + business_type TEXT, + metadata TEXT, + is_active INTEGER DEFAULT 1, + created_at TEXT, + updated_at TEXT + ) + '''); + + // Printer table + await db.execute(''' + CREATE TABLE printers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT UNIQUE NOT NULL, + name TEXT NOT NULL, + address TEXT, + paper TEXT, + type TEXT, + created_at TEXT, + updated_at TEXT + ) + '''); + + // Create indexes for better performance + await db.execute( + 'CREATE INDEX idx_products_category_id ON products(category_id)', + ); + await db.execute('CREATE INDEX idx_products_name ON products(name)'); + await db.execute('CREATE INDEX idx_products_sku ON products(sku)'); + await db.execute('CREATE INDEX idx_categories_name ON categories(name)'); + await db.execute( + 'CREATE INDEX idx_categories_organization_id ON categories(organization_id)', + ); + await db.execute( + 'CREATE INDEX idx_categories_is_active ON categories(is_active)', + ); + await db.execute('CREATE INDEX idx_printers_code ON printers(code)'); + await db.execute('CREATE INDEX idx_printers_type ON printers(type)'); + } + + Future _onUpgrade(Database db, int oldVersion, int newVersion) async { + if (oldVersion < 2) { + // Add printer table in version 2 + await db.execute(''' + CREATE TABLE printers ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + code TEXT UNIQUE NOT NULL, + name TEXT NOT NULL, + address TEXT, + paper TEXT, + type TEXT, + created_at TEXT, + updated_at TEXT + ) + '''); + + await db.execute('CREATE INDEX idx_printers_code ON printers(code)'); + await db.execute('CREATE INDEX idx_printers_type ON printers(type)'); + } + + if (oldVersion < 3) { + // Add categories table in version 3 + await db.execute(''' + CREATE TABLE categories ( + id TEXT PRIMARY KEY, + organization_id TEXT, + name TEXT NOT NULL, + description TEXT, + business_type TEXT, + metadata TEXT, + is_active INTEGER DEFAULT 1, + created_at TEXT, + updated_at TEXT + ) + '''); + + await db.execute('CREATE INDEX idx_categories_name ON categories(name)'); + await db.execute( + 'CREATE INDEX idx_categories_organization_id ON categories(organization_id)', + ); + await db.execute( + 'CREATE INDEX idx_categories_is_active ON categories(is_active)', + ); + } + } + + Future close() async { + final db = await database; + await db.close(); + _database = null; + } +} diff --git a/lib/common/di/di_database.dart b/lib/common/di/di_database.dart new file mode 100644 index 0000000..72467ce --- /dev/null +++ b/lib/common/di/di_database.dart @@ -0,0 +1,9 @@ +import 'package:injectable/injectable.dart'; + +import '../database/database_helper.dart'; + +@module +abstract class DatabaseDi { + @singleton + DatabaseHelper get databaseHelper => DatabaseHelper(); +} diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart index 7bb6fc8..cbba024 100644 --- a/lib/common/url/api_path.dart +++ b/lib/common/url/api_path.dart @@ -1,4 +1,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'; } diff --git a/lib/domain/category/category.dart b/lib/domain/category/category.dart new file mode 100644 index 0000000..efc8f07 --- /dev/null +++ b/lib/domain/category/category.dart @@ -0,0 +1,10 @@ +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/api/api_failure.dart'; + +part 'category.freezed.dart'; + +part 'entities/category_entity.dart'; +part 'failures/category_failure.dart'; +part 'repositories/i_category_repository.dart'; diff --git a/lib/domain/category/category.freezed.dart b/lib/domain/category/category.freezed.dart new file mode 100644 index 0000000..d51e2d4 --- /dev/null +++ b/lib/domain/category/category.freezed.dart @@ -0,0 +1,1410 @@ +// 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 'category.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 _$ListCategory { + List get categories => 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 ListCategory + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ListCategoryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ListCategoryCopyWith<$Res> { + factory $ListCategoryCopyWith( + ListCategory value, + $Res Function(ListCategory) then, + ) = _$ListCategoryCopyWithImpl<$Res, ListCategory>; + @useResult + $Res call({ + List categories, + int totalCount, + int page, + int limit, + int totalPages, + }); +} + +/// @nodoc +class _$ListCategoryCopyWithImpl<$Res, $Val extends ListCategory> + implements $ListCategoryCopyWith<$Res> { + _$ListCategoryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ListCategory + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categories = null, + Object? totalCount = null, + Object? page = null, + Object? limit = null, + Object? totalPages = null, + }) { + return _then( + _value.copyWith( + categories: null == categories + ? _value.categories + : categories // 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 _$$ListCategoryImplCopyWith<$Res> + implements $ListCategoryCopyWith<$Res> { + factory _$$ListCategoryImplCopyWith( + _$ListCategoryImpl value, + $Res Function(_$ListCategoryImpl) then, + ) = __$$ListCategoryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List categories, + int totalCount, + int page, + int limit, + int totalPages, + }); +} + +/// @nodoc +class __$$ListCategoryImplCopyWithImpl<$Res> + extends _$ListCategoryCopyWithImpl<$Res, _$ListCategoryImpl> + implements _$$ListCategoryImplCopyWith<$Res> { + __$$ListCategoryImplCopyWithImpl( + _$ListCategoryImpl _value, + $Res Function(_$ListCategoryImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ListCategory + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categories = null, + Object? totalCount = null, + Object? page = null, + Object? limit = null, + Object? totalPages = null, + }) { + return _then( + _$ListCategoryImpl( + categories: null == categories + ? _value._categories + : categories // 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 _$ListCategoryImpl implements _ListCategory { + const _$ListCategoryImpl({ + required final List categories, + required this.totalCount, + required this.page, + required this.limit, + required this.totalPages, + }) : _categories = categories; + + final List _categories; + @override + List get categories { + if (_categories is EqualUnmodifiableListView) return _categories; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_categories); + } + + @override + final int totalCount; + @override + final int page; + @override + final int limit; + @override + final int totalPages; + + @override + String toString() { + return 'ListCategory(categories: $categories, totalCount: $totalCount, page: $page, limit: $limit, totalPages: $totalPages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ListCategoryImpl && + const DeepCollectionEquality().equals( + other._categories, + _categories, + ) && + (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(_categories), + totalCount, + page, + limit, + totalPages, + ); + + /// Create a copy of ListCategory + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ListCategoryImplCopyWith<_$ListCategoryImpl> get copyWith => + __$$ListCategoryImplCopyWithImpl<_$ListCategoryImpl>(this, _$identity); +} + +abstract class _ListCategory implements ListCategory { + const factory _ListCategory({ + required final List categories, + required final int totalCount, + required final int page, + required final int limit, + required final int totalPages, + }) = _$ListCategoryImpl; + + @override + List get categories; + @override + int get totalCount; + @override + int get page; + @override + int get limit; + @override + int get totalPages; + + /// Create a copy of ListCategory + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ListCategoryImplCopyWith<_$ListCategoryImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$Category { + String get id => throw _privateConstructorUsedError; + String get organizationId => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get businessType => throw _privateConstructorUsedError; + int get order => throw _privateConstructorUsedError; + Map get metadata => throw _privateConstructorUsedError; + String get createdAt => throw _privateConstructorUsedError; + String get updatedAt => throw _privateConstructorUsedError; + + /// Create a copy of Category + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CategoryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CategoryCopyWith<$Res> { + factory $CategoryCopyWith(Category value, $Res Function(Category) then) = + _$CategoryCopyWithImpl<$Res, Category>; + @useResult + $Res call({ + String id, + String organizationId, + String name, + String description, + String businessType, + int order, + Map metadata, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class _$CategoryCopyWithImpl<$Res, $Val extends Category> + implements $CategoryCopyWith<$Res> { + _$CategoryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Category + /// 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? name = null, + Object? description = null, + Object? businessType = null, + Object? order = 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, + organizationId: null == organizationId + ? _value.organizationId + : organizationId // 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, + businessType: null == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String, + order: null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as int, + 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 _$$CategoryImplCopyWith<$Res> + implements $CategoryCopyWith<$Res> { + factory _$$CategoryImplCopyWith( + _$CategoryImpl value, + $Res Function(_$CategoryImpl) then, + ) = __$$CategoryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String organizationId, + String name, + String description, + String businessType, + int order, + Map metadata, + String createdAt, + String updatedAt, + }); +} + +/// @nodoc +class __$$CategoryImplCopyWithImpl<$Res> + extends _$CategoryCopyWithImpl<$Res, _$CategoryImpl> + implements _$$CategoryImplCopyWith<$Res> { + __$$CategoryImplCopyWithImpl( + _$CategoryImpl _value, + $Res Function(_$CategoryImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Category + /// 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? name = null, + Object? description = null, + Object? businessType = null, + Object? order = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _$CategoryImpl( + 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, + 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, + businessType: null == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String, + order: null == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as int, + 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 _$CategoryImpl implements _Category { + const _$CategoryImpl({ + required this.id, + required this.organizationId, + required this.name, + required this.description, + required this.businessType, + required this.order, + required final Map metadata, + required this.createdAt, + required this.updatedAt, + }) : _metadata = metadata; + + @override + final String id; + @override + final String organizationId; + @override + final String name; + @override + final String description; + @override + final String businessType; + @override + final int order; + 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() { + return 'Category(id: $id, organizationId: $organizationId, name: $name, description: $description, businessType: $businessType, order: $order, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CategoryImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.organizationId, organizationId) || + other.organizationId == organizationId) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.businessType, businessType) || + other.businessType == businessType) && + (identical(other.order, order) || other.order == order) && + 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, + organizationId, + name, + description, + businessType, + order, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of Category + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CategoryImplCopyWith<_$CategoryImpl> get copyWith => + __$$CategoryImplCopyWithImpl<_$CategoryImpl>(this, _$identity); +} + +abstract class _Category implements Category { + const factory _Category({ + required final String id, + required final String organizationId, + required final String name, + required final String description, + required final String businessType, + required final int order, + required final Map metadata, + required final String createdAt, + required final String updatedAt, + }) = _$CategoryImpl; + + @override + String get id; + @override + String get organizationId; + @override + String get name; + @override + String get description; + @override + String get businessType; + @override + int get order; + @override + Map get metadata; + @override + String get createdAt; + @override + String get updatedAt; + + /// Create a copy of Category + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CategoryImplCopyWith<_$CategoryImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$CategoryFailure { + @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 $CategoryFailureCopyWith<$Res> { + factory $CategoryFailureCopyWith( + CategoryFailure value, + $Res Function(CategoryFailure) then, + ) = _$CategoryFailureCopyWithImpl<$Res, CategoryFailure>; +} + +/// @nodoc +class _$CategoryFailureCopyWithImpl<$Res, $Val extends CategoryFailure> + implements $CategoryFailureCopyWith<$Res> { + _$CategoryFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryFailure + /// 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 _$CategoryFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryFailure + /// 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 CategoryFailure + /// 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 implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString() { + return 'CategoryFailure.serverError(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 CategoryFailure + /// 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 CategoryFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of CategoryFailure + /// 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 _$CategoryFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString() { + return 'CategoryFailure.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 CategoryFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$EmptyImplCopyWith<$Res> { + factory _$$EmptyImplCopyWith( + _$EmptyImpl value, + $Res Function(_$EmptyImpl) then, + ) = __$$EmptyImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$EmptyImplCopyWithImpl<$Res> + extends _$CategoryFailureCopyWithImpl<$Res, _$EmptyImpl> + implements _$$EmptyImplCopyWith<$Res> { + __$$EmptyImplCopyWithImpl( + _$EmptyImpl _value, + $Res Function(_$EmptyImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$EmptyImpl implements _Empty { + const _$EmptyImpl(); + + @override + String toString() { + return 'CategoryFailure.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 CategoryFailure { + 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 _$CategoryFailureCopyWithImpl<$Res, _$LocalStorageErrorImpl> + implements _$$LocalStorageErrorImplCopyWith<$Res> { + __$$LocalStorageErrorImplCopyWithImpl( + _$LocalStorageErrorImpl _value, + $Res Function(_$LocalStorageErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryFailure + /// 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 implements _LocalStorageError { + const _$LocalStorageErrorImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'CategoryFailure.localStorageError(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 CategoryFailure + /// 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 CategoryFailure { + const factory _LocalStorageError(final String erroMessage) = + _$LocalStorageErrorImpl; + + String get erroMessage; + + /// Create a copy of CategoryFailure + /// 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 _$CategoryFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryFailure + /// 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 implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'CategoryFailure.dynamicErrorMessage(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 CategoryFailure + /// 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 CategoryFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of CategoryFailure + /// 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/category/entities/category_entity.dart b/lib/domain/category/entities/category_entity.dart new file mode 100644 index 0000000..6ede5d8 --- /dev/null +++ b/lib/domain/category/entities/category_entity.dart @@ -0,0 +1,59 @@ +part of '../category.dart'; + +@freezed +class ListCategory with _$ListCategory { + const factory ListCategory({ + required List categories, + required int totalCount, + required int page, + required int limit, + required int totalPages, + }) = _ListCategory; + + factory ListCategory.empty() => const ListCategory( + categories: [], + totalCount: 0, + page: 0, + limit: 0, + totalPages: 0, + ); +} + +@freezed +class Category with _$Category { + const factory Category({ + required String id, + required String organizationId, + required String name, + required String description, + required String businessType, + required int order, + required Map metadata, + required String createdAt, + required String updatedAt, + }) = _Category; + + factory Category.empty() => const Category( + id: '', + organizationId: '', + name: '', + description: '', + businessType: '', + order: 0, + metadata: {}, + createdAt: '', + updatedAt: '', + ); + + factory Category.all() => const Category( + id: 'all', + organizationId: '', + name: 'Semua', + businessType: 'restaurant', + metadata: {}, + createdAt: '', + updatedAt: '', + description: '', + order: 1, + ); +} diff --git a/lib/domain/category/failures/category_failure.dart b/lib/domain/category/failures/category_failure.dart new file mode 100644 index 0000000..36d8058 --- /dev/null +++ b/lib/domain/category/failures/category_failure.dart @@ -0,0 +1,12 @@ +part of '../category.dart'; + +@freezed +sealed class CategoryFailure with _$CategoryFailure { + const factory CategoryFailure.serverError(ApiFailure failure) = _ServerError; + const factory CategoryFailure.unexpectedError() = _UnexpectedError; + const factory CategoryFailure.empty() = _Empty; + const factory CategoryFailure.localStorageError(String erroMessage) = + _LocalStorageError; + const factory CategoryFailure.dynamicErrorMessage(String erroMessage) = + _DynamicErrorMessage; +} diff --git a/lib/domain/category/repositories/i_category_repository.dart b/lib/domain/category/repositories/i_category_repository.dart new file mode 100644 index 0000000..957ffe7 --- /dev/null +++ b/lib/domain/category/repositories/i_category_repository.dart @@ -0,0 +1,26 @@ +part of '../category.dart'; + +abstract class ICategoryRepository { + Future> getCategories({ + int page = 1, + int limit = 10, + bool isActive = true, + String? search, + bool forceRemote = false, + }); + + Future> getCategoryById(String id); + + Future> syncAllCategories(); + + Future> refreshCategories({ + bool isActive = true, + String? search, + }); + + Future hasLocalCategories(); + + Future>> getDatabaseStats(); + + void clearCache(); +} diff --git a/lib/infrastructure/category/category_dtos.dart b/lib/infrastructure/category/category_dtos.dart new file mode 100644 index 0000000..2c01d91 --- /dev/null +++ b/lib/infrastructure/category/category_dtos.dart @@ -0,0 +1,10 @@ +import 'dart:convert'; + +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../domain/category/category.dart'; + +part 'category_dtos.freezed.dart'; +part 'category_dtos.g.dart'; + +part 'dtos/category_dto.dart'; diff --git a/lib/infrastructure/category/category_dtos.freezed.dart b/lib/infrastructure/category/category_dtos.freezed.dart new file mode 100644 index 0000000..d4ad9d9 --- /dev/null +++ b/lib/infrastructure/category/category_dtos.freezed.dart @@ -0,0 +1,675 @@ +// 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 'category_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', +); + +ListCategoryDto _$ListCategoryDtoFromJson(Map json) { + return _ListCategoryDto.fromJson(json); +} + +/// @nodoc +mixin _$ListCategoryDto { + @JsonKey(name: "categories") + List? get categories => 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 ListCategoryDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ListCategoryDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ListCategoryDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ListCategoryDtoCopyWith<$Res> { + factory $ListCategoryDtoCopyWith( + ListCategoryDto value, + $Res Function(ListCategoryDto) then, + ) = _$ListCategoryDtoCopyWithImpl<$Res, ListCategoryDto>; + @useResult + $Res call({ + @JsonKey(name: "categories") List? categories, + @JsonKey(name: "total_count") int? totalCount, + @JsonKey(name: "page") int? page, + @JsonKey(name: "limit") int? limit, + @JsonKey(name: "total_pages") int? totalPages, + }); +} + +/// @nodoc +class _$ListCategoryDtoCopyWithImpl<$Res, $Val extends ListCategoryDto> + implements $ListCategoryDtoCopyWith<$Res> { + _$ListCategoryDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ListCategoryDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categories = freezed, + Object? totalCount = freezed, + Object? page = freezed, + Object? limit = freezed, + Object? totalPages = freezed, + }) { + return _then( + _value.copyWith( + categories: freezed == categories + ? _value.categories + : categories // ignore: cast_nullable_to_non_nullable + as List?, + totalCount: freezed == totalCount + ? _value.totalCount + : totalCount // ignore: cast_nullable_to_non_nullable + as int?, + page: freezed == page + ? _value.page + : page // ignore: cast_nullable_to_non_nullable + as int?, + limit: freezed == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as int?, + totalPages: freezed == totalPages + ? _value.totalPages + : totalPages // ignore: cast_nullable_to_non_nullable + as int?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ListCategoryDtoImplCopyWith<$Res> + implements $ListCategoryDtoCopyWith<$Res> { + factory _$$ListCategoryDtoImplCopyWith( + _$ListCategoryDtoImpl value, + $Res Function(_$ListCategoryDtoImpl) then, + ) = __$$ListCategoryDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: "categories") List? categories, + @JsonKey(name: "total_count") int? totalCount, + @JsonKey(name: "page") int? page, + @JsonKey(name: "limit") int? limit, + @JsonKey(name: "total_pages") int? totalPages, + }); +} + +/// @nodoc +class __$$ListCategoryDtoImplCopyWithImpl<$Res> + extends _$ListCategoryDtoCopyWithImpl<$Res, _$ListCategoryDtoImpl> + implements _$$ListCategoryDtoImplCopyWith<$Res> { + __$$ListCategoryDtoImplCopyWithImpl( + _$ListCategoryDtoImpl _value, + $Res Function(_$ListCategoryDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ListCategoryDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categories = freezed, + Object? totalCount = freezed, + Object? page = freezed, + Object? limit = freezed, + Object? totalPages = freezed, + }) { + return _then( + _$ListCategoryDtoImpl( + categories: freezed == categories + ? _value._categories + : categories // ignore: cast_nullable_to_non_nullable + as List?, + totalCount: freezed == totalCount + ? _value.totalCount + : totalCount // ignore: cast_nullable_to_non_nullable + as int?, + page: freezed == page + ? _value.page + : page // ignore: cast_nullable_to_non_nullable + as int?, + limit: freezed == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as int?, + totalPages: freezed == totalPages + ? _value.totalPages + : totalPages // ignore: cast_nullable_to_non_nullable + as int?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ListCategoryDtoImpl extends _ListCategoryDto { + const _$ListCategoryDtoImpl({ + @JsonKey(name: "categories") final List? categories, + @JsonKey(name: "total_count") this.totalCount, + @JsonKey(name: "page") this.page, + @JsonKey(name: "limit") this.limit, + @JsonKey(name: "total_pages") this.totalPages, + }) : _categories = categories, + super._(); + + factory _$ListCategoryDtoImpl.fromJson(Map json) => + _$$ListCategoryDtoImplFromJson(json); + + final List? _categories; + @override + @JsonKey(name: "categories") + List? get categories { + final value = _categories; + if (value == null) return null; + if (_categories is EqualUnmodifiableListView) return _categories; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @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 'ListCategoryDto(categories: $categories, totalCount: $totalCount, page: $page, limit: $limit, totalPages: $totalPages)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ListCategoryDtoImpl && + const DeepCollectionEquality().equals( + other._categories, + _categories, + ) && + (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(_categories), + totalCount, + page, + limit, + totalPages, + ); + + /// Create a copy of ListCategoryDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ListCategoryDtoImplCopyWith<_$ListCategoryDtoImpl> get copyWith => + __$$ListCategoryDtoImplCopyWithImpl<_$ListCategoryDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$ListCategoryDtoImplToJson(this); + } +} + +abstract class _ListCategoryDto extends ListCategoryDto { + const factory _ListCategoryDto({ + @JsonKey(name: "categories") final List? categories, + @JsonKey(name: "total_count") final int? totalCount, + @JsonKey(name: "page") final int? page, + @JsonKey(name: "limit") final int? limit, + @JsonKey(name: "total_pages") final int? totalPages, + }) = _$ListCategoryDtoImpl; + const _ListCategoryDto._() : super._(); + + factory _ListCategoryDto.fromJson(Map json) = + _$ListCategoryDtoImpl.fromJson; + + @override + @JsonKey(name: "categories") + List? get categories; + @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 ListCategoryDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ListCategoryDtoImplCopyWith<_$ListCategoryDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +CategoryDto _$CategoryDtoFromJson(Map json) { + return _CategoryDto.fromJson(json); +} + +/// @nodoc +mixin _$CategoryDto { + @JsonKey(name: "id") + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: "organization_id") + String? get organizationId => throw _privateConstructorUsedError; + @JsonKey(name: "name") + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: "description") + String? get description => throw _privateConstructorUsedError; + @JsonKey(name: "business_type") + String? get businessType => throw _privateConstructorUsedError; + @JsonKey(name: "order") + int? get order => 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 CategoryDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of CategoryDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $CategoryDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CategoryDtoCopyWith<$Res> { + factory $CategoryDtoCopyWith( + CategoryDto value, + $Res Function(CategoryDto) then, + ) = _$CategoryDtoCopyWithImpl<$Res, CategoryDto>; + @useResult + $Res call({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "organization_id") String? organizationId, + @JsonKey(name: "name") String? name, + @JsonKey(name: "description") String? description, + @JsonKey(name: "business_type") String? businessType, + @JsonKey(name: "order") int? order, + @JsonKey(name: "metadata") Map? metadata, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + }); +} + +/// @nodoc +class _$CategoryDtoCopyWithImpl<$Res, $Val extends CategoryDto> + implements $CategoryDtoCopyWith<$Res> { + _$CategoryDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryDto + /// 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? name = freezed, + Object? description = freezed, + Object? businessType = freezed, + Object? order = 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?, + organizationId: freezed == organizationId + ? _value.organizationId + : organizationId // 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?, + businessType: freezed == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String?, + order: freezed == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as int?, + 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 _$$CategoryDtoImplCopyWith<$Res> + implements $CategoryDtoCopyWith<$Res> { + factory _$$CategoryDtoImplCopyWith( + _$CategoryDtoImpl value, + $Res Function(_$CategoryDtoImpl) then, + ) = __$$CategoryDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "organization_id") String? organizationId, + @JsonKey(name: "name") String? name, + @JsonKey(name: "description") String? description, + @JsonKey(name: "business_type") String? businessType, + @JsonKey(name: "order") int? order, + @JsonKey(name: "metadata") Map? metadata, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + }); +} + +/// @nodoc +class __$$CategoryDtoImplCopyWithImpl<$Res> + extends _$CategoryDtoCopyWithImpl<$Res, _$CategoryDtoImpl> + implements _$$CategoryDtoImplCopyWith<$Res> { + __$$CategoryDtoImplCopyWithImpl( + _$CategoryDtoImpl _value, + $Res Function(_$CategoryDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of CategoryDto + /// 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? name = freezed, + Object? description = freezed, + Object? businessType = freezed, + Object? order = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _$CategoryDtoImpl( + 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?, + 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?, + businessType: freezed == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String?, + order: freezed == order + ? _value.order + : order // ignore: cast_nullable_to_non_nullable + as int?, + 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 _$CategoryDtoImpl extends _CategoryDto { + const _$CategoryDtoImpl({ + @JsonKey(name: "id") this.id, + @JsonKey(name: "organization_id") this.organizationId, + @JsonKey(name: "name") this.name, + @JsonKey(name: "description") this.description, + @JsonKey(name: "business_type") this.businessType, + @JsonKey(name: "order") this.order, + @JsonKey(name: "metadata") final Map? metadata, + @JsonKey(name: "created_at") this.createdAt, + @JsonKey(name: "updated_at") this.updatedAt, + }) : _metadata = metadata, + super._(); + + factory _$CategoryDtoImpl.fromJson(Map json) => + _$$CategoryDtoImplFromJson(json); + + @override + @JsonKey(name: "id") + final String? id; + @override + @JsonKey(name: "organization_id") + final String? organizationId; + @override + @JsonKey(name: "name") + final String? name; + @override + @JsonKey(name: "description") + final String? description; + @override + @JsonKey(name: "business_type") + final String? businessType; + @override + @JsonKey(name: "order") + final int? order; + 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 'CategoryDto(id: $id, organizationId: $organizationId, name: $name, description: $description, businessType: $businessType, order: $order, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CategoryDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.organizationId, organizationId) || + other.organizationId == organizationId) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.businessType, businessType) || + other.businessType == businessType) && + (identical(other.order, order) || other.order == order) && + 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, + organizationId, + name, + description, + businessType, + order, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of CategoryDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CategoryDtoImplCopyWith<_$CategoryDtoImpl> get copyWith => + __$$CategoryDtoImplCopyWithImpl<_$CategoryDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$CategoryDtoImplToJson(this); + } +} + +abstract class _CategoryDto extends CategoryDto { + const factory _CategoryDto({ + @JsonKey(name: "id") final String? id, + @JsonKey(name: "organization_id") final String? organizationId, + @JsonKey(name: "name") final String? name, + @JsonKey(name: "description") final String? description, + @JsonKey(name: "business_type") final String? businessType, + @JsonKey(name: "order") final int? order, + @JsonKey(name: "metadata") final Map? metadata, + @JsonKey(name: "created_at") final String? createdAt, + @JsonKey(name: "updated_at") final String? updatedAt, + }) = _$CategoryDtoImpl; + const _CategoryDto._() : super._(); + + factory _CategoryDto.fromJson(Map json) = + _$CategoryDtoImpl.fromJson; + + @override + @JsonKey(name: "id") + String? get id; + @override + @JsonKey(name: "organization_id") + String? get organizationId; + @override + @JsonKey(name: "name") + String? get name; + @override + @JsonKey(name: "description") + String? get description; + @override + @JsonKey(name: "business_type") + String? get businessType; + @override + @JsonKey(name: "order") + int? get order; + @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 CategoryDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CategoryDtoImplCopyWith<_$CategoryDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/category/category_dtos.g.dart b/lib/infrastructure/category/category_dtos.g.dart new file mode 100644 index 0000000..48b1750 --- /dev/null +++ b/lib/infrastructure/category/category_dtos.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'category_dtos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ListCategoryDtoImpl _$$ListCategoryDtoImplFromJson( + Map json, +) => _$ListCategoryDtoImpl( + categories: (json['categories'] as List?) + ?.map((e) => CategoryDto.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 _$$ListCategoryDtoImplToJson( + _$ListCategoryDtoImpl instance, +) => { + 'categories': instance.categories, + 'total_count': instance.totalCount, + 'page': instance.page, + 'limit': instance.limit, + 'total_pages': instance.totalPages, +}; + +_$CategoryDtoImpl _$$CategoryDtoImplFromJson(Map json) => + _$CategoryDtoImpl( + id: json['id'] as String?, + organizationId: json['organization_id'] as String?, + name: json['name'] as String?, + description: json['description'] as String?, + businessType: json['business_type'] as String?, + order: (json['order'] as num?)?.toInt(), + metadata: json['metadata'] as Map?, + createdAt: json['created_at'] as String?, + updatedAt: json['updated_at'] as String?, + ); + +Map _$$CategoryDtoImplToJson(_$CategoryDtoImpl instance) => + { + 'id': instance.id, + 'organization_id': instance.organizationId, + 'name': instance.name, + 'description': instance.description, + 'business_type': instance.businessType, + 'order': instance.order, + 'metadata': instance.metadata, + 'created_at': instance.createdAt, + 'updated_at': instance.updatedAt, + }; diff --git a/lib/infrastructure/category/datasources/local_data_provider.dart b/lib/infrastructure/category/datasources/local_data_provider.dart new file mode 100644 index 0000000..0592e21 --- /dev/null +++ b/lib/infrastructure/category/datasources/local_data_provider.dart @@ -0,0 +1,349 @@ +import 'dart:developer'; + +import 'package:data_channel/data_channel.dart'; +import 'package:injectable/injectable.dart'; +import 'package:sqflite/sqflite.dart'; + +import '../../../common/constant/app_constant.dart'; +import '../../../common/database/database_helper.dart'; +import '../../../domain/category/category.dart'; +import '../category_dtos.dart'; + +@injectable +class CategoryLocalDataProvider { + final DatabaseHelper _databaseHelper; + final _logName = 'CategoryLocalDataProvider'; + + CategoryLocalDataProvider(this._databaseHelper); + + final Map> _queryCache = {}; + final Duration _cacheExpiry = Duration(minutes: AppConstant.cacheExpire); + final Map _cacheTimestamps = {}; + + Future> saveCategoriesBatch( + List categories, { + bool clearFirst = false, + }) async { + final db = await _databaseHelper.database; + + try { + await db.transaction((txn) async { + if (clearFirst) { + log('๐Ÿ—‘๏ธ Clearing existing categories...', name: _logName); + await txn.delete('categories'); + } + + log( + '๐Ÿ’พ Batch saving ${categories.length} categories...', + name: _logName, + ); + + // Batch insert categories + final batch = txn.batch(); + for (final category in categories) { + batch.insert( + 'categories', + category.toMap(), + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + await batch.commit(noResult: true); + }); + + // Clear cache after update + clearCache(); + log( + 'โœ… Successfully batch saved ${categories.length} categories', + name: _logName, + ); + + return DC.data(null); + } catch (e, s) { + log( + 'โŒ Error batch saving categories', + name: _logName, + error: e, + stackTrace: s, + ); + return DC.error(CategoryFailure.dynamicErrorMessage(e.toString())); + } + } + + Future>> getCachedCategories({ + int page = 1, + int limit = 10, + bool isActive = true, + String? search, + }) async { + final cacheKey = _generateCacheKey(page, limit, isActive, 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) { + log( + '๐Ÿš€ Cache HIT: $cacheKey (${_queryCache[cacheKey]!.length} categories)', + name: _logName, + ); + return DC.data(_queryCache[cacheKey]!); + } + } + + log('๐Ÿ“€ Cache MISS: $cacheKey, querying database...', name: _logName); + + // Cache miss, query database + final result = await getCategories( + page: page, + limit: limit, + isActive: isActive, + search: search, + ); + + // Check if result has data or error + if (result.hasData) { + final categories = result.data!; + + // Store in cache + _queryCache[cacheKey] = categories; + _cacheTimestamps[cacheKey] = now; + + log( + '๐Ÿ’พ Cached ${categories.length} categories for key: $cacheKey', + name: _logName, + ); + + return DC.data(categories); + } else { + // Return error from database query + return DC.error(result.error!); + } + } catch (e, s) { + log( + 'โŒ Error getting cached categories', + name: _logName, + error: e, + stackTrace: s, + ); + return DC.error(CategoryFailure.localStorageError(e.toString())); + } + } + + Future>> getCategories({ + int page = 1, + int limit = 10, + bool isActive = true, + String? search, + }) async { + final db = await _databaseHelper.database; + + try { + String query = 'SELECT * FROM categories WHERE 1=1'; + List whereArgs = []; + + // Note: Assuming is_active will be added to database schema + if (isActive) { + query += ' AND is_active = ?'; + whereArgs.add(1); + } + + if (search != null && search.isNotEmpty) { + query += ' AND (name LIKE ? OR description LIKE ?)'; + whereArgs.add('%$search%'); + whereArgs.add('%$search%'); + } + + // query += ' ORDER BY name ASC'; + + 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 categories = maps.map((map) => CategoryDto.fromMap(map)).toList(); + + log( + '๐Ÿ“Š Retrieved ${categories.length} categories from database', + name: _logName, + ); + + return DC.data(categories); + } catch (e, s) { + log( + 'โŒ Error getting categories', + name: _logName, + error: e, + stackTrace: s, + ); + return DC.error(CategoryFailure.localStorageError(e.toString())); + } + } + + Future> getCategoryById(String id) async { + final db = await _databaseHelper.database; + + try { + final List> maps = await db.query( + 'categories', + where: 'id = ?', + whereArgs: [id], + ); + + if (maps.isEmpty) { + log('โŒ Category not found: $id', name: _logName); + return DC.error(CategoryFailure.empty()); + } + + final category = CategoryDto.fromMap(maps.first); + log('โœ… Category found: ${category.name}', name: _logName); + return DC.data(category); + } catch (e, s) { + log( + 'โŒ Error getting category by ID', + name: _logName, + error: e, + stackTrace: s, + ); + return DC.error(CategoryFailure.localStorageError(e.toString())); + } + } + + Future getTotalCount({bool isActive = true, String? search}) async { + final db = await _databaseHelper.database; + + try { + String query = 'SELECT COUNT(*) FROM categories WHERE 1=1'; + List whereArgs = []; + + if (isActive) { + query += ' AND is_active = ?'; + whereArgs.add(1); + } + + if (search != null && search.isNotEmpty) { + query += ' AND (name LIKE ? OR description LIKE ?)'; + whereArgs.add('%$search%'); + whereArgs.add('%$search%'); + } + + final result = await db.rawQuery(query, whereArgs); + final count = Sqflite.firstIntValue(result) ?? 0; + log( + '๐Ÿ“Š Category total count: $count (isActive: $isActive, search: $search)', + name: _logName, + ); + return count; + } catch (e) { + log('โŒ Error getting category total count: $e', name: _logName); + return 0; + } + } + + Future hasCategories() async { + final count = await getTotalCount(); + final hasData = count > 0; + log('๐Ÿ” Has categories: $hasData ($count categories)', name: _logName); + return hasData; + } + + Future>> getDatabaseStats() async { + final db = await _databaseHelper.database; + + try { + final categoryCount = + Sqflite.firstIntValue( + await db.rawQuery('SELECT COUNT(*) FROM categories'), + ) ?? + 0; + + final activeCount = + Sqflite.firstIntValue( + await db.rawQuery( + 'SELECT COUNT(*) FROM categories WHERE is_active = 1', + ), + ) ?? + 0; + + final stats = { + 'total_categories': categoryCount, + 'active_categories': activeCount, + 'cache_entries': _queryCache.length, + 'last_updated': DateTime.now().toIso8601String(), + }; + + log('๐Ÿ“Š Category Database Stats: $stats', name: _logName); + return DC.data(stats); + } catch (e, s) { + log( + 'โŒ Error getting category database stats', + name: _logName, + error: e, + stackTrace: s, + ); + return DC.error( + CategoryFailure.localStorageError( + 'Gagal memuat statistik database: $e', + ), + ); + } + } + + Future clearAllCategories() async { + final db = await _databaseHelper.database; + + try { + await db.delete('categories'); + clearCache(); + log('๐Ÿ—‘๏ธ All categories cleared from local DB'); + } catch (e) { + log('โŒ Error clearing categories: $e'); + rethrow; + } + } + + void clearCache() { + final count = _queryCache.length; + _queryCache.clear(); + _cacheTimestamps.clear(); + log('๐Ÿงน Category cache cleared: $count entries removed', name: _logName); + } + + String _generateCacheKey(int page, int limit, bool isActive, String? search) { + return 'categories_${page}_${limit}_${isActive}_${search ?? 'null'}'; + } + + 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 category cache cleared: ${expiredKeys.length} entries', + name: _logName, + ); + } + } +} diff --git a/lib/infrastructure/category/datasources/remote_data_provider.dart b/lib/infrastructure/category/datasources/remote_data_provider.dart new file mode 100644 index 0000000..4de4e62 --- /dev/null +++ b/lib/infrastructure/category/datasources/remote_data_provider.dart @@ -0,0 +1,45 @@ +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/category/category.dart'; +import '../category_dtos.dart'; + +@injectable +class CategoryRemoteDataProvider { + final ApiClient _apiClient; + final _logName = 'CategoryRemoteDataProvider'; + + CategoryRemoteDataProvider(this._apiClient); + + Future> fetchCategories({ + int page = 1, + int limit = 10, + }) async { + try { + final response = await _apiClient.get( + ApiPath.categories, + params: {'page': page, 'limit': limit}, + headers: getAuthorizationHeader(), + ); + + if (response.data['data'] == null) { + return DC.error(CategoryFailure.empty()); + } + + final categories = ListCategoryDto.fromJson( + response.data['data'] as Map, + ); + + return DC.data(categories); + } on ApiFailure catch (e, s) { + log('fetchCategoryError', name: _logName, error: e, stackTrace: s); + return DC.error(CategoryFailure.serverError(e)); + } + } +} diff --git a/lib/infrastructure/category/dtos/category_dto.dart b/lib/infrastructure/category/dtos/category_dto.dart new file mode 100644 index 0000000..c5585c7 --- /dev/null +++ b/lib/infrastructure/category/dtos/category_dto.dart @@ -0,0 +1,98 @@ +part of '../category_dtos.dart'; + +@freezed +class ListCategoryDto with _$ListCategoryDto { + const ListCategoryDto._(); + + const factory ListCategoryDto({ + @JsonKey(name: "categories") List? categories, + @JsonKey(name: "total_count") int? totalCount, + @JsonKey(name: "page") int? page, + @JsonKey(name: "limit") int? limit, + @JsonKey(name: "total_pages") int? totalPages, + }) = _ListCategoryDto; + + factory ListCategoryDto.fromJson(Map json) => + _$ListCategoryDtoFromJson(json); + + ListCategory toDomain() => ListCategory( + categories: categories?.map((dto) => dto.toDomain()).toList() ?? [], + totalCount: totalCount ?? 0, + page: page ?? 0, + limit: limit ?? 0, + totalPages: totalPages ?? 0, + ); +} + +@freezed +class CategoryDto with _$CategoryDto { + const CategoryDto._(); + + const factory CategoryDto({ + @JsonKey(name: "id") String? id, + @JsonKey(name: "organization_id") String? organizationId, + @JsonKey(name: "name") String? name, + @JsonKey(name: "description") String? description, + @JsonKey(name: "business_type") String? businessType, + @JsonKey(name: "order") int? order, + @JsonKey(name: "metadata") Map? metadata, + @JsonKey(name: "created_at") String? createdAt, + @JsonKey(name: "updated_at") String? updatedAt, + }) = _CategoryDto; + + factory CategoryDto.fromJson(Map json) => + _$CategoryDtoFromJson(json); + + /// Mapping ke domain + Category toDomain() => Category( + id: id ?? '', + organizationId: organizationId ?? '', + name: name ?? '', + description: description ?? '', + businessType: businessType ?? '', + order: order ?? 0, + metadata: metadata ?? {}, + createdAt: createdAt ?? '', + updatedAt: updatedAt ?? '', + ); + + /// Mapping ke Map untuk SQLite + Map toMap() => { + 'id': id, + 'organization_id': organizationId, + 'name': name, + 'description': description, + 'business_type': businessType, + 'order': order, + 'metadata': metadata != null ? jsonEncode(metadata) : null, + 'created_at': createdAt, + 'updated_at': updatedAt, + }; + + /// Mapping dari Map SQLite + factory CategoryDto.fromMap(Map map) => CategoryDto( + id: map['id'] as String?, + organizationId: map['organization_id'] as String?, + name: map['name'] as String?, + description: map['description'] as String?, + businessType: map['business_type'] as String?, + order: map['order'] as int?, + metadata: map['metadata'] != null + ? jsonDecode(map['metadata'] as String) as Map + : null, + createdAt: map['created_at'] as String?, + updatedAt: map['updated_at'] as String?, + ); + + factory CategoryDto.fromDomain(Category category) => CategoryDto( + id: category.id, + organizationId: category.organizationId, + name: category.name, + description: category.description, + businessType: category.businessType, + order: category.order, + metadata: category.metadata, + createdAt: category.createdAt, + updatedAt: category.updatedAt, + ); +} diff --git a/lib/infrastructure/category/repositories/category_repository.dart b/lib/infrastructure/category/repositories/category_repository.dart new file mode 100644 index 0000000..c2c6899 --- /dev/null +++ b/lib/infrastructure/category/repositories/category_repository.dart @@ -0,0 +1,352 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/category/category.dart'; +import '../category_dtos.dart'; +import '../datasources/local_data_provider.dart'; +import '../datasources/remote_data_provider.dart'; + +@Injectable(as: ICategoryRepository) +class CategoryRepository implements ICategoryRepository { + final CategoryRemoteDataProvider _remoteDataProvider; + final CategoryLocalDataProvider _localDataProvider; + + final _logName = 'CategoryRepository'; + + CategoryRepository(this._remoteDataProvider, this._localDataProvider); + + @override + Future> getCategories({ + int page = 1, + int limit = 10, + bool isActive = true, + String? search, + bool forceRemote = false, + }) async { + try { + log( + '๐Ÿ“ฑ Getting categories - page: $page, isActive: $isActive, search: $search, forceRemote: $forceRemote', + name: _logName, + ); + + // Clean expired cache + _localDataProvider.clearExpiredCache(); + + // Check if we should try remote first + if (forceRemote || !await _localDataProvider.hasCategories()) { + log('๐ŸŒ Attempting remote fetch first...', name: _logName); + + final remoteResult = await _getRemoteCategories( + page: page, + limit: limit, + isActive: isActive, + ); + + return await remoteResult.fold( + (failure) async { + log('โŒ Remote fetch failed: $failure', name: _logName); + log('๐Ÿ“ฑ Falling back to local data...', name: _logName); + return await _getLocalCategories( + page: page, + limit: limit, + isActive: isActive, + search: search, + ); + }, + (data) async { + log( + 'โœ… Remote fetch successful, syncing to local...', + name: _logName, + ); + + // Sync remote data to local + if (data.categories.isNotEmpty) { + await _syncToLocal(data.categories, clearFirst: page == 1); + } + + return Right(data); + }, + ); + } else { + log('๐Ÿ“ฑ Using local data (cache available)...', name: _logName); + return await _getLocalCategories( + page: page, + limit: limit, + isActive: isActive, + search: search, + ); + } + } catch (e, s) { + log('โŒ Error in getCategories', name: _logName, error: e, stackTrace: s); + return Left( + CategoryFailure.dynamicErrorMessage('Gagal memuat kategori: $e'), + ); + } + } + + @override + Future> getCategoryById(String id) async { + try { + log('๐Ÿ” Getting category by ID: $id', name: _logName); + + final result = await _localDataProvider.getCategoryById(id); + + if (result.hasData) { + final category = result.data!.toDomain(); + log('โœ… Category found: ${category.name}', name: _logName); + return Right(category); + } else { + log('โŒ Category not found or error: ${result.error}', name: _logName); + return Left(result.error!); + } + } catch (e, s) { + log( + 'โŒ Error getting category by ID', + name: _logName, + error: e, + stackTrace: s, + ); + return Left( + CategoryFailure.localStorageError('Gagal memuat kategori: $e'), + ); + } + } + + @override + Future>> + getDatabaseStats() async { + try { + log('๐Ÿ“Š Getting database stats...', name: _logName); + + final result = await _localDataProvider.getDatabaseStats(); + + if (result.hasData) { + final stats = result.data!; + log('๐Ÿ“Š Category database stats: $stats', name: _logName); + return Right(stats); + } else { + log('โŒ Error getting stats: ${result.error}', name: _logName); + return Left(result.error!); + } + } catch (e, s) { + log( + 'โŒ Error getting database stats', + name: _logName, + error: e, + stackTrace: s, + ); + return Left( + CategoryFailure.localStorageError( + 'Gagal memuat statistik database: $e', + ), + ); + } + } + + @override + Future hasLocalCategories() async { + final hasCategories = await _localDataProvider.hasCategories(); + log('๐Ÿ“Š Has local categories: $hasCategories', name: _logName); + return hasCategories; + } + + @override + Future> refreshCategories({ + bool isActive = true, + String? search, + }) async { + try { + log('๐Ÿ”„ Refreshing categories...', name: _logName); + + // Clear cache before refresh + _localDataProvider.clearCache(); + + return await getCategories( + page: 1, + limit: 10, + isActive: isActive, + search: search, + forceRemote: true, // Force remote refresh + ); + } catch (e, s) { + log( + 'โŒ Error refreshing categories', + name: _logName, + error: e, + stackTrace: s, + ); + return Left( + CategoryFailure.localStorageError('Gagal memperbarui kategori: $e'), + ); + } + } + + @override + Future> syncAllCategories() async { + try { + log('๐Ÿ”„ Starting manual sync of all categories...', name: _logName); + + int page = 1; + const limit = 50; // Higher limit for bulk sync + bool hasMore = true; + int totalSynced = 0; + + // Clear local data first for fresh sync + await _localDataProvider.clearAllCategories(); + + while (hasMore) { + log('๐Ÿ“„ Syncing page $page...'); + + final result = await _remoteDataProvider.fetchCategories( + page: page, + limit: limit, + // isActive: true, + ); + + // If fetchCategories returns DC directly, handle it directly + final data = result.data!.toDomain(); + + if (data.categories.isNotEmpty) { + await _localDataProvider.saveCategoriesBatch( + data.categories + .map((category) => CategoryDto.fromDomain(category)) + .toList(), + clearFirst: false, // Don't clear on subsequent pages + ); + totalSynced += data.categories.length; + + // Check if we have more pages + hasMore = page < data.totalPages; + page++; + + log( + '๐Ÿ“ฆ Page ${page - 1} synced: ${data.categories.length} categories', + ); + } else { + hasMore = false; + } + } + + final message = 'Berhasil sinkronisasi $totalSynced kategori'; + log('โœ… $message'); + return Right(message); + } catch (e) { + final error = 'Gagal sinkronisasi kategori: $e'; + log('โŒ $error'); + return Left(CategoryFailure.localStorageError(error)); + } + } + + Future> _getRemoteCategories({ + int page = 1, + int limit = 10, + bool isActive = true, + }) async { + try { + log('๐ŸŒ Fetching categories from remote...', name: _logName); + + final result = await _remoteDataProvider.fetchCategories( + page: page, + limit: limit, + ); + + // Convert DC to Either and DTO to Domain + if (result.hasData) { + final categories = result.data!.toDomain(); + return Right(categories); + } else { + return Left(result.error!); + } + } catch (e, s) { + log('โŒ Remote fetch error', name: _logName, error: e, stackTrace: s); + return Left( + CategoryFailure.dynamicErrorMessage( + 'Gagal mengambil data dari server: $e', + ), + ); + } + } + + Future> _getLocalCategories({ + int page = 1, + int limit = 10, + bool isActive = true, + String? search, + }) async { + try { + log('๐Ÿ’พ Fetching categories from local...', name: _logName); + + final result = await _localDataProvider.getCachedCategories( + page: page, + limit: limit, + isActive: isActive, + search: search, + ); + + final totalCount = await _localDataProvider.getTotalCount( + isActive: isActive, + search: search, + ); + + // Convert DC to Either and DTO to Domain + if (result.hasData) { + final categories = result.data!.map((dto) => dto.toDomain()).toList(); + final categoryData = ListCategory( + categories: categories, + totalCount: totalCount, + page: page, + limit: limit, + totalPages: totalCount > 0 ? (totalCount / limit).ceil() : 0, + ); + log('โœ… Returned ${categories.length} local categories', name: _logName); + return Right(categoryData); + } else { + log( + 'โŒ Error getting local categories: ${result.error}', + name: _logName, + ); + return Left(result.error!); + } + } catch (e, s) { + log( + 'โŒ Error getting local categories', + name: _logName, + error: e, + stackTrace: s, + ); + return Left( + CategoryFailure.localStorageError( + 'Gagal memuat kategori dari database lokal: $e', + ), + ); + } + } + + Future _syncToLocal( + List categories, { + bool clearFirst = false, + }) async { + try { + log( + '๐Ÿ’พ Syncing ${categories.length} categories to local database...', + name: _logName, + ); + await _localDataProvider.saveCategoriesBatch( + categories.map((category) => CategoryDto.fromDomain(category)).toList(), + clearFirst: clearFirst, + ); + log('โœ… Categories synced to local successfully', name: _logName); + } catch (e) { + log('โŒ Error syncing categories to local: $e', name: _logName); + rethrow; + } + } + + @override + void clearCache() { + log('๐Ÿงน Clearing category cache', name: _logName); + _localDataProvider.clearCache(); + } +} diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 044ec1c..8e76678 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -15,14 +15,18 @@ import 'package:apskel_pos_flutter_v2/application/auth/login_form/login_form_blo import 'package:apskel_pos_flutter_v2/application/outlet/outlet_loader/outlet_loader_bloc.dart' as _i76; 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; import 'package:apskel_pos_flutter_v2/common/di/di_auto_route.dart' as _i729; import 'package:apskel_pos_flutter_v2/common/di/di_connectivity.dart' as _i807; +import 'package:apskel_pos_flutter_v2/common/di/di_database.dart' as _i209; import 'package:apskel_pos_flutter_v2/common/di/di_dio.dart' as _i86; import 'package:apskel_pos_flutter_v2/common/di/di_shared_preferences.dart' as _i135; import 'package:apskel_pos_flutter_v2/common/network/network_client.dart' as _i171; 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/env.dart' as _i923; import 'package:apskel_pos_flutter_v2/infrastructure/auth/datasources/local_data_provider.dart' @@ -31,6 +35,12 @@ import 'package:apskel_pos_flutter_v2/infrastructure/auth/datasources/remote_dat as _i370; import 'package:apskel_pos_flutter_v2/infrastructure/auth/repositories/auth_repository.dart' as _i941; +import 'package:apskel_pos_flutter_v2/infrastructure/category/datasources/local_data_provider.dart' + as _i708; +import 'package:apskel_pos_flutter_v2/infrastructure/category/datasources/remote_data_provider.dart' + as _i856; +import 'package:apskel_pos_flutter_v2/infrastructure/category/repositories/category_repository.dart' + as _i604; import 'package:apskel_pos_flutter_v2/infrastructure/outlet/datasources/local_data_provider.dart' as _i693; import 'package:apskel_pos_flutter_v2/infrastructure/outlet/datasources/remote_data_provider.dart' @@ -56,6 +66,7 @@ extension GetItInjectableX on _i174.GetIt { }) async { final gh = _i526.GetItHelper(this, environment, environmentFilter); final sharedPreferencesDi = _$SharedPreferencesDi(); + final databaseDi = _$DatabaseDi(); final dioDi = _$DioDi(); final autoRouteDi = _$AutoRouteDi(); final connectivityDi = _$ConnectivityDi(); @@ -63,6 +74,7 @@ extension GetItInjectableX on _i174.GetIt { () => sharedPreferencesDi.prefs, preResolve: true, ); + gh.singleton<_i487.DatabaseHelper>(() => databaseDi.databaseHelper); gh.lazySingleton<_i361.Dio>(() => dioDi.dio); gh.lazySingleton<_i800.AppRouter>(() => autoRouteDi.appRouter); gh.lazySingleton<_i895.Connectivity>(() => connectivityDi.connectivity); @@ -70,6 +82,9 @@ extension GetItInjectableX on _i174.GetIt { () => _i171.NetworkClient(gh<_i895.Connectivity>()), ); gh.factory<_i923.Env>(() => _i923.DevEnv(), registerFor: {_dev}); + gh.factory<_i708.CategoryLocalDataProvider>( + () => _i708.CategoryLocalDataProvider(gh<_i487.DatabaseHelper>()), + ); gh.factory<_i204.AuthLocalDataProvider>( () => _i204.AuthLocalDataProvider(gh<_i460.SharedPreferences>()), ); @@ -80,6 +95,9 @@ extension GetItInjectableX on _i174.GetIt { () => _i457.ApiClient(gh<_i361.Dio>(), gh<_i923.Env>()), ); gh.factory<_i923.Env>(() => _i923.ProdEnv(), registerFor: {_prod}); + gh.factory<_i856.CategoryRemoteDataProvider>( + () => _i856.CategoryRemoteDataProvider(gh<_i457.ApiClient>()), + ); gh.factory<_i370.AuthRemoteDataProvider>( () => _i370.AuthRemoteDataProvider(gh<_i457.ApiClient>()), ); @@ -92,6 +110,12 @@ extension GetItInjectableX on _i174.GetIt { gh<_i204.AuthLocalDataProvider>(), ), ); + gh.factory<_i502.ICategoryRepository>( + () => _i604.CategoryRepository( + gh<_i856.CategoryRemoteDataProvider>(), + gh<_i708.CategoryLocalDataProvider>(), + ), + ); gh.factory<_i552.IOutletRepository>( () => _i845.OutletRepository( gh<_i132.OutletRemoteDataProvider>(), @@ -116,6 +140,8 @@ extension GetItInjectableX on _i174.GetIt { class _$SharedPreferencesDi extends _i135.SharedPreferencesDi {} +class _$DatabaseDi extends _i209.DatabaseDi {} + class _$DioDi extends _i86.DioDi {} class _$AutoRouteDi extends _i729.AutoRouteDi {} diff --git a/lib/presentation/app_widget.dart b/lib/presentation/app_widget.dart index 5a21363..8cbfb23 100644 --- a/lib/presentation/app_widget.dart +++ b/lib/presentation/app_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import '../application/auth/auth_bloc.dart'; +import '../application/category/category_loader/category_loader_bloc.dart'; import '../application/outlet/outlet_loader/outlet_loader_bloc.dart'; import '../common/theme/theme.dart'; import '../common/constant/app_constant.dart'; @@ -25,6 +26,7 @@ class _AppWidgetState extends State { providers: [ BlocProvider(create: (context) => getIt()), BlocProvider(create: (context) => getIt()), + BlocProvider(create: (context) => getIt()), ], child: MaterialApp.router( debugShowCheckedModeBanner: false, diff --git a/lib/presentation/pages/splash/splash_page.dart b/lib/presentation/pages/splash/splash_page.dart index e5e3e38..e331c29 100644 --- a/lib/presentation/pages/splash/splash_page.dart +++ b/lib/presentation/pages/splash/splash_page.dart @@ -35,7 +35,7 @@ class _SplashPageState extends State { listenWhen: (previous, current) => previous.status != current.status, listener: (context, state) { if (state.isAuthenticated) { - context.router.replace(const MainRoute()); + context.router.replace(const SyncRoute()); } else { context.router.replace(const LoginRoute()); } diff --git a/lib/presentation/pages/sync/sync_page.dart b/lib/presentation/pages/sync/sync_page.dart new file mode 100644 index 0000000..6bbed68 --- /dev/null +++ b/lib/presentation/pages/sync/sync_page.dart @@ -0,0 +1,12 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter/widgets.dart'; + +@RoutePage() +class SyncPage extends StatelessWidget { + const SyncPage({super.key}); + + @override + Widget build(BuildContext context) { + return const Placeholder(); + } +} diff --git a/lib/presentation/router/app_router.dart b/lib/presentation/router/app_router.dart index 7ccc5c3..56181e6 100644 --- a/lib/presentation/router/app_router.dart +++ b/lib/presentation/router/app_router.dart @@ -22,5 +22,8 @@ class AppRouter extends RootStackRouter { AutoRoute(page: SettingRoute.page), ], ), + + // Sync + AutoRoute(page: SyncRoute.page), ]; } diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index 5914064..fb8a0a1 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -22,20 +22,22 @@ import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/report/repor import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/setting/setting_page.dart' as _i6; import 'package:apskel_pos_flutter_v2/presentation/pages/main/pages/table/table_page.dart' - as _i8; + as _i9; import 'package:apskel_pos_flutter_v2/presentation/pages/splash/splash_page.dart' as _i7; -import 'package:auto_route/auto_route.dart' as _i9; +import 'package:apskel_pos_flutter_v2/presentation/pages/sync/sync_page.dart' + as _i8; +import 'package:auto_route/auto_route.dart' as _i10; /// generated route for /// [_i1.CustomerPage] -class CustomerRoute extends _i9.PageRouteInfo { - const CustomerRoute({List<_i9.PageRouteInfo>? children}) +class CustomerRoute extends _i10.PageRouteInfo { + const CustomerRoute({List<_i10.PageRouteInfo>? children}) : super(CustomerRoute.name, initialChildren: children); static const String name = 'CustomerRoute'; - static _i9.PageInfo page = _i9.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { return const _i1.CustomerPage(); @@ -45,13 +47,13 @@ class CustomerRoute extends _i9.PageRouteInfo { /// generated route for /// [_i2.HomePage] -class HomeRoute extends _i9.PageRouteInfo { - const HomeRoute({List<_i9.PageRouteInfo>? children}) +class HomeRoute extends _i10.PageRouteInfo { + const HomeRoute({List<_i10.PageRouteInfo>? children}) : super(HomeRoute.name, initialChildren: children); static const String name = 'HomeRoute'; - static _i9.PageInfo page = _i9.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { return const _i2.HomePage(); @@ -61,29 +63,29 @@ class HomeRoute extends _i9.PageRouteInfo { /// generated route for /// [_i3.LoginPage] -class LoginRoute extends _i9.PageRouteInfo { - const LoginRoute({List<_i9.PageRouteInfo>? children}) +class LoginRoute extends _i10.PageRouteInfo { + const LoginRoute({List<_i10.PageRouteInfo>? children}) : super(LoginRoute.name, initialChildren: children); static const String name = 'LoginRoute'; - static _i9.PageInfo page = _i9.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { - return _i9.WrappedRoute(child: const _i3.LoginPage()); + return _i10.WrappedRoute(child: const _i3.LoginPage()); }, ); } /// generated route for /// [_i4.MainPage] -class MainRoute extends _i9.PageRouteInfo { - const MainRoute({List<_i9.PageRouteInfo>? children}) +class MainRoute extends _i10.PageRouteInfo { + const MainRoute({List<_i10.PageRouteInfo>? children}) : super(MainRoute.name, initialChildren: children); static const String name = 'MainRoute'; - static _i9.PageInfo page = _i9.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { return const _i4.MainPage(); @@ -93,13 +95,13 @@ class MainRoute extends _i9.PageRouteInfo { /// generated route for /// [_i5.ReportPage] -class ReportRoute extends _i9.PageRouteInfo { - const ReportRoute({List<_i9.PageRouteInfo>? children}) +class ReportRoute extends _i10.PageRouteInfo { + const ReportRoute({List<_i10.PageRouteInfo>? children}) : super(ReportRoute.name, initialChildren: children); static const String name = 'ReportRoute'; - static _i9.PageInfo page = _i9.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { return const _i5.ReportPage(); @@ -109,13 +111,13 @@ class ReportRoute extends _i9.PageRouteInfo { /// generated route for /// [_i6.SettingPage] -class SettingRoute extends _i9.PageRouteInfo { - const SettingRoute({List<_i9.PageRouteInfo>? children}) +class SettingRoute extends _i10.PageRouteInfo { + const SettingRoute({List<_i10.PageRouteInfo>? children}) : super(SettingRoute.name, initialChildren: children); static const String name = 'SettingRoute'; - static _i9.PageInfo page = _i9.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { return const _i6.SettingPage(); @@ -125,13 +127,13 @@ class SettingRoute extends _i9.PageRouteInfo { /// generated route for /// [_i7.SplashPage] -class SplashRoute extends _i9.PageRouteInfo { - const SplashRoute({List<_i9.PageRouteInfo>? children}) +class SplashRoute extends _i10.PageRouteInfo { + const SplashRoute({List<_i10.PageRouteInfo>? children}) : super(SplashRoute.name, initialChildren: children); static const String name = 'SplashRoute'; - static _i9.PageInfo page = _i9.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { return const _i7.SplashPage(); @@ -140,17 +142,33 @@ class SplashRoute extends _i9.PageRouteInfo { } /// generated route for -/// [_i8.TablePage] -class TableRoute extends _i9.PageRouteInfo { - const TableRoute({List<_i9.PageRouteInfo>? children}) +/// [_i8.SyncPage] +class SyncRoute extends _i10.PageRouteInfo { + const SyncRoute({List<_i10.PageRouteInfo>? children}) + : super(SyncRoute.name, initialChildren: children); + + static const String name = 'SyncRoute'; + + static _i10.PageInfo page = _i10.PageInfo( + name, + builder: (data) { + return const _i8.SyncPage(); + }, + ); +} + +/// generated route for +/// [_i9.TablePage] +class TableRoute extends _i10.PageRouteInfo { + const TableRoute({List<_i10.PageRouteInfo>? children}) : super(TableRoute.name, initialChildren: children); static const String name = 'TableRoute'; - static _i9.PageInfo page = _i9.PageInfo( + static _i10.PageInfo page = _i10.PageInfo( name, builder: (data) { - return const _i8.TablePage(); + return const _i9.TablePage(); }, ); } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 602170f..13d89ca 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import firebase_core import firebase_crashlytics import path_provider_foundation import shared_preferences_foundation +import sqflite_darwin func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) @@ -17,4 +18,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 6fa7bae..b7f6046 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -949,6 +949,46 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" + sqflite: + dependency: "direct main" + description: + name: sqflite + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88 + url: "https://pub.dev" + source: hosted + version: "2.4.2+2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -981,6 +1021,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: c254ade258ec8282947a0acbbc90b9575b4f19673533ee46f2f6e9b3aeefd7c0 + url: "https://pub.dev" + source: hosted + version: "3.4.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bcdb115..c99cfc0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,6 +33,7 @@ dependencies: flutter_spinkit: ^5.2.2 bloc: ^9.1.0 flutter_bloc: ^9.1.1 + sqflite: ^2.4.2 dev_dependencies: flutter_test: