From 7d24b3296dbccc5da1a52e6ebd721c116dab0da6 Mon Sep 17 00:00:00 2001 From: efrilm Date: Sun, 17 Aug 2025 14:18:10 +0700 Subject: [PATCH] feat: product --- .../product_loader/product_loader_bloc.dart | 92 ++ .../product_loader_bloc.freezed.dart | 821 ++++++++++ .../product_loader/product_loader_event.dart | 11 + .../product_loader/product_loader_state.dart | 17 + lib/common/url/api_path.dart | 3 + .../product/entities/product_entity.dart | 43 + .../entities/product_variant_entity.dart | 26 + .../product/failures/product_failure.dart | 10 + lib/domain/product/product.dart | 11 + lib/domain/product/product.freezed.dart | 1435 +++++++++++++++++ .../repositories/i_product_repository.dart | 10 + .../datasources/remote_data_provider.dart | 52 + .../product/dto/product_dto.dart | 70 + .../product/dto/product_variant_dto.dart | 45 + lib/infrastructure/product/product_dtos.dart | 9 + .../product/product_dtos.freezed.dart | 926 +++++++++++ .../product/product_dtos.g.dart | 83 + .../repositories/product_repository.dart | 43 + lib/injection.config.dart | 16 + .../pages/product/product_page.dart | 225 +-- .../pages/product/widgets/product_tile.dart | 6 +- lib/presentation/router/app_router.gr.dart | 8 +- 22 files changed, 3779 insertions(+), 183 deletions(-) create mode 100644 lib/application/product/product_loader/product_loader_bloc.dart create mode 100644 lib/application/product/product_loader/product_loader_bloc.freezed.dart create mode 100644 lib/application/product/product_loader/product_loader_event.dart create mode 100644 lib/application/product/product_loader/product_loader_state.dart create mode 100644 lib/domain/product/entities/product_entity.dart create mode 100644 lib/domain/product/entities/product_variant_entity.dart create mode 100644 lib/domain/product/failures/product_failure.dart create mode 100644 lib/domain/product/product.dart create mode 100644 lib/domain/product/product.freezed.dart create mode 100644 lib/domain/product/repositories/i_product_repository.dart create mode 100644 lib/infrastructure/product/datasources/remote_data_provider.dart create mode 100644 lib/infrastructure/product/dto/product_dto.dart create mode 100644 lib/infrastructure/product/dto/product_variant_dto.dart create mode 100644 lib/infrastructure/product/product_dtos.dart create mode 100644 lib/infrastructure/product/product_dtos.freezed.dart create mode 100644 lib/infrastructure/product/product_dtos.g.dart create mode 100644 lib/infrastructure/product/repositories/product_repository.dart diff --git a/lib/application/product/product_loader/product_loader_bloc.dart b/lib/application/product/product_loader/product_loader_bloc.dart new file mode 100644 index 0000000..b08b29c --- /dev/null +++ b/lib/application/product/product_loader/product_loader_bloc.dart @@ -0,0 +1,92 @@ +import 'package:dartz/dartz.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/product/product.dart'; + +part 'product_loader_event.dart'; +part 'product_loader_state.dart'; +part 'product_loader_bloc.freezed.dart'; + +@injectable +class ProductLoaderBloc extends Bloc { + final IProductRepository _productRepository; + + ProductLoaderBloc(this._productRepository) + : super(ProductLoaderState.initial()) { + on(_onProductLoaderEvent); + } + + Future _onProductLoaderEvent( + ProductLoaderEvent event, + Emitter emit, + ) { + return event.map( + categoryIdChanged: (e) async { + emit(state.copyWith(categoryId: e.categoryId)); + }, + searchChanged: (e) async { + emit(state.copyWith(search: e.search)); + }, + fetched: (e) async { + var newState = state; + + if (e.isRefresh) { + newState = state.copyWith(isFetching: true); + + emit(newState); + } + + newState = await _mapFetchedToState(state, isRefresh: e.isRefresh); + + emit(newState); + }, + ); + } + + Future _mapFetchedToState( + ProductLoaderState state, { + bool isRefresh = false, + }) async { + state = state.copyWith(isFetching: false); + + if (state.hasReachedMax && state.products.isNotEmpty && !isRefresh) { + return state; + } + + if (isRefresh) { + state = state.copyWith( + page: 1, + failureOptionProduct: none(), + hasReachedMax: false, + products: [], + ); + } + + final failureOrProduct = await _productRepository.get( + categoryId: state.categoryId, + page: state.page, + search: state.search, + ); + + state = failureOrProduct.fold( + (f) { + if (state.products.isNotEmpty) { + return state.copyWith(hasReachedMax: true); + } + return state.copyWith(failureOptionProduct: optionOf(f)); + }, + (products) { + return state.copyWith( + products: List.from(state.products)..addAll(products), + failureOptionProduct: none(), + page: state.page + 1, + hasReachedMax: products.length < 10, + ); + }, + ); + + return state; + } +} diff --git a/lib/application/product/product_loader/product_loader_bloc.freezed.dart b/lib/application/product/product_loader/product_loader_bloc.freezed.dart new file mode 100644 index 0000000..73ea5b2 --- /dev/null +++ b/lib/application/product/product_loader/product_loader_bloc.freezed.dart @@ -0,0 +1,821 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'product_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$ProductLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function(String categoryId) categoryIdChanged, + required TResult Function(String search) searchChanged, + required TResult Function(bool isRefresh) fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String categoryId)? categoryIdChanged, + TResult? Function(String search)? searchChanged, + TResult? Function(bool isRefresh)? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String categoryId)? categoryIdChanged, + TResult Function(String search)? searchChanged, + TResult Function(bool isRefresh)? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_CategoryIdChanged value) categoryIdChanged, + required TResult Function(_SearchChanged value) searchChanged, + required TResult Function(_Fetched value) fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_CategoryIdChanged value)? categoryIdChanged, + TResult? Function(_SearchChanged value)? searchChanged, + TResult? Function(_Fetched value)? fetched, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_CategoryIdChanged value)? categoryIdChanged, + TResult Function(_SearchChanged value)? searchChanged, + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductLoaderEventCopyWith<$Res> { + factory $ProductLoaderEventCopyWith( + ProductLoaderEvent value, + $Res Function(ProductLoaderEvent) then, + ) = _$ProductLoaderEventCopyWithImpl<$Res, ProductLoaderEvent>; +} + +/// @nodoc +class _$ProductLoaderEventCopyWithImpl<$Res, $Val extends ProductLoaderEvent> + implements $ProductLoaderEventCopyWith<$Res> { + _$ProductLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$CategoryIdChangedImplCopyWith<$Res> { + factory _$$CategoryIdChangedImplCopyWith( + _$CategoryIdChangedImpl value, + $Res Function(_$CategoryIdChangedImpl) then, + ) = __$$CategoryIdChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String categoryId}); +} + +/// @nodoc +class __$$CategoryIdChangedImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$CategoryIdChangedImpl> + implements _$$CategoryIdChangedImplCopyWith<$Res> { + __$$CategoryIdChangedImplCopyWithImpl( + _$CategoryIdChangedImpl _value, + $Res Function(_$CategoryIdChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? categoryId = null}) { + return _then( + _$CategoryIdChangedImpl( + null == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$CategoryIdChangedImpl implements _CategoryIdChanged { + const _$CategoryIdChangedImpl(this.categoryId); + + @override + final String categoryId; + + @override + String toString() { + return 'ProductLoaderEvent.categoryIdChanged(categoryId: $categoryId)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$CategoryIdChangedImpl && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId)); + } + + @override + int get hashCode => Object.hash(runtimeType, categoryId); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$CategoryIdChangedImplCopyWith<_$CategoryIdChangedImpl> get copyWith => + __$$CategoryIdChangedImplCopyWithImpl<_$CategoryIdChangedImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String categoryId) categoryIdChanged, + required TResult Function(String search) searchChanged, + required TResult Function(bool isRefresh) fetched, + }) { + return categoryIdChanged(categoryId); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String categoryId)? categoryIdChanged, + TResult? Function(String search)? searchChanged, + TResult? Function(bool isRefresh)? fetched, + }) { + return categoryIdChanged?.call(categoryId); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String categoryId)? categoryIdChanged, + TResult Function(String search)? searchChanged, + TResult Function(bool isRefresh)? fetched, + required TResult orElse(), + }) { + if (categoryIdChanged != null) { + return categoryIdChanged(categoryId); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_CategoryIdChanged value) categoryIdChanged, + required TResult Function(_SearchChanged value) searchChanged, + required TResult Function(_Fetched value) fetched, + }) { + return categoryIdChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_CategoryIdChanged value)? categoryIdChanged, + TResult? Function(_SearchChanged value)? searchChanged, + TResult? Function(_Fetched value)? fetched, + }) { + return categoryIdChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_CategoryIdChanged value)? categoryIdChanged, + TResult Function(_SearchChanged value)? searchChanged, + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) { + if (categoryIdChanged != null) { + return categoryIdChanged(this); + } + return orElse(); + } +} + +abstract class _CategoryIdChanged implements ProductLoaderEvent { + const factory _CategoryIdChanged(final String categoryId) = + _$CategoryIdChangedImpl; + + String get categoryId; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$CategoryIdChangedImplCopyWith<_$CategoryIdChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SearchChangedImplCopyWith<$Res> { + factory _$$SearchChangedImplCopyWith( + _$SearchChangedImpl value, + $Res Function(_$SearchChangedImpl) then, + ) = __$$SearchChangedImplCopyWithImpl<$Res>; + @useResult + $Res call({String search}); +} + +/// @nodoc +class __$$SearchChangedImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$SearchChangedImpl> + implements _$$SearchChangedImplCopyWith<$Res> { + __$$SearchChangedImplCopyWithImpl( + _$SearchChangedImpl _value, + $Res Function(_$SearchChangedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? search = null}) { + return _then( + _$SearchChangedImpl( + null == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$SearchChangedImpl implements _SearchChanged { + const _$SearchChangedImpl(this.search); + + @override + final String search; + + @override + String toString() { + return 'ProductLoaderEvent.searchChanged(search: $search)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SearchChangedImpl && + (identical(other.search, search) || other.search == search)); + } + + @override + int get hashCode => Object.hash(runtimeType, search); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SearchChangedImplCopyWith<_$SearchChangedImpl> get copyWith => + __$$SearchChangedImplCopyWithImpl<_$SearchChangedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String categoryId) categoryIdChanged, + required TResult Function(String search) searchChanged, + required TResult Function(bool isRefresh) fetched, + }) { + return searchChanged(search); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String categoryId)? categoryIdChanged, + TResult? Function(String search)? searchChanged, + TResult? Function(bool isRefresh)? fetched, + }) { + return searchChanged?.call(search); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String categoryId)? categoryIdChanged, + TResult Function(String search)? searchChanged, + TResult Function(bool isRefresh)? fetched, + required TResult orElse(), + }) { + if (searchChanged != null) { + return searchChanged(search); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_CategoryIdChanged value) categoryIdChanged, + required TResult Function(_SearchChanged value) searchChanged, + required TResult Function(_Fetched value) fetched, + }) { + return searchChanged(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_CategoryIdChanged value)? categoryIdChanged, + TResult? Function(_SearchChanged value)? searchChanged, + TResult? Function(_Fetched value)? fetched, + }) { + return searchChanged?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_CategoryIdChanged value)? categoryIdChanged, + TResult Function(_SearchChanged value)? searchChanged, + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) { + if (searchChanged != null) { + return searchChanged(this); + } + return orElse(); + } +} + +abstract class _SearchChanged implements ProductLoaderEvent { + const factory _SearchChanged(final String search) = _$SearchChangedImpl; + + String get search; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SearchChangedImplCopyWith<_$SearchChangedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$FetchedImplCopyWith<$Res> { + factory _$$FetchedImplCopyWith( + _$FetchedImpl value, + $Res Function(_$FetchedImpl) then, + ) = __$$FetchedImplCopyWithImpl<$Res>; + @useResult + $Res call({bool isRefresh}); +} + +/// @nodoc +class __$$FetchedImplCopyWithImpl<$Res> + extends _$ProductLoaderEventCopyWithImpl<$Res, _$FetchedImpl> + implements _$$FetchedImplCopyWith<$Res> { + __$$FetchedImplCopyWithImpl( + _$FetchedImpl _value, + $Res Function(_$FetchedImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? isRefresh = null}) { + return _then( + _$FetchedImpl( + isRefresh: null == isRefresh + ? _value.isRefresh + : isRefresh // ignore: cast_nullable_to_non_nullable + as bool, + ), + ); + } +} + +/// @nodoc + +class _$FetchedImpl implements _Fetched { + const _$FetchedImpl({this.isRefresh = false}); + + @override + @JsonKey() + final bool isRefresh; + + @override + String toString() { + return 'ProductLoaderEvent.fetched(isRefresh: $isRefresh)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$FetchedImpl && + (identical(other.isRefresh, isRefresh) || + other.isRefresh == isRefresh)); + } + + @override + int get hashCode => Object.hash(runtimeType, isRefresh); + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$FetchedImplCopyWith<_$FetchedImpl> get copyWith => + __$$FetchedImplCopyWithImpl<_$FetchedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String categoryId) categoryIdChanged, + required TResult Function(String search) searchChanged, + required TResult Function(bool isRefresh) fetched, + }) { + return fetched(isRefresh); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String categoryId)? categoryIdChanged, + TResult? Function(String search)? searchChanged, + TResult? Function(bool isRefresh)? fetched, + }) { + return fetched?.call(isRefresh); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String categoryId)? categoryIdChanged, + TResult Function(String search)? searchChanged, + TResult Function(bool isRefresh)? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(isRefresh); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_CategoryIdChanged value) categoryIdChanged, + required TResult Function(_SearchChanged value) searchChanged, + required TResult Function(_Fetched value) fetched, + }) { + return fetched(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_CategoryIdChanged value)? categoryIdChanged, + TResult? Function(_SearchChanged value)? searchChanged, + TResult? Function(_Fetched value)? fetched, + }) { + return fetched?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_CategoryIdChanged value)? categoryIdChanged, + TResult Function(_SearchChanged value)? searchChanged, + TResult Function(_Fetched value)? fetched, + required TResult orElse(), + }) { + if (fetched != null) { + return fetched(this); + } + return orElse(); + } +} + +abstract class _Fetched implements ProductLoaderEvent { + const factory _Fetched({final bool isRefresh}) = _$FetchedImpl; + + bool get isRefresh; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$FetchedImplCopyWith<_$FetchedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProductLoaderState { + List get products => throw _privateConstructorUsedError; + Option get failureOptionProduct => + throw _privateConstructorUsedError; + String? get categoryId => throw _privateConstructorUsedError; + String? get search => throw _privateConstructorUsedError; + bool get isFetching => throw _privateConstructorUsedError; + bool get hasReachedMax => throw _privateConstructorUsedError; + int get page => throw _privateConstructorUsedError; + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductLoaderStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductLoaderStateCopyWith<$Res> { + factory $ProductLoaderStateCopyWith( + ProductLoaderState value, + $Res Function(ProductLoaderState) then, + ) = _$ProductLoaderStateCopyWithImpl<$Res, ProductLoaderState>; + @useResult + $Res call({ + List products, + Option failureOptionProduct, + String? categoryId, + String? search, + bool isFetching, + bool hasReachedMax, + int page, + }); +} + +/// @nodoc +class _$ProductLoaderStateCopyWithImpl<$Res, $Val extends ProductLoaderState> + implements $ProductLoaderStateCopyWith<$Res> { + _$ProductLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? failureOptionProduct = null, + Object? categoryId = freezed, + Object? search = freezed, + Object? isFetching = null, + Object? hasReachedMax = null, + Object? page = null, + }) { + return _then( + _value.copyWith( + products: null == products + ? _value.products + : products // ignore: cast_nullable_to_non_nullable + as List, + failureOptionProduct: null == failureOptionProduct + ? _value.failureOptionProduct + : failureOptionProduct // ignore: cast_nullable_to_non_nullable + as Option, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + 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, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductLoaderStateImplCopyWith<$Res> + implements $ProductLoaderStateCopyWith<$Res> { + factory _$$ProductLoaderStateImplCopyWith( + _$ProductLoaderStateImpl value, + $Res Function(_$ProductLoaderStateImpl) then, + ) = __$$ProductLoaderStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + List products, + Option failureOptionProduct, + String? categoryId, + String? search, + bool isFetching, + bool hasReachedMax, + int page, + }); +} + +/// @nodoc +class __$$ProductLoaderStateImplCopyWithImpl<$Res> + extends _$ProductLoaderStateCopyWithImpl<$Res, _$ProductLoaderStateImpl> + implements _$$ProductLoaderStateImplCopyWith<$Res> { + __$$ProductLoaderStateImplCopyWithImpl( + _$ProductLoaderStateImpl _value, + $Res Function(_$ProductLoaderStateImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? products = null, + Object? failureOptionProduct = null, + Object? categoryId = freezed, + Object? search = freezed, + Object? isFetching = null, + Object? hasReachedMax = null, + Object? page = null, + }) { + return _then( + _$ProductLoaderStateImpl( + products: null == products + ? _value._products + : products // ignore: cast_nullable_to_non_nullable + as List, + failureOptionProduct: null == failureOptionProduct + ? _value.failureOptionProduct + : failureOptionProduct // ignore: cast_nullable_to_non_nullable + as Option, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + search: freezed == search + ? _value.search + : search // ignore: cast_nullable_to_non_nullable + as String?, + isFetching: null == isFetching + ? _value.isFetching + : isFetching // ignore: cast_nullable_to_non_nullable + as bool, + 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, + ), + ); + } +} + +/// @nodoc + +class _$ProductLoaderStateImpl implements _ProductLoaderState { + const _$ProductLoaderStateImpl({ + required final List products, + required this.failureOptionProduct, + this.categoryId, + this.search, + this.isFetching = false, + this.hasReachedMax = false, + this.page = 1, + }) : _products = products; + + final List _products; + @override + List get products { + if (_products is EqualUnmodifiableListView) return _products; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_products); + } + + @override + final Option failureOptionProduct; + @override + final String? categoryId; + @override + final String? search; + @override + @JsonKey() + final bool isFetching; + @override + @JsonKey() + final bool hasReachedMax; + @override + @JsonKey() + final int page; + + @override + String toString() { + return 'ProductLoaderState(products: $products, failureOptionProduct: $failureOptionProduct, categoryId: $categoryId, search: $search, isFetching: $isFetching, hasReachedMax: $hasReachedMax, page: $page)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductLoaderStateImpl && + const DeepCollectionEquality().equals(other._products, _products) && + (identical(other.failureOptionProduct, failureOptionProduct) || + other.failureOptionProduct == failureOptionProduct) && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.search, search) || other.search == search) && + (identical(other.isFetching, isFetching) || + other.isFetching == isFetching) && + (identical(other.hasReachedMax, hasReachedMax) || + other.hasReachedMax == hasReachedMax) && + (identical(other.page, page) || other.page == page)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_products), + failureOptionProduct, + categoryId, + search, + isFetching, + hasReachedMax, + page, + ); + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductLoaderStateImplCopyWith<_$ProductLoaderStateImpl> get copyWith => + __$$ProductLoaderStateImplCopyWithImpl<_$ProductLoaderStateImpl>( + this, + _$identity, + ); +} + +abstract class _ProductLoaderState implements ProductLoaderState { + const factory _ProductLoaderState({ + required final List products, + required final Option failureOptionProduct, + final String? categoryId, + final String? search, + final bool isFetching, + final bool hasReachedMax, + final int page, + }) = _$ProductLoaderStateImpl; + + @override + List get products; + @override + Option get failureOptionProduct; + @override + String? get categoryId; + @override + String? get search; + @override + bool get isFetching; + @override + bool get hasReachedMax; + @override + int get page; + + /// Create a copy of ProductLoaderState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductLoaderStateImplCopyWith<_$ProductLoaderStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/application/product/product_loader/product_loader_event.dart b/lib/application/product/product_loader/product_loader_event.dart new file mode 100644 index 0000000..1c6838c --- /dev/null +++ b/lib/application/product/product_loader/product_loader_event.dart @@ -0,0 +1,11 @@ +part of 'product_loader_bloc.dart'; + +@freezed +class ProductLoaderEvent with _$ProductLoaderEvent { + const factory ProductLoaderEvent.categoryIdChanged(String categoryId) = + _CategoryIdChanged; + const factory ProductLoaderEvent.searchChanged(String search) = + _SearchChanged; + const factory ProductLoaderEvent.fetched({@Default(false) bool isRefresh}) = + _Fetched; +} diff --git a/lib/application/product/product_loader/product_loader_state.dart b/lib/application/product/product_loader/product_loader_state.dart new file mode 100644 index 0000000..4d8856a --- /dev/null +++ b/lib/application/product/product_loader/product_loader_state.dart @@ -0,0 +1,17 @@ +part of 'product_loader_bloc.dart'; + +@freezed +class ProductLoaderState with _$ProductLoaderState { + const factory ProductLoaderState({ + required List products, + required Option failureOptionProduct, + String? categoryId, + String? search, + @Default(false) bool isFetching, + @Default(false) bool hasReachedMax, + @Default(1) int page, + }) = _ProductLoaderState; + + factory ProductLoaderState.initial() => + ProductLoaderState(products: [], failureOptionProduct: none()); +} diff --git a/lib/common/url/api_path.dart b/lib/common/url/api_path.dart index 17064a4..d71dcfd 100644 --- a/lib/common/url/api_path.dart +++ b/lib/common/url/api_path.dart @@ -8,4 +8,7 @@ class ApiPath { // Category static const String category = '/api/v1/categories'; + + // Product + static const String product = '/api/v1/products'; } diff --git a/lib/domain/product/entities/product_entity.dart b/lib/domain/product/entities/product_entity.dart new file mode 100644 index 0000000..c461e5e --- /dev/null +++ b/lib/domain/product/entities/product_entity.dart @@ -0,0 +1,43 @@ +part of '../product.dart'; + +@freezed +class Product with _$Product { + const factory Product({ + required String id, + required String organizationId, + required String categoryId, + required String sku, + required String name, + required String description, + required int price, + required int cost, + required String businessType, + required String imageUrl, + required String printerType, + required Map metadata, + required bool isActive, + required DateTime createdAt, + required DateTime updatedAt, + required List variants, + }) = _Product; + + /// ✅ factory kosong untuk default state + factory Product.empty() => Product( + id: '', + organizationId: '', + categoryId: '', + sku: '', + name: '', + description: '', + price: 0, + cost: 0, + businessType: '', + imageUrl: '', + printerType: '', + metadata: {}, + isActive: false, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + variants: [], + ); +} diff --git a/lib/domain/product/entities/product_variant_entity.dart b/lib/domain/product/entities/product_variant_entity.dart new file mode 100644 index 0000000..d8cb985 --- /dev/null +++ b/lib/domain/product/entities/product_variant_entity.dart @@ -0,0 +1,26 @@ +part of '../product.dart'; + +@freezed +class ProductVariant with _$ProductVariant { + const factory ProductVariant({ + required String id, + required String productId, + required String name, + required int priceModifier, + required int cost, + required Map metadata, + required DateTime createdAt, + required DateTime updatedAt, + }) = _ProductVariant; + + factory ProductVariant.empty() => ProductVariant( + id: '', + productId: '', + name: '', + priceModifier: 0, + cost: 0, + metadata: {}, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ); +} diff --git a/lib/domain/product/failures/product_failure.dart b/lib/domain/product/failures/product_failure.dart new file mode 100644 index 0000000..97612c1 --- /dev/null +++ b/lib/domain/product/failures/product_failure.dart @@ -0,0 +1,10 @@ +part of '../product.dart'; + +@freezed +sealed class ProductFailure with _$ProductFailure { + const factory ProductFailure.serverError(ApiFailure failure) = _ServerError; + const factory ProductFailure.unexpectedError() = _UnexpectedError; + const factory ProductFailure.empty() = _Empty; + const factory ProductFailure.dynamicErrorMessage(String erroMessage) = + _DynamicErrorMessage; +} diff --git a/lib/domain/product/product.dart b/lib/domain/product/product.dart new file mode 100644 index 0000000..97aa7ee --- /dev/null +++ b/lib/domain/product/product.dart @@ -0,0 +1,11 @@ +import 'package:dartz/dartz.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../common/api/api_failure.dart'; + +part 'product.freezed.dart'; + +part 'entities/product_entity.dart'; +part 'entities/product_variant_entity.dart'; +part 'failures/product_failure.dart'; +part 'repositories/i_product_repository.dart'; diff --git a/lib/domain/product/product.freezed.dart b/lib/domain/product/product.freezed.dart new file mode 100644 index 0000000..75b42f9 --- /dev/null +++ b/lib/domain/product/product.freezed.dart @@ -0,0 +1,1435 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'product.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +/// @nodoc +mixin _$Product { + String get id => throw _privateConstructorUsedError; + String get organizationId => throw _privateConstructorUsedError; + String get categoryId => throw _privateConstructorUsedError; + String get sku => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + int get price => throw _privateConstructorUsedError; + int get cost => throw _privateConstructorUsedError; + String get businessType => throw _privateConstructorUsedError; + String get imageUrl => throw _privateConstructorUsedError; + String get printerType => throw _privateConstructorUsedError; + Map get metadata => throw _privateConstructorUsedError; + bool get isActive => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + List get variants => throw _privateConstructorUsedError; + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductCopyWith get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductCopyWith<$Res> { + factory $ProductCopyWith(Product value, $Res Function(Product) then) = + _$ProductCopyWithImpl<$Res, Product>; + @useResult + $Res call({ + String id, + String organizationId, + String categoryId, + String sku, + String name, + String description, + int price, + int cost, + String businessType, + String imageUrl, + String printerType, + Map metadata, + bool isActive, + DateTime createdAt, + DateTime updatedAt, + List variants, + }); +} + +/// @nodoc +class _$ProductCopyWithImpl<$Res, $Val extends Product> + implements $ProductCopyWith<$Res> { + _$ProductCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? organizationId = null, + Object? categoryId = null, + Object? sku = null, + Object? name = null, + Object? description = null, + Object? price = null, + Object? cost = null, + Object? businessType = null, + Object? imageUrl = null, + Object? printerType = null, + Object? metadata = null, + Object? isActive = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? variants = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + organizationId: null == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String, + categoryId: null == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String, + sku: null == sku + ? _value.sku + : sku // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as int, + cost: null == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as int, + businessType: null == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + printerType: null == printerType + ? _value.printerType + : printerType // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + variants: null == variants + ? _value.variants + : variants // ignore: cast_nullable_to_non_nullable + as List, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductImplCopyWith<$Res> implements $ProductCopyWith<$Res> { + factory _$$ProductImplCopyWith( + _$ProductImpl value, + $Res Function(_$ProductImpl) then, + ) = __$$ProductImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String organizationId, + String categoryId, + String sku, + String name, + String description, + int price, + int cost, + String businessType, + String imageUrl, + String printerType, + Map metadata, + bool isActive, + DateTime createdAt, + DateTime updatedAt, + List variants, + }); +} + +/// @nodoc +class __$$ProductImplCopyWithImpl<$Res> + extends _$ProductCopyWithImpl<$Res, _$ProductImpl> + implements _$$ProductImplCopyWith<$Res> { + __$$ProductImplCopyWithImpl( + _$ProductImpl _value, + $Res Function(_$ProductImpl) _then, + ) : super(_value, _then); + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? organizationId = null, + Object? categoryId = null, + Object? sku = null, + Object? name = null, + Object? description = null, + Object? price = null, + Object? cost = null, + Object? businessType = null, + Object? imageUrl = null, + Object? printerType = null, + Object? metadata = null, + Object? isActive = null, + Object? createdAt = null, + Object? updatedAt = null, + Object? variants = null, + }) { + return _then( + _$ProductImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + organizationId: null == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String, + categoryId: null == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String, + sku: null == sku + ? _value.sku + : sku // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + price: null == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as int, + cost: null == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as int, + businessType: null == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String, + imageUrl: null == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String, + printerType: null == printerType + ? _value.printerType + : printerType // ignore: cast_nullable_to_non_nullable + as String, + metadata: null == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map, + isActive: null == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool, + createdAt: null == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + variants: null == variants + ? _value._variants + : variants // ignore: cast_nullable_to_non_nullable + as List, + ), + ); + } +} + +/// @nodoc + +class _$ProductImpl implements _Product { + const _$ProductImpl({ + required this.id, + required this.organizationId, + required this.categoryId, + required this.sku, + required this.name, + required this.description, + required this.price, + required this.cost, + required this.businessType, + required this.imageUrl, + required this.printerType, + required final Map metadata, + required this.isActive, + required this.createdAt, + required this.updatedAt, + required final List variants, + }) : _metadata = metadata, + _variants = variants; + + @override + final String id; + @override + final String organizationId; + @override + final String categoryId; + @override + final String sku; + @override + final String name; + @override + final String description; + @override + final int price; + @override + final int cost; + @override + final String businessType; + @override + final String imageUrl; + @override + final String printerType; + final Map _metadata; + @override + Map get metadata { + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_metadata); + } + + @override + final bool isActive; + @override + final DateTime createdAt; + @override + final DateTime updatedAt; + final List _variants; + @override + List get variants { + if (_variants is EqualUnmodifiableListView) return _variants; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_variants); + } + + @override + String toString() { + return 'Product(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.organizationId, organizationId) || + other.organizationId == organizationId) && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.sku, sku) || other.sku == sku) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.price, price) || other.price == price) && + (identical(other.cost, cost) || other.cost == cost) && + (identical(other.businessType, businessType) || + other.businessType == businessType) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl) && + (identical(other.printerType, printerType) || + other.printerType == printerType) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + const DeepCollectionEquality().equals(other._variants, _variants)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + organizationId, + categoryId, + sku, + name, + description, + price, + cost, + businessType, + imageUrl, + printerType, + const DeepCollectionEquality().hash(_metadata), + isActive, + createdAt, + updatedAt, + const DeepCollectionEquality().hash(_variants), + ); + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductImplCopyWith<_$ProductImpl> get copyWith => + __$$ProductImplCopyWithImpl<_$ProductImpl>(this, _$identity); +} + +abstract class _Product implements Product { + const factory _Product({ + required final String id, + required final String organizationId, + required final String categoryId, + required final String sku, + required final String name, + required final String description, + required final int price, + required final int cost, + required final String businessType, + required final String imageUrl, + required final String printerType, + required final Map metadata, + required final bool isActive, + required final DateTime createdAt, + required final DateTime updatedAt, + required final List variants, + }) = _$ProductImpl; + + @override + String get id; + @override + String get organizationId; + @override + String get categoryId; + @override + String get sku; + @override + String get name; + @override + String get description; + @override + int get price; + @override + int get cost; + @override + String get businessType; + @override + String get imageUrl; + @override + String get printerType; + @override + Map get metadata; + @override + bool get isActive; + @override + DateTime get createdAt; + @override + DateTime get updatedAt; + @override + List get variants; + + /// Create a copy of Product + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductImplCopyWith<_$ProductImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProductVariant { + String get id => throw _privateConstructorUsedError; + String get productId => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + int get priceModifier => throw _privateConstructorUsedError; + int get cost => throw _privateConstructorUsedError; + Map get metadata => throw _privateConstructorUsedError; + DateTime get createdAt => throw _privateConstructorUsedError; + DateTime get updatedAt => throw _privateConstructorUsedError; + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductVariantCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductVariantCopyWith<$Res> { + factory $ProductVariantCopyWith( + ProductVariant value, + $Res Function(ProductVariant) then, + ) = _$ProductVariantCopyWithImpl<$Res, ProductVariant>; + @useResult + $Res call({ + String id, + String productId, + String name, + int priceModifier, + int cost, + Map metadata, + DateTime createdAt, + DateTime updatedAt, + }); +} + +/// @nodoc +class _$ProductVariantCopyWithImpl<$Res, $Val extends ProductVariant> + implements $ProductVariantCopyWith<$Res> { + _$ProductVariantCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? productId = null, + Object? name = null, + Object? priceModifier = null, + Object? cost = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + productId: null == productId + ? _value.productId + : productId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + priceModifier: null == priceModifier + ? _value.priceModifier + : priceModifier // ignore: cast_nullable_to_non_nullable + as int, + cost: null == cost + ? _value.cost + : cost // 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 DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductVariantImplCopyWith<$Res> + implements $ProductVariantCopyWith<$Res> { + factory _$$ProductVariantImplCopyWith( + _$ProductVariantImpl value, + $Res Function(_$ProductVariantImpl) then, + ) = __$$ProductVariantImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + String id, + String productId, + String name, + int priceModifier, + int cost, + Map metadata, + DateTime createdAt, + DateTime updatedAt, + }); +} + +/// @nodoc +class __$$ProductVariantImplCopyWithImpl<$Res> + extends _$ProductVariantCopyWithImpl<$Res, _$ProductVariantImpl> + implements _$$ProductVariantImplCopyWith<$Res> { + __$$ProductVariantImplCopyWithImpl( + _$ProductVariantImpl _value, + $Res Function(_$ProductVariantImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? productId = null, + Object? name = null, + Object? priceModifier = null, + Object? cost = null, + Object? metadata = null, + Object? createdAt = null, + Object? updatedAt = null, + }) { + return _then( + _$ProductVariantImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + productId: null == productId + ? _value.productId + : productId // ignore: cast_nullable_to_non_nullable + as String, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + priceModifier: null == priceModifier + ? _value.priceModifier + : priceModifier // ignore: cast_nullable_to_non_nullable + as int, + cost: null == cost + ? _value.cost + : cost // 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 DateTime, + updatedAt: null == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime, + ), + ); + } +} + +/// @nodoc + +class _$ProductVariantImpl implements _ProductVariant { + const _$ProductVariantImpl({ + required this.id, + required this.productId, + required this.name, + required this.priceModifier, + required this.cost, + required final Map metadata, + required this.createdAt, + required this.updatedAt, + }) : _metadata = metadata; + + @override + final String id; + @override + final String productId; + @override + final String name; + @override + final int priceModifier; + @override + final int cost; + final Map _metadata; + @override + Map get metadata { + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(_metadata); + } + + @override + final DateTime createdAt; + @override + final DateTime updatedAt; + + @override + String toString() { + return 'ProductVariant(id: $id, productId: $productId, name: $name, priceModifier: $priceModifier, cost: $cost, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductVariantImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.productId, productId) || + other.productId == productId) && + (identical(other.name, name) || other.name == name) && + (identical(other.priceModifier, priceModifier) || + other.priceModifier == priceModifier) && + (identical(other.cost, cost) || other.cost == cost) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + id, + productId, + name, + priceModifier, + cost, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductVariantImplCopyWith<_$ProductVariantImpl> get copyWith => + __$$ProductVariantImplCopyWithImpl<_$ProductVariantImpl>( + this, + _$identity, + ); +} + +abstract class _ProductVariant implements ProductVariant { + const factory _ProductVariant({ + required final String id, + required final String productId, + required final String name, + required final int priceModifier, + required final int cost, + required final Map metadata, + required final DateTime createdAt, + required final DateTime updatedAt, + }) = _$ProductVariantImpl; + + @override + String get id; + @override + String get productId; + @override + String get name; + @override + int get priceModifier; + @override + int get cost; + @override + Map get metadata; + @override + DateTime get createdAt; + @override + DateTime get updatedAt; + + /// Create a copy of ProductVariant + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductVariantImplCopyWith<_$ProductVariantImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProductFailure { + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + 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)? 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(_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(_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(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductFailureCopyWith<$Res> { + factory $ProductFailureCopyWith( + ProductFailure value, + $Res Function(ProductFailure) then, + ) = _$ProductFailureCopyWithImpl<$Res, ProductFailure>; +} + +/// @nodoc +class _$ProductFailureCopyWithImpl<$Res, $Val extends ProductFailure> + implements $ProductFailureCopyWith<$Res> { + _$ProductFailureCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$ServerErrorImplCopyWith<$Res> { + factory _$$ServerErrorImplCopyWith( + _$ServerErrorImpl value, + $Res Function(_$ServerErrorImpl) then, + ) = __$$ServerErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({ApiFailure failure}); + + $ApiFailureCopyWith<$Res> get failure; +} + +/// @nodoc +class __$$ServerErrorImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$ServerErrorImpl> + implements _$$ServerErrorImplCopyWith<$Res> { + __$$ServerErrorImplCopyWithImpl( + _$ServerErrorImpl _value, + $Res Function(_$ServerErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? failure = null}) { + return _then( + _$ServerErrorImpl( + null == failure + ? _value.failure + : failure // ignore: cast_nullable_to_non_nullable + as ApiFailure, + ), + ); + } + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $ApiFailureCopyWith<$Res> get failure { + return $ApiFailureCopyWith<$Res>(_value.failure, (value) { + return _then(_value.copyWith(failure: value)); + }); + } +} + +/// @nodoc + +class _$ServerErrorImpl implements _ServerError { + const _$ServerErrorImpl(this.failure); + + @override + final ApiFailure failure; + + @override + String toString() { + return 'ProductFailure.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 ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + __$$ServerErrorImplCopyWithImpl<_$ServerErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return serverError(failure); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + 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)? 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(_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(_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(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (serverError != null) { + return serverError(this); + } + return orElse(); + } +} + +abstract class _ServerError implements ProductFailure { + const factory _ServerError(final ApiFailure failure) = _$ServerErrorImpl; + + ApiFailure get failure; + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ServerErrorImplCopyWith<_$ServerErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$UnexpectedErrorImplCopyWith<$Res> { + factory _$$UnexpectedErrorImplCopyWith( + _$UnexpectedErrorImpl value, + $Res Function(_$UnexpectedErrorImpl) then, + ) = __$$UnexpectedErrorImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$UnexpectedErrorImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$UnexpectedErrorImpl> + implements _$$UnexpectedErrorImplCopyWith<$Res> { + __$$UnexpectedErrorImplCopyWithImpl( + _$UnexpectedErrorImpl _value, + $Res Function(_$UnexpectedErrorImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$UnexpectedErrorImpl implements _UnexpectedError { + const _$UnexpectedErrorImpl(); + + @override + String toString() { + return 'ProductFailure.unexpectedError()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$UnexpectedErrorImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return unexpectedError(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + 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)? 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(_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(_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(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (unexpectedError != null) { + return unexpectedError(this); + } + return orElse(); + } +} + +abstract class _UnexpectedError implements ProductFailure { + const factory _UnexpectedError() = _$UnexpectedErrorImpl; +} + +/// @nodoc +abstract class _$$EmptyImplCopyWith<$Res> { + factory _$$EmptyImplCopyWith( + _$EmptyImpl value, + $Res Function(_$EmptyImpl) then, + ) = __$$EmptyImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$EmptyImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$EmptyImpl> + implements _$$EmptyImplCopyWith<$Res> { + __$$EmptyImplCopyWithImpl( + _$EmptyImpl _value, + $Res Function(_$EmptyImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$EmptyImpl implements _Empty { + const _$EmptyImpl(); + + @override + String toString() { + return 'ProductFailure.empty()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$EmptyImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return empty(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + 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)? 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(_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(_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(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (empty != null) { + return empty(this); + } + return orElse(); + } +} + +abstract class _Empty implements ProductFailure { + const factory _Empty() = _$EmptyImpl; +} + +/// @nodoc +abstract class _$$DynamicErrorMessageImplCopyWith<$Res> { + factory _$$DynamicErrorMessageImplCopyWith( + _$DynamicErrorMessageImpl value, + $Res Function(_$DynamicErrorMessageImpl) then, + ) = __$$DynamicErrorMessageImplCopyWithImpl<$Res>; + @useResult + $Res call({String erroMessage}); +} + +/// @nodoc +class __$$DynamicErrorMessageImplCopyWithImpl<$Res> + extends _$ProductFailureCopyWithImpl<$Res, _$DynamicErrorMessageImpl> + implements _$$DynamicErrorMessageImplCopyWith<$Res> { + __$$DynamicErrorMessageImplCopyWithImpl( + _$DynamicErrorMessageImpl _value, + $Res Function(_$DynamicErrorMessageImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({Object? erroMessage = null}) { + return _then( + _$DynamicErrorMessageImpl( + null == erroMessage + ? _value.erroMessage + : erroMessage // ignore: cast_nullable_to_non_nullable + as String, + ), + ); + } +} + +/// @nodoc + +class _$DynamicErrorMessageImpl implements _DynamicErrorMessage { + const _$DynamicErrorMessageImpl(this.erroMessage); + + @override + final String erroMessage; + + @override + String toString() { + return 'ProductFailure.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 ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + __$$DynamicErrorMessageImplCopyWithImpl<_$DynamicErrorMessageImpl>( + this, + _$identity, + ); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(ApiFailure failure) serverError, + required TResult Function() unexpectedError, + required TResult Function() empty, + required TResult Function(String erroMessage) dynamicErrorMessage, + }) { + return dynamicErrorMessage(erroMessage); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(ApiFailure failure)? serverError, + TResult? Function()? unexpectedError, + TResult? Function()? empty, + 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)? 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(_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(_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(_DynamicErrorMessage value)? dynamicErrorMessage, + required TResult orElse(), + }) { + if (dynamicErrorMessage != null) { + return dynamicErrorMessage(this); + } + return orElse(); + } +} + +abstract class _DynamicErrorMessage implements ProductFailure { + const factory _DynamicErrorMessage(final String erroMessage) = + _$DynamicErrorMessageImpl; + + String get erroMessage; + + /// Create a copy of ProductFailure + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$DynamicErrorMessageImplCopyWith<_$DynamicErrorMessageImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/domain/product/repositories/i_product_repository.dart b/lib/domain/product/repositories/i_product_repository.dart new file mode 100644 index 0000000..e357866 --- /dev/null +++ b/lib/domain/product/repositories/i_product_repository.dart @@ -0,0 +1,10 @@ +part of '../product.dart'; + +abstract class IProductRepository { + Future>> get({ + int page = 1, + int limit = 10, + String? categoryId, + String? search, + }); +} diff --git a/lib/infrastructure/product/datasources/remote_data_provider.dart b/lib/infrastructure/product/datasources/remote_data_provider.dart new file mode 100644 index 0000000..9d3db60 --- /dev/null +++ b/lib/infrastructure/product/datasources/remote_data_provider.dart @@ -0,0 +1,52 @@ +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/url/api_path.dart'; +import '../../../domain/product/product.dart'; +import '../product_dtos.dart'; + +@injectable +class ProductRemoteDataProvider { + final ApiClient _apiClient; + final String _logName = 'ProductRemoteDataProvider'; + + ProductRemoteDataProvider(this._apiClient); + + Future>> fetch({ + int page = 1, + int limit = 10, + String? categoryId, + String? search, + }) async { + try { + Map params = {'page': page, 'limit': limit}; + + if (categoryId != null) { + params['category_id'] = categoryId; + } + + if (search != null) { + params['search'] = search; + } + + final response = await _apiClient.get(ApiPath.product, params: params); + + if (response.data['data'] == null) { + return DC.error(ProductFailure.empty()); + } + + final dto = (response.data['data']['products'] as List) + .map((item) => ProductDto.fromJson(item)) + .toList(); + + return DC.data(dto); + } on ApiFailure catch (e, s) { + log('fetchProductError', name: _logName, error: e, stackTrace: s); + return DC.error(ProductFailure.serverError(e)); + } + } +} diff --git a/lib/infrastructure/product/dto/product_dto.dart b/lib/infrastructure/product/dto/product_dto.dart new file mode 100644 index 0000000..03b0b39 --- /dev/null +++ b/lib/infrastructure/product/dto/product_dto.dart @@ -0,0 +1,70 @@ +part of '../product_dtos.dart'; + +@freezed +class ProductDto with _$ProductDto { + const ProductDto._(); + + const factory ProductDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'organization_id') String? organizationId, + @JsonKey(name: 'category_id') String? categoryId, + @JsonKey(name: 'sku') String? sku, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'description') String? description, + @JsonKey(name: 'price') int? price, + @JsonKey(name: 'cost') int? cost, + @JsonKey(name: 'business_type') String? businessType, + @JsonKey(name: 'image_url') String? imageUrl, + @JsonKey(name: 'printer_type') String? printerType, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'created_at') DateTime? createdAt, + @JsonKey(name: 'updated_at') DateTime? updatedAt, + @JsonKey(name: 'variants') List? variants, + }) = _ProductDto; + + factory ProductDto.fromJson(Map json) => + _$ProductDtoFromJson(json); + + /// DTO -> Domain (isi default kalau null) + Product toDomain() => Product( + id: id ?? '', + organizationId: organizationId ?? '', + categoryId: categoryId ?? '', + sku: sku ?? '', + name: name ?? '', + description: description ?? '', + price: price ?? 0, + cost: cost ?? 0, + businessType: businessType ?? '', + imageUrl: imageUrl ?? '', + printerType: printerType ?? '', + metadata: metadata ?? {}, + isActive: isActive ?? false, + createdAt: createdAt ?? DateTime.now(), + updatedAt: updatedAt ?? DateTime.now(), + variants: variants?.map((v) => v.toDomain()).toList() ?? [], + ); + + /// Domain -> DTO + factory ProductDto.fromDomain(Product product) => ProductDto( + id: product.id, + organizationId: product.organizationId, + categoryId: product.categoryId, + sku: product.sku, + name: product.name, + description: product.description, + price: product.price, + cost: product.cost, + businessType: product.businessType, + imageUrl: product.imageUrl, + printerType: product.printerType, + metadata: product.metadata, + isActive: product.isActive, + createdAt: product.createdAt, + updatedAt: product.updatedAt, + variants: product.variants + .map((v) => ProductVariantDto.fromDomain(v)) + .toList(), + ); +} diff --git a/lib/infrastructure/product/dto/product_variant_dto.dart b/lib/infrastructure/product/dto/product_variant_dto.dart new file mode 100644 index 0000000..10b94b4 --- /dev/null +++ b/lib/infrastructure/product/dto/product_variant_dto.dart @@ -0,0 +1,45 @@ +part of '../product_dtos.dart'; + +@freezed +class ProductVariantDto with _$ProductVariantDto { + const ProductVariantDto._(); + + const factory ProductVariantDto({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'product_id') String? productId, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'price_modifier') int? priceModifier, + @JsonKey(name: 'cost') int? cost, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') DateTime? createdAt, + @JsonKey(name: 'updated_at') DateTime? updatedAt, + }) = _ProductVariantDto; + + factory ProductVariantDto.fromJson(Map json) => + _$ProductVariantDtoFromJson(json); + + /// DTO -> Domain + ProductVariant toDomain() => ProductVariant( + id: id ?? '', + productId: productId ?? '', + name: name ?? '', + priceModifier: priceModifier ?? 0, + cost: cost ?? 0, + metadata: metadata ?? {}, + createdAt: createdAt ?? DateTime.now(), + updatedAt: updatedAt ?? DateTime.now(), + ); + + /// Domain -> DTO + factory ProductVariantDto.fromDomain(ProductVariant variant) => + ProductVariantDto( + id: variant.id, + productId: variant.productId, + name: variant.name, + priceModifier: variant.priceModifier, + cost: variant.cost, + metadata: variant.metadata, + createdAt: variant.createdAt, + updatedAt: variant.updatedAt, + ); +} diff --git a/lib/infrastructure/product/product_dtos.dart b/lib/infrastructure/product/product_dtos.dart new file mode 100644 index 0000000..1d606ca --- /dev/null +++ b/lib/infrastructure/product/product_dtos.dart @@ -0,0 +1,9 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; + +import '../../domain/product/product.dart'; + +part 'product_dtos.freezed.dart'; +part 'product_dtos.g.dart'; + +part 'dto/product_dto.dart'; +part 'dto/product_variant_dto.dart'; diff --git a/lib/infrastructure/product/product_dtos.freezed.dart b/lib/infrastructure/product/product_dtos.freezed.dart new file mode 100644 index 0000000..61a3ed4 --- /dev/null +++ b/lib/infrastructure/product/product_dtos.freezed.dart @@ -0,0 +1,926 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'product_dtos.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models', +); + +ProductDto _$ProductDtoFromJson(Map json) { + return _ProductDto.fromJson(json); +} + +/// @nodoc +mixin _$ProductDto { + @JsonKey(name: 'id') + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: 'organization_id') + String? get organizationId => throw _privateConstructorUsedError; + @JsonKey(name: 'category_id') + String? get categoryId => throw _privateConstructorUsedError; + @JsonKey(name: 'sku') + String? get sku => throw _privateConstructorUsedError; + @JsonKey(name: 'name') + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: 'description') + String? get description => throw _privateConstructorUsedError; + @JsonKey(name: 'price') + int? get price => throw _privateConstructorUsedError; + @JsonKey(name: 'cost') + int? get cost => throw _privateConstructorUsedError; + @JsonKey(name: 'business_type') + String? get businessType => throw _privateConstructorUsedError; + @JsonKey(name: 'image_url') + String? get imageUrl => throw _privateConstructorUsedError; + @JsonKey(name: 'printer_type') + String? get printerType => throw _privateConstructorUsedError; + @JsonKey(name: 'metadata') + Map? get metadata => throw _privateConstructorUsedError; + @JsonKey(name: 'is_active') + bool? get isActive => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at') + DateTime? get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: 'updated_at') + DateTime? get updatedAt => throw _privateConstructorUsedError; + @JsonKey(name: 'variants') + List? get variants => throw _privateConstructorUsedError; + + /// Serializes this ProductDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductDtoCopyWith<$Res> { + factory $ProductDtoCopyWith( + ProductDto value, + $Res Function(ProductDto) then, + ) = _$ProductDtoCopyWithImpl<$Res, ProductDto>; + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'organization_id') String? organizationId, + @JsonKey(name: 'category_id') String? categoryId, + @JsonKey(name: 'sku') String? sku, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'description') String? description, + @JsonKey(name: 'price') int? price, + @JsonKey(name: 'cost') int? cost, + @JsonKey(name: 'business_type') String? businessType, + @JsonKey(name: 'image_url') String? imageUrl, + @JsonKey(name: 'printer_type') String? printerType, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'created_at') DateTime? createdAt, + @JsonKey(name: 'updated_at') DateTime? updatedAt, + @JsonKey(name: 'variants') List? variants, + }); +} + +/// @nodoc +class _$ProductDtoCopyWithImpl<$Res, $Val extends ProductDto> + implements $ProductDtoCopyWith<$Res> { + _$ProductDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? organizationId = freezed, + Object? categoryId = freezed, + Object? sku = freezed, + Object? name = freezed, + Object? description = freezed, + Object? price = freezed, + Object? cost = freezed, + Object? businessType = freezed, + Object? imageUrl = freezed, + Object? printerType = freezed, + Object? metadata = freezed, + Object? isActive = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + Object? variants = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + organizationId: freezed == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String?, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + sku: freezed == sku + ? _value.sku + : sku // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + price: freezed == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as int?, + cost: freezed == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as int?, + businessType: freezed == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String?, + imageUrl: freezed == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String?, + printerType: freezed == printerType + ? _value.printerType + : printerType // ignore: cast_nullable_to_non_nullable + as String?, + metadata: freezed == metadata + ? _value.metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + variants: freezed == variants + ? _value.variants + : variants // ignore: cast_nullable_to_non_nullable + as List?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductDtoImplCopyWith<$Res> + implements $ProductDtoCopyWith<$Res> { + factory _$$ProductDtoImplCopyWith( + _$ProductDtoImpl value, + $Res Function(_$ProductDtoImpl) then, + ) = __$$ProductDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'organization_id') String? organizationId, + @JsonKey(name: 'category_id') String? categoryId, + @JsonKey(name: 'sku') String? sku, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'description') String? description, + @JsonKey(name: 'price') int? price, + @JsonKey(name: 'cost') int? cost, + @JsonKey(name: 'business_type') String? businessType, + @JsonKey(name: 'image_url') String? imageUrl, + @JsonKey(name: 'printer_type') String? printerType, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'is_active') bool? isActive, + @JsonKey(name: 'created_at') DateTime? createdAt, + @JsonKey(name: 'updated_at') DateTime? updatedAt, + @JsonKey(name: 'variants') List? variants, + }); +} + +/// @nodoc +class __$$ProductDtoImplCopyWithImpl<$Res> + extends _$ProductDtoCopyWithImpl<$Res, _$ProductDtoImpl> + implements _$$ProductDtoImplCopyWith<$Res> { + __$$ProductDtoImplCopyWithImpl( + _$ProductDtoImpl _value, + $Res Function(_$ProductDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? organizationId = freezed, + Object? categoryId = freezed, + Object? sku = freezed, + Object? name = freezed, + Object? description = freezed, + Object? price = freezed, + Object? cost = freezed, + Object? businessType = freezed, + Object? imageUrl = freezed, + Object? printerType = freezed, + Object? metadata = freezed, + Object? isActive = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + Object? variants = freezed, + }) { + return _then( + _$ProductDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + organizationId: freezed == organizationId + ? _value.organizationId + : organizationId // ignore: cast_nullable_to_non_nullable + as String?, + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + sku: freezed == sku + ? _value.sku + : sku // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + description: freezed == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String?, + price: freezed == price + ? _value.price + : price // ignore: cast_nullable_to_non_nullable + as int?, + cost: freezed == cost + ? _value.cost + : cost // ignore: cast_nullable_to_non_nullable + as int?, + businessType: freezed == businessType + ? _value.businessType + : businessType // ignore: cast_nullable_to_non_nullable + as String?, + imageUrl: freezed == imageUrl + ? _value.imageUrl + : imageUrl // ignore: cast_nullable_to_non_nullable + as String?, + printerType: freezed == printerType + ? _value.printerType + : printerType // ignore: cast_nullable_to_non_nullable + as String?, + metadata: freezed == metadata + ? _value._metadata + : metadata // ignore: cast_nullable_to_non_nullable + as Map?, + isActive: freezed == isActive + ? _value.isActive + : isActive // ignore: cast_nullable_to_non_nullable + as bool?, + createdAt: freezed == createdAt + ? _value.createdAt + : createdAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + variants: freezed == variants + ? _value._variants + : variants // ignore: cast_nullable_to_non_nullable + as List?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProductDtoImpl extends _ProductDto { + const _$ProductDtoImpl({ + @JsonKey(name: 'id') this.id, + @JsonKey(name: 'organization_id') this.organizationId, + @JsonKey(name: 'category_id') this.categoryId, + @JsonKey(name: 'sku') this.sku, + @JsonKey(name: 'name') this.name, + @JsonKey(name: 'description') this.description, + @JsonKey(name: 'price') this.price, + @JsonKey(name: 'cost') this.cost, + @JsonKey(name: 'business_type') this.businessType, + @JsonKey(name: 'image_url') this.imageUrl, + @JsonKey(name: 'printer_type') this.printerType, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'is_active') this.isActive, + @JsonKey(name: 'created_at') this.createdAt, + @JsonKey(name: 'updated_at') this.updatedAt, + @JsonKey(name: 'variants') final List? variants, + }) : _metadata = metadata, + _variants = variants, + super._(); + + factory _$ProductDtoImpl.fromJson(Map json) => + _$$ProductDtoImplFromJson(json); + + @override + @JsonKey(name: 'id') + final String? id; + @override + @JsonKey(name: 'organization_id') + final String? organizationId; + @override + @JsonKey(name: 'category_id') + final String? categoryId; + @override + @JsonKey(name: 'sku') + final String? sku; + @override + @JsonKey(name: 'name') + final String? name; + @override + @JsonKey(name: 'description') + final String? description; + @override + @JsonKey(name: 'price') + final int? price; + @override + @JsonKey(name: 'cost') + final int? cost; + @override + @JsonKey(name: 'business_type') + final String? businessType; + @override + @JsonKey(name: 'image_url') + final String? imageUrl; + @override + @JsonKey(name: 'printer_type') + final String? printerType; + final Map? _metadata; + @override + @JsonKey(name: 'metadata') + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey(name: 'is_active') + final bool? isActive; + @override + @JsonKey(name: 'created_at') + final DateTime? createdAt; + @override + @JsonKey(name: 'updated_at') + final DateTime? updatedAt; + final List? _variants; + @override + @JsonKey(name: 'variants') + List? get variants { + final value = _variants; + if (value == null) return null; + if (_variants is EqualUnmodifiableListView) return _variants; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + String toString() { + return 'ProductDto(id: $id, organizationId: $organizationId, categoryId: $categoryId, sku: $sku, name: $name, description: $description, price: $price, cost: $cost, businessType: $businessType, imageUrl: $imageUrl, printerType: $printerType, metadata: $metadata, isActive: $isActive, createdAt: $createdAt, updatedAt: $updatedAt, variants: $variants)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.organizationId, organizationId) || + other.organizationId == organizationId) && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId) && + (identical(other.sku, sku) || other.sku == sku) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.price, price) || other.price == price) && + (identical(other.cost, cost) || other.cost == cost) && + (identical(other.businessType, businessType) || + other.businessType == businessType) && + (identical(other.imageUrl, imageUrl) || + other.imageUrl == imageUrl) && + (identical(other.printerType, printerType) || + other.printerType == printerType) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.isActive, isActive) || + other.isActive == isActive) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt) && + const DeepCollectionEquality().equals(other._variants, _variants)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + organizationId, + categoryId, + sku, + name, + description, + price, + cost, + businessType, + imageUrl, + printerType, + const DeepCollectionEquality().hash(_metadata), + isActive, + createdAt, + updatedAt, + const DeepCollectionEquality().hash(_variants), + ); + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductDtoImplCopyWith<_$ProductDtoImpl> get copyWith => + __$$ProductDtoImplCopyWithImpl<_$ProductDtoImpl>(this, _$identity); + + @override + Map toJson() { + return _$$ProductDtoImplToJson(this); + } +} + +abstract class _ProductDto extends ProductDto { + const factory _ProductDto({ + @JsonKey(name: 'id') final String? id, + @JsonKey(name: 'organization_id') final String? organizationId, + @JsonKey(name: 'category_id') final String? categoryId, + @JsonKey(name: 'sku') final String? sku, + @JsonKey(name: 'name') final String? name, + @JsonKey(name: 'description') final String? description, + @JsonKey(name: 'price') final int? price, + @JsonKey(name: 'cost') final int? cost, + @JsonKey(name: 'business_type') final String? businessType, + @JsonKey(name: 'image_url') final String? imageUrl, + @JsonKey(name: 'printer_type') final String? printerType, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'is_active') final bool? isActive, + @JsonKey(name: 'created_at') final DateTime? createdAt, + @JsonKey(name: 'updated_at') final DateTime? updatedAt, + @JsonKey(name: 'variants') final List? variants, + }) = _$ProductDtoImpl; + const _ProductDto._() : super._(); + + factory _ProductDto.fromJson(Map json) = + _$ProductDtoImpl.fromJson; + + @override + @JsonKey(name: 'id') + String? get id; + @override + @JsonKey(name: 'organization_id') + String? get organizationId; + @override + @JsonKey(name: 'category_id') + String? get categoryId; + @override + @JsonKey(name: 'sku') + String? get sku; + @override + @JsonKey(name: 'name') + String? get name; + @override + @JsonKey(name: 'description') + String? get description; + @override + @JsonKey(name: 'price') + int? get price; + @override + @JsonKey(name: 'cost') + int? get cost; + @override + @JsonKey(name: 'business_type') + String? get businessType; + @override + @JsonKey(name: 'image_url') + String? get imageUrl; + @override + @JsonKey(name: 'printer_type') + String? get printerType; + @override + @JsonKey(name: 'metadata') + Map? get metadata; + @override + @JsonKey(name: 'is_active') + bool? get isActive; + @override + @JsonKey(name: 'created_at') + DateTime? get createdAt; + @override + @JsonKey(name: 'updated_at') + DateTime? get updatedAt; + @override + @JsonKey(name: 'variants') + List? get variants; + + /// Create a copy of ProductDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductDtoImplCopyWith<_$ProductDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +ProductVariantDto _$ProductVariantDtoFromJson(Map json) { + return _ProductVariantDto.fromJson(json); +} + +/// @nodoc +mixin _$ProductVariantDto { + @JsonKey(name: 'id') + String? get id => throw _privateConstructorUsedError; + @JsonKey(name: 'product_id') + String? get productId => throw _privateConstructorUsedError; + @JsonKey(name: 'name') + String? get name => throw _privateConstructorUsedError; + @JsonKey(name: 'price_modifier') + int? get priceModifier => throw _privateConstructorUsedError; + @JsonKey(name: 'cost') + int? get cost => throw _privateConstructorUsedError; + @JsonKey(name: 'metadata') + Map? get metadata => throw _privateConstructorUsedError; + @JsonKey(name: 'created_at') + DateTime? get createdAt => throw _privateConstructorUsedError; + @JsonKey(name: 'updated_at') + DateTime? get updatedAt => throw _privateConstructorUsedError; + + /// Serializes this ProductVariantDto to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ProductVariantDtoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProductVariantDtoCopyWith<$Res> { + factory $ProductVariantDtoCopyWith( + ProductVariantDto value, + $Res Function(ProductVariantDto) then, + ) = _$ProductVariantDtoCopyWithImpl<$Res, ProductVariantDto>; + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'product_id') String? productId, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'price_modifier') int? priceModifier, + @JsonKey(name: 'cost') int? cost, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') DateTime? createdAt, + @JsonKey(name: 'updated_at') DateTime? updatedAt, + }); +} + +/// @nodoc +class _$ProductVariantDtoCopyWithImpl<$Res, $Val extends ProductVariantDto> + implements $ProductVariantDtoCopyWith<$Res> { + _$ProductVariantDtoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? productId = freezed, + Object? name = freezed, + Object? priceModifier = freezed, + Object? cost = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _value.copyWith( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + productId: freezed == productId + ? _value.productId + : productId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + priceModifier: freezed == priceModifier + ? _value.priceModifier + : priceModifier // ignore: cast_nullable_to_non_nullable + as int?, + cost: freezed == cost + ? _value.cost + : cost // 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 DateTime?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + ) + as $Val, + ); + } +} + +/// @nodoc +abstract class _$$ProductVariantDtoImplCopyWith<$Res> + implements $ProductVariantDtoCopyWith<$Res> { + factory _$$ProductVariantDtoImplCopyWith( + _$ProductVariantDtoImpl value, + $Res Function(_$ProductVariantDtoImpl) then, + ) = __$$ProductVariantDtoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ + @JsonKey(name: 'id') String? id, + @JsonKey(name: 'product_id') String? productId, + @JsonKey(name: 'name') String? name, + @JsonKey(name: 'price_modifier') int? priceModifier, + @JsonKey(name: 'cost') int? cost, + @JsonKey(name: 'metadata') Map? metadata, + @JsonKey(name: 'created_at') DateTime? createdAt, + @JsonKey(name: 'updated_at') DateTime? updatedAt, + }); +} + +/// @nodoc +class __$$ProductVariantDtoImplCopyWithImpl<$Res> + extends _$ProductVariantDtoCopyWithImpl<$Res, _$ProductVariantDtoImpl> + implements _$$ProductVariantDtoImplCopyWith<$Res> { + __$$ProductVariantDtoImplCopyWithImpl( + _$ProductVariantDtoImpl _value, + $Res Function(_$ProductVariantDtoImpl) _then, + ) : super(_value, _then); + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = freezed, + Object? productId = freezed, + Object? name = freezed, + Object? priceModifier = freezed, + Object? cost = freezed, + Object? metadata = freezed, + Object? createdAt = freezed, + Object? updatedAt = freezed, + }) { + return _then( + _$ProductVariantDtoImpl( + id: freezed == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String?, + productId: freezed == productId + ? _value.productId + : productId // ignore: cast_nullable_to_non_nullable + as String?, + name: freezed == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String?, + priceModifier: freezed == priceModifier + ? _value.priceModifier + : priceModifier // ignore: cast_nullable_to_non_nullable + as int?, + cost: freezed == cost + ? _value.cost + : cost // 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 DateTime?, + updatedAt: freezed == updatedAt + ? _value.updatedAt + : updatedAt // ignore: cast_nullable_to_non_nullable + as DateTime?, + ), + ); + } +} + +/// @nodoc +@JsonSerializable() +class _$ProductVariantDtoImpl extends _ProductVariantDto { + const _$ProductVariantDtoImpl({ + @JsonKey(name: 'id') this.id, + @JsonKey(name: 'product_id') this.productId, + @JsonKey(name: 'name') this.name, + @JsonKey(name: 'price_modifier') this.priceModifier, + @JsonKey(name: 'cost') this.cost, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'created_at') this.createdAt, + @JsonKey(name: 'updated_at') this.updatedAt, + }) : _metadata = metadata, + super._(); + + factory _$ProductVariantDtoImpl.fromJson(Map json) => + _$$ProductVariantDtoImplFromJson(json); + + @override + @JsonKey(name: 'id') + final String? id; + @override + @JsonKey(name: 'product_id') + final String? productId; + @override + @JsonKey(name: 'name') + final String? name; + @override + @JsonKey(name: 'price_modifier') + final int? priceModifier; + @override + @JsonKey(name: 'cost') + final int? cost; + final Map? _metadata; + @override + @JsonKey(name: 'metadata') + Map? get metadata { + final value = _metadata; + if (value == null) return null; + if (_metadata is EqualUnmodifiableMapView) return _metadata; + // ignore: implicit_dynamic_type + return EqualUnmodifiableMapView(value); + } + + @override + @JsonKey(name: 'created_at') + final DateTime? createdAt; + @override + @JsonKey(name: 'updated_at') + final DateTime? updatedAt; + + @override + String toString() { + return 'ProductVariantDto(id: $id, productId: $productId, name: $name, priceModifier: $priceModifier, cost: $cost, metadata: $metadata, createdAt: $createdAt, updatedAt: $updatedAt)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProductVariantDtoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.productId, productId) || + other.productId == productId) && + (identical(other.name, name) || other.name == name) && + (identical(other.priceModifier, priceModifier) || + other.priceModifier == priceModifier) && + (identical(other.cost, cost) || other.cost == cost) && + const DeepCollectionEquality().equals(other._metadata, _metadata) && + (identical(other.createdAt, createdAt) || + other.createdAt == createdAt) && + (identical(other.updatedAt, updatedAt) || + other.updatedAt == updatedAt)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + productId, + name, + priceModifier, + cost, + const DeepCollectionEquality().hash(_metadata), + createdAt, + updatedAt, + ); + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ProductVariantDtoImplCopyWith<_$ProductVariantDtoImpl> get copyWith => + __$$ProductVariantDtoImplCopyWithImpl<_$ProductVariantDtoImpl>( + this, + _$identity, + ); + + @override + Map toJson() { + return _$$ProductVariantDtoImplToJson(this); + } +} + +abstract class _ProductVariantDto extends ProductVariantDto { + const factory _ProductVariantDto({ + @JsonKey(name: 'id') final String? id, + @JsonKey(name: 'product_id') final String? productId, + @JsonKey(name: 'name') final String? name, + @JsonKey(name: 'price_modifier') final int? priceModifier, + @JsonKey(name: 'cost') final int? cost, + @JsonKey(name: 'metadata') final Map? metadata, + @JsonKey(name: 'created_at') final DateTime? createdAt, + @JsonKey(name: 'updated_at') final DateTime? updatedAt, + }) = _$ProductVariantDtoImpl; + const _ProductVariantDto._() : super._(); + + factory _ProductVariantDto.fromJson(Map json) = + _$ProductVariantDtoImpl.fromJson; + + @override + @JsonKey(name: 'id') + String? get id; + @override + @JsonKey(name: 'product_id') + String? get productId; + @override + @JsonKey(name: 'name') + String? get name; + @override + @JsonKey(name: 'price_modifier') + int? get priceModifier; + @override + @JsonKey(name: 'cost') + int? get cost; + @override + @JsonKey(name: 'metadata') + Map? get metadata; + @override + @JsonKey(name: 'created_at') + DateTime? get createdAt; + @override + @JsonKey(name: 'updated_at') + DateTime? get updatedAt; + + /// Create a copy of ProductVariantDto + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ProductVariantDtoImplCopyWith<_$ProductVariantDtoImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/infrastructure/product/product_dtos.g.dart b/lib/infrastructure/product/product_dtos.g.dart new file mode 100644 index 0000000..e15a390 --- /dev/null +++ b/lib/infrastructure/product/product_dtos.g.dart @@ -0,0 +1,83 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'product_dtos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$ProductDtoImpl _$$ProductDtoImplFromJson(Map json) => + _$ProductDtoImpl( + id: json['id'] as String?, + organizationId: json['organization_id'] as String?, + categoryId: json['category_id'] as String?, + sku: json['sku'] as String?, + name: json['name'] as String?, + description: json['description'] as String?, + price: (json['price'] as num?)?.toInt(), + cost: (json['cost'] as num?)?.toInt(), + businessType: json['business_type'] as String?, + imageUrl: json['image_url'] as String?, + printerType: json['printer_type'] as String?, + metadata: json['metadata'] as Map?, + isActive: json['is_active'] as bool?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + variants: (json['variants'] as List?) + ?.map((e) => ProductVariantDto.fromJson(e as Map)) + .toList(), + ); + +Map _$$ProductDtoImplToJson(_$ProductDtoImpl instance) => + { + 'id': instance.id, + 'organization_id': instance.organizationId, + 'category_id': instance.categoryId, + 'sku': instance.sku, + 'name': instance.name, + 'description': instance.description, + 'price': instance.price, + 'cost': instance.cost, + 'business_type': instance.businessType, + 'image_url': instance.imageUrl, + 'printer_type': instance.printerType, + 'metadata': instance.metadata, + 'is_active': instance.isActive, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'variants': instance.variants, + }; + +_$ProductVariantDtoImpl _$$ProductVariantDtoImplFromJson( + Map json, +) => _$ProductVariantDtoImpl( + id: json['id'] as String?, + productId: json['product_id'] as String?, + name: json['name'] as String?, + priceModifier: (json['price_modifier'] as num?)?.toInt(), + cost: (json['cost'] as num?)?.toInt(), + metadata: json['metadata'] as Map?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), +); + +Map _$$ProductVariantDtoImplToJson( + _$ProductVariantDtoImpl instance, +) => { + 'id': instance.id, + 'product_id': instance.productId, + 'name': instance.name, + 'price_modifier': instance.priceModifier, + 'cost': instance.cost, + 'metadata': instance.metadata, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), +}; diff --git a/lib/infrastructure/product/repositories/product_repository.dart b/lib/infrastructure/product/repositories/product_repository.dart new file mode 100644 index 0000000..32bb7bc --- /dev/null +++ b/lib/infrastructure/product/repositories/product_repository.dart @@ -0,0 +1,43 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:injectable/injectable.dart'; + +import '../../../domain/product/product.dart'; +import '../datasources/remote_data_provider.dart'; + +@Injectable(as: IProductRepository) +class ProductRepository implements IProductRepository { + final ProductRemoteDataProvider _dataProvider; + final String _logName = 'ProductRepository'; + + ProductRepository(this._dataProvider); + + @override + Future>> get({ + int page = 1, + int limit = 10, + String? categoryId, + String? search, + }) async { + try { + final result = await _dataProvider.fetch( + page: page, + limit: limit, + categoryId: categoryId, + search: search, + ); + + if (result.hasError) { + return left(result.error!); + } + + final auth = result.data!.map((e) => e.toDomain()).toList(); + + return right(auth); + } catch (e, s) { + log('getProductError', name: _logName, error: e, stackTrace: s); + return left(const ProductFailure.unexpectedError()); + } + } +} diff --git a/lib/injection.config.dart b/lib/injection.config.dart index 9836760..35d7433 100644 --- a/lib/injection.config.dart +++ b/lib/injection.config.dart @@ -18,6 +18,8 @@ import 'package:apskel_owner_flutter/application/category/category_loader/catego as _i183; import 'package:apskel_owner_flutter/application/language/language_bloc.dart' as _i455; +import 'package:apskel_owner_flutter/application/product/product_loader/product_loader_bloc.dart' + as _i458; import 'package:apskel_owner_flutter/application/sales/sales_loader/sales_loader_bloc.dart' as _i882; import 'package:apskel_owner_flutter/common/api/api_client.dart' as _i115; @@ -33,6 +35,7 @@ import 'package:apskel_owner_flutter/domain/analytic/repositories/i_analytic_rep as _i477; import 'package:apskel_owner_flutter/domain/auth/auth.dart' as _i49; import 'package:apskel_owner_flutter/domain/category/category.dart' as _i1020; +import 'package:apskel_owner_flutter/domain/product/product.dart' as _i419; import 'package:apskel_owner_flutter/env.dart' as _i6; import 'package:apskel_owner_flutter/infrastructure/analytic/datasource/remote_data_provider.dart' as _i866; @@ -48,6 +51,10 @@ import 'package:apskel_owner_flutter/infrastructure/category/datasource/remote_d as _i333; import 'package:apskel_owner_flutter/infrastructure/category/repositories/category_repository.dart' as _i869; +import 'package:apskel_owner_flutter/infrastructure/product/datasources/remote_data_provider.dart' + as _i823; +import 'package:apskel_owner_flutter/infrastructure/product/repositories/product_repository.dart' + as _i121; import 'package:apskel_owner_flutter/presentation/router/app_router.dart' as _i258; import 'package:connectivity_plus/connectivity_plus.dart' as _i895; @@ -110,6 +117,9 @@ extension GetItInjectableX on _i174.GetIt { gh.factory<_i333.CategoryRemoteDataProvider>( () => _i333.CategoryRemoteDataProvider(gh<_i115.ApiClient>()), ); + gh.factory<_i823.ProductRemoteDataProvider>( + () => _i823.ProductRemoteDataProvider(gh<_i115.ApiClient>()), + ); gh.factory<_i477.IAnalyticRepository>( () => _i393.AnalyticRepository(gh<_i866.AnalyticRemoteDataProvider>()), ); @@ -119,9 +129,15 @@ extension GetItInjectableX on _i174.GetIt { gh<_i17.AuthRemoteDataProvider>(), ), ); + gh.factory<_i419.IProductRepository>( + () => _i121.ProductRepository(gh<_i823.ProductRemoteDataProvider>()), + ); gh.factory<_i1020.ICategoryRepository>( () => _i869.CategoryRepository(gh<_i333.CategoryRemoteDataProvider>()), ); + gh.factory<_i458.ProductLoaderBloc>( + () => _i458.ProductLoaderBloc(gh<_i419.IProductRepository>()), + ); gh.factory<_i183.CategoryLoaderBloc>( () => _i183.CategoryLoaderBloc(gh<_i1020.ICategoryRepository>()), ); diff --git a/lib/presentation/pages/product/product_page.dart b/lib/presentation/pages/product/product_page.dart index b00989d..d5d2636 100644 --- a/lib/presentation/pages/product/product_page.dart +++ b/lib/presentation/pages/product/product_page.dart @@ -4,11 +4,14 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:line_icons/line_icons.dart'; import '../../../application/category/category_loader/category_loader_bloc.dart'; +import '../../../application/product/product_loader/product_loader_bloc.dart'; import '../../../common/theme/theme.dart'; import '../../../domain/category/category.dart'; +import '../../../domain/product/product.dart'; import '../../../injection.dart'; import '../../components/appbar/appbar.dart'; import '../../components/button/button.dart'; +import '../../components/widgets/empty_widget.dart'; import 'widgets/category_delegate.dart'; import 'widgets/product_tile.dart'; @@ -20,9 +23,18 @@ class ProductPage extends StatefulWidget implements AutoRouteWrapper { State createState() => _ProductPageState(); @override - Widget wrappedRoute(BuildContext context) => BlocProvider( - create: (context) => - getIt()..add(CategoryLoaderEvent.fetched()), + Widget wrappedRoute(BuildContext context) => MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => + getIt()..add(CategoryLoaderEvent.fetched()), + ), + BlocProvider( + create: (context) => + getIt() + ..add(ProductLoaderEvent.fetched(isRefresh: true)), + ), + ], child: this, ); } @@ -34,109 +46,6 @@ class _ProductPageState extends State Category selectedCategory = Category.addAllData(); ViewType currentViewType = ViewType.grid; - // Sample product data - List products = [ - Product( - id: '1', - name: 'Nasi Goreng Special', - price: 25000, - category: 'Makanan', - stock: 50, - imageUrl: 'assets/images/nasi_goreng.jpg', - isActive: true, - ), - Product( - id: '8', - name: 'Nasi Goreng', - price: 15000, - category: 'Makanan', - stock: 50, - imageUrl: 'assets/images/nasi_goreng.jpg', - isActive: true, - ), - Product( - id: '9', - name: 'Nasi Goreng Telor', - price: 18000, - category: 'Makanan', - stock: 50, - imageUrl: 'assets/images/nasi_goreng.jpg', - isActive: true, - ), - Product( - id: '10', - name: 'Mie Goreng ', - price: 18000, - category: 'Makanan', - stock: 50, - imageUrl: 'assets/images/nasi_goreng.jpg', - isActive: true, - ), - Product( - id: '2', - name: 'Es Teh Manis', - price: 8000, - category: 'Minuman', - stock: 100, - imageUrl: 'assets/images/es_teh.jpg', - isActive: true, - ), - Product( - id: '6', - name: 'Es Jeruk', - price: 10000, - category: 'Minuman', - stock: 100, - imageUrl: 'assets/images/es_teh.jpg', - isActive: true, - ), - Product( - id: '7', - name: 'Es Kelapa', - price: 12000, - category: 'Minuman', - stock: 100, - imageUrl: 'assets/images/es_teh.jpg', - isActive: true, - ), - Product( - id: '3', - name: 'Keripik Singkong', - price: 15000, - category: 'Snack', - stock: 25, - imageUrl: 'assets/images/keripik.jpg', - isActive: true, - ), - Product( - id: '4', - name: 'Es Krim Vanilla', - price: 12000, - category: 'Dessert', - stock: 30, - imageUrl: 'assets/images/ice_cream.jpg', - isActive: false, - ), - Product( - id: '5', - name: 'Ayam Bakar', - price: 35000, - category: 'Makanan', - stock: 20, - imageUrl: 'assets/images/ayam_bakar.jpg', - isActive: true, - ), - ]; - - List get filteredProducts { - return products.where((product) { - bool matchesCategory = - selectedCategory.name == 'Semua' || - product.category == selectedCategory.id; - return matchesCategory; - }).toList(); - } - @override initState() { super.initState(); @@ -144,16 +53,20 @@ class _ProductPageState extends State @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColor.background, - body: CustomScrollView( - slivers: [ - _buildSliverAppBar(), - _buildCategoryFilter(), - _buildProductContent(), - _buildEmptyState(), - ], - ), + return BlocBuilder( + builder: (context, state) { + return Scaffold( + backgroundColor: AppColor.background, + body: CustomScrollView( + slivers: [ + _buildSliverAppBar(), + _buildCategoryFilter(), + _buildProductContent(state.products), + _buildEmptyState(state.products), + ], + ), + ); + }, ); } @@ -195,17 +108,17 @@ class _ProductPageState extends State ); } - Widget _buildProductContent() { - if (filteredProducts.isEmpty) { + Widget _buildProductContent(List products) { + if (products.isEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); } return currentViewType == ViewType.grid - ? _buildProductGrid() - : _buildProductList(); + ? _buildProductGrid(products) + : _buildProductList(products); } - Widget _buildProductGrid() { + Widget _buildProductGrid(List products) { return SliverPadding( padding: const EdgeInsets.all(16.0), sliver: SliverGrid( @@ -216,21 +129,21 @@ class _ProductPageState extends State mainAxisSpacing: 16.0, ), delegate: SliverChildBuilderDelegate((context, index) { - final product = filteredProducts[index]; + final product = products[index]; return ProductTile(product: product, onTap: () {}); - }, childCount: filteredProducts.length), + }, childCount: products.length), ), ); } - Widget _buildProductList() { + Widget _buildProductList(List products) { return SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), sliver: SliverList( delegate: SliverChildBuilderDelegate((context, index) { - final product = filteredProducts[index]; + final product = products[index]; return _buildProductListItem(product); - }, childCount: filteredProducts.length), + }, childCount: products.length), ), ); } @@ -300,7 +213,7 @@ class _ProductPageState extends State ), const SizedBox(height: 4), Text( - product.category, + '', style: TextStyle(fontSize: 12, color: AppColor.textLight), ), const SizedBox(height: 8), @@ -327,7 +240,7 @@ class _ProductPageState extends State borderRadius: BorderRadius.circular(12), ), child: Text( - 'Stock: ${product.stock}', + 'Stock: ', style: TextStyle( fontSize: 12, color: product.isActive @@ -370,62 +283,16 @@ class _ProductPageState extends State }); } - Widget _buildEmptyState() { - if (filteredProducts.isNotEmpty) { + Widget _buildEmptyState(List products) { + if (products.isNotEmpty) { return const SliverToBoxAdapter(child: SizedBox.shrink()); } return SliverToBoxAdapter( - child: Container( - height: 300, - margin: const EdgeInsets.all(32.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.inventory_2_outlined, - size: 64, - color: AppColor.textLight, - ), - const SizedBox(height: 16), - Text( - 'Tidak ada produk ditemukan', - style: TextStyle( - color: AppColor.textSecondary, - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 8), - Text( - 'Coba ubah filter atau tambah produk baru', - style: TextStyle(color: AppColor.textLight, fontSize: 14), - textAlign: TextAlign.center, - ), - ], - ), + child: EmptyWidget( + title: 'Tidak ada produk ditemukan', + message: 'Coba ubah filter atau tambah produk baru', ), ); } } - -// Product Model -class Product { - final String id; - final String name; - final int price; - final String category; - final int stock; - final String imageUrl; - bool isActive; - - Product({ - required this.id, - required this.name, - required this.price, - required this.category, - required this.stock, - required this.imageUrl, - required this.isActive, - }); -} diff --git a/lib/presentation/pages/product/widgets/product_tile.dart b/lib/presentation/pages/product/widgets/product_tile.dart index 7cc54c4..807df12 100644 --- a/lib/presentation/pages/product/widgets/product_tile.dart +++ b/lib/presentation/pages/product/widgets/product_tile.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import '../../../../common/theme/theme.dart'; +import '../../../../domain/product/product.dart'; import '../../../components/spacer/spacer.dart'; -import '../product_page.dart'; class ProductTile extends StatelessWidget { final Product product; @@ -156,7 +156,7 @@ class ProductTile extends StatelessWidget { children: [ Flexible( child: Text( - 'Stok: ${product.stock}', + 'Stok: ', style: AppStyle.xs.copyWith( color: AppColor.textSecondary, fontSize: 9, @@ -171,7 +171,7 @@ class ProductTile extends StatelessWidget { borderRadius: BorderRadius.circular(3.0), ), child: Text( - product.category, + '', style: AppStyle.xs.copyWith( color: product.isActive ? AppColor.primary diff --git a/lib/presentation/router/app_router.gr.dart b/lib/presentation/router/app_router.gr.dart index c5b3785..b4e4d2a 100644 --- a/lib/presentation/router/app_router.gr.dart +++ b/lib/presentation/router/app_router.gr.dart @@ -88,6 +88,7 @@ class ErrorRoute extends _i18.PageRouteInfo { _i19.VoidCallback? onRetry, _i19.VoidCallback? onBack, String? errorCode, + _i19.IconData? errorIcon, List<_i18.PageRouteInfo>? children, }) : super( ErrorRoute.name, @@ -98,6 +99,7 @@ class ErrorRoute extends _i18.PageRouteInfo { onRetry: onRetry, onBack: onBack, errorCode: errorCode, + errorIcon: errorIcon, ), initialChildren: children, ); @@ -117,6 +119,7 @@ class ErrorRoute extends _i18.PageRouteInfo { onRetry: args.onRetry, onBack: args.onBack, errorCode: args.errorCode, + errorIcon: args.errorIcon, ); }, ); @@ -130,6 +133,7 @@ class ErrorRouteArgs { this.onRetry, this.onBack, this.errorCode, + this.errorIcon, }); final _i19.Key? key; @@ -144,9 +148,11 @@ class ErrorRouteArgs { final String? errorCode; + final _i19.IconData? errorIcon; + @override String toString() { - return 'ErrorRouteArgs{key: $key, title: $title, message: $message, onRetry: $onRetry, onBack: $onBack, errorCode: $errorCode}'; + return 'ErrorRouteArgs{key: $key, title: $title, message: $message, onRetry: $onRetry, onBack: $onBack, errorCode: $errorCode, errorIcon: $errorIcon}'; } }