feat: product

This commit is contained in:
efrilm 2025-08-17 14:18:10 +07:00
parent 82c0eaf5fe
commit 7d24b3296d
22 changed files with 3779 additions and 183 deletions

View File

@ -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<ProductLoaderEvent, ProductLoaderState> {
final IProductRepository _productRepository;
ProductLoaderBloc(this._productRepository)
: super(ProductLoaderState.initial()) {
on<ProductLoaderEvent>(_onProductLoaderEvent);
}
Future<void> _onProductLoaderEvent(
ProductLoaderEvent event,
Emitter<ProductLoaderState> 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<ProductLoaderState> _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;
}
}

View File

@ -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>(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<TResult extends Object?>({
required TResult Function(String categoryId) categoryIdChanged,
required TResult Function(String search) searchChanged,
required TResult Function(bool isRefresh) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function(String categoryId)? categoryIdChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(bool isRefresh)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function(String categoryId)? categoryIdChanged,
TResult Function(String search)? searchChanged,
TResult Function(bool isRefresh)? fetched,
required TResult orElse(),
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_CategoryIdChanged value) categoryIdChanged,
required TResult Function(_SearchChanged value) searchChanged,
required TResult Function(_Fetched value) fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_CategoryIdChanged value)? categoryIdChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_Fetched value)? fetched,
}) => throw _privateConstructorUsedError;
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(String categoryId)? categoryIdChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(bool isRefresh)? fetched,
}) {
return categoryIdChanged?.call(categoryId);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(_CategoryIdChanged value)? categoryIdChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_Fetched value)? fetched,
}) {
return categoryIdChanged?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(String categoryId)? categoryIdChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(bool isRefresh)? fetched,
}) {
return searchChanged?.call(search);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(_CategoryIdChanged value)? categoryIdChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_Fetched value)? fetched,
}) {
return searchChanged?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(String categoryId)? categoryIdChanged,
TResult? Function(String search)? searchChanged,
TResult? Function(bool isRefresh)? fetched,
}) {
return fetched?.call(isRefresh);
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
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<TResult extends Object?>({
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 extends Object?>({
TResult? Function(_CategoryIdChanged value)? categoryIdChanged,
TResult? Function(_SearchChanged value)? searchChanged,
TResult? Function(_Fetched value)? fetched,
}) {
return fetched?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
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<Product> get products => throw _privateConstructorUsedError;
Option<ProductFailure> 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<ProductLoaderState> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProductLoaderStateCopyWith<$Res> {
factory $ProductLoaderStateCopyWith(
ProductLoaderState value,
$Res Function(ProductLoaderState) then,
) = _$ProductLoaderStateCopyWithImpl<$Res, ProductLoaderState>;
@useResult
$Res call({
List<Product> products,
Option<ProductFailure> 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<Product>,
failureOptionProduct: null == failureOptionProduct
? _value.failureOptionProduct
: failureOptionProduct // ignore: cast_nullable_to_non_nullable
as Option<ProductFailure>,
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<Product> products,
Option<ProductFailure> 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<Product>,
failureOptionProduct: null == failureOptionProduct
? _value.failureOptionProduct
: failureOptionProduct // ignore: cast_nullable_to_non_nullable
as Option<ProductFailure>,
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<Product> products,
required this.failureOptionProduct,
this.categoryId,
this.search,
this.isFetching = false,
this.hasReachedMax = false,
this.page = 1,
}) : _products = products;
final List<Product> _products;
@override
List<Product> get products {
if (_products is EqualUnmodifiableListView) return _products;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_products);
}
@override
final Option<ProductFailure> 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<Product> products,
required final Option<ProductFailure> failureOptionProduct,
final String? categoryId,
final String? search,
final bool isFetching,
final bool hasReachedMax,
final int page,
}) = _$ProductLoaderStateImpl;
@override
List<Product> get products;
@override
Option<ProductFailure> 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;
}

View File

@ -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;
}

View File

@ -0,0 +1,17 @@
part of 'product_loader_bloc.dart';
@freezed
class ProductLoaderState with _$ProductLoaderState {
const factory ProductLoaderState({
required List<Product> products,
required Option<ProductFailure> 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());
}

View File

@ -8,4 +8,7 @@ class ApiPath {
// Category
static const String category = '/api/v1/categories';
// Product
static const String product = '/api/v1/products';
}

View File

@ -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<String, dynamic> metadata,
required bool isActive,
required DateTime createdAt,
required DateTime updatedAt,
required List<ProductVariant> 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: [],
);
}

View File

@ -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<String, dynamic> 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(),
);
}

View File

@ -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;
}

View File

@ -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';

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,10 @@
part of '../product.dart';
abstract class IProductRepository {
Future<Either<ProductFailure, List<Product>>> get({
int page = 1,
int limit = 10,
String? categoryId,
String? search,
});
}

View File

@ -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<DC<ProductFailure, List<ProductDto>>> fetch({
int page = 1,
int limit = 10,
String? categoryId,
String? search,
}) async {
try {
Map<String, dynamic> 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));
}
}
}

View File

@ -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<String, dynamic>? metadata,
@JsonKey(name: 'is_active') bool? isActive,
@JsonKey(name: 'created_at') DateTime? createdAt,
@JsonKey(name: 'updated_at') DateTime? updatedAt,
@JsonKey(name: 'variants') List<ProductVariantDto>? variants,
}) = _ProductDto;
factory ProductDto.fromJson(Map<String, dynamic> 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(),
);
}

View File

@ -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<String, dynamic>? metadata,
@JsonKey(name: 'created_at') DateTime? createdAt,
@JsonKey(name: 'updated_at') DateTime? updatedAt,
}) = _ProductVariantDto;
factory ProductVariantDto.fromJson(Map<String, dynamic> 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,
);
}

View File

@ -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';

View File

@ -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>(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<String, dynamic> 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<String, dynamic>? 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<ProductVariantDto>? get variants => throw _privateConstructorUsedError;
/// Serializes this ProductDto to a JSON map.
Map<String, dynamic> 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<ProductDto> 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<String, dynamic>? metadata,
@JsonKey(name: 'is_active') bool? isActive,
@JsonKey(name: 'created_at') DateTime? createdAt,
@JsonKey(name: 'updated_at') DateTime? updatedAt,
@JsonKey(name: 'variants') List<ProductVariantDto>? 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<String, dynamic>?,
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<ProductVariantDto>?,
)
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<String, dynamic>? metadata,
@JsonKey(name: 'is_active') bool? isActive,
@JsonKey(name: 'created_at') DateTime? createdAt,
@JsonKey(name: 'updated_at') DateTime? updatedAt,
@JsonKey(name: 'variants') List<ProductVariantDto>? 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<String, dynamic>?,
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<ProductVariantDto>?,
),
);
}
}
/// @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<String, dynamic>? metadata,
@JsonKey(name: 'is_active') this.isActive,
@JsonKey(name: 'created_at') this.createdAt,
@JsonKey(name: 'updated_at') this.updatedAt,
@JsonKey(name: 'variants') final List<ProductVariantDto>? variants,
}) : _metadata = metadata,
_variants = variants,
super._();
factory _$ProductDtoImpl.fromJson(Map<String, dynamic> 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<String, dynamic>? _metadata;
@override
@JsonKey(name: 'metadata')
Map<String, dynamic>? 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<ProductVariantDto>? _variants;
@override
@JsonKey(name: 'variants')
List<ProductVariantDto>? 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<String, dynamic> 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<String, dynamic>? 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<ProductVariantDto>? variants,
}) = _$ProductDtoImpl;
const _ProductDto._() : super._();
factory _ProductDto.fromJson(Map<String, dynamic> 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<String, dynamic>? 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<ProductVariantDto>? 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<String, dynamic> 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<String, dynamic>? 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<String, dynamic> 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<ProductVariantDto> 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<String, dynamic>? 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<String, dynamic>?,
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<String, dynamic>? 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<String, dynamic>?,
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<String, dynamic>? metadata,
@JsonKey(name: 'created_at') this.createdAt,
@JsonKey(name: 'updated_at') this.updatedAt,
}) : _metadata = metadata,
super._();
factory _$ProductVariantDtoImpl.fromJson(Map<String, dynamic> 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<String, dynamic>? _metadata;
@override
@JsonKey(name: 'metadata')
Map<String, dynamic>? 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<String, dynamic> 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<String, dynamic>? metadata,
@JsonKey(name: 'created_at') final DateTime? createdAt,
@JsonKey(name: 'updated_at') final DateTime? updatedAt,
}) = _$ProductVariantDtoImpl;
const _ProductVariantDto._() : super._();
factory _ProductVariantDto.fromJson(Map<String, dynamic> 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<String, dynamic>? 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;
}

View File

@ -0,0 +1,83 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'product_dtos.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$ProductDtoImpl _$$ProductDtoImplFromJson(Map<String, dynamic> json) =>
_$ProductDtoImpl(
id: json['id'] as String?,
organizationId: json['organization_id'] as String?,
categoryId: json['category_id'] as String?,
sku: json['sku'] as String?,
name: json['name'] as String?,
description: json['description'] as String?,
price: (json['price'] as num?)?.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<String, dynamic>?,
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<dynamic>?)
?.map((e) => ProductVariantDto.fromJson(e as Map<String, dynamic>))
.toList(),
);
Map<String, dynamic> _$$ProductDtoImplToJson(_$ProductDtoImpl instance) =>
<String, dynamic>{
'id': instance.id,
'organization_id': instance.organizationId,
'category_id': instance.categoryId,
'sku': instance.sku,
'name': instance.name,
'description': instance.description,
'price': instance.price,
'cost': instance.cost,
'business_type': instance.businessType,
'image_url': instance.imageUrl,
'printer_type': instance.printerType,
'metadata': instance.metadata,
'is_active': instance.isActive,
'created_at': instance.createdAt?.toIso8601String(),
'updated_at': instance.updatedAt?.toIso8601String(),
'variants': instance.variants,
};
_$ProductVariantDtoImpl _$$ProductVariantDtoImplFromJson(
Map<String, dynamic> json,
) => _$ProductVariantDtoImpl(
id: json['id'] as String?,
productId: json['product_id'] as String?,
name: json['name'] as String?,
priceModifier: (json['price_modifier'] as num?)?.toInt(),
cost: (json['cost'] as num?)?.toInt(),
metadata: json['metadata'] as Map<String, dynamic>?,
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<String, dynamic> _$$ProductVariantDtoImplToJson(
_$ProductVariantDtoImpl instance,
) => <String, dynamic>{
'id': instance.id,
'product_id': instance.productId,
'name': instance.name,
'price_modifier': instance.priceModifier,
'cost': instance.cost,
'metadata': instance.metadata,
'created_at': instance.createdAt?.toIso8601String(),
'updated_at': instance.updatedAt?.toIso8601String(),
};

View File

@ -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<Either<ProductFailure, List<Product>>> 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());
}
}
}

View File

@ -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>()),
);

View File

@ -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<ProductPage> createState() => _ProductPageState();
@override
Widget wrappedRoute(BuildContext context) => BlocProvider(
create: (context) =>
getIt<CategoryLoaderBloc>()..add(CategoryLoaderEvent.fetched()),
Widget wrappedRoute(BuildContext context) => MultiBlocProvider(
providers: [
BlocProvider(
create: (context) =>
getIt<CategoryLoaderBloc>()..add(CategoryLoaderEvent.fetched()),
),
BlocProvider(
create: (context) =>
getIt<ProductLoaderBloc>()
..add(ProductLoaderEvent.fetched(isRefresh: true)),
),
],
child: this,
);
}
@ -34,109 +46,6 @@ class _ProductPageState extends State<ProductPage>
Category selectedCategory = Category.addAllData();
ViewType currentViewType = ViewType.grid;
// Sample product data
List<Product> 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<Product> 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<ProductPage>
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: AppColor.background,
body: CustomScrollView(
slivers: [
_buildSliverAppBar(),
_buildCategoryFilter(),
_buildProductContent(),
_buildEmptyState(),
],
),
return BlocBuilder<ProductLoaderBloc, ProductLoaderState>(
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<ProductPage>
);
}
Widget _buildProductContent() {
if (filteredProducts.isEmpty) {
Widget _buildProductContent(List<Product> 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<Product> products) {
return SliverPadding(
padding: const EdgeInsets.all(16.0),
sliver: SliverGrid(
@ -216,21 +129,21 @@ class _ProductPageState extends State<ProductPage>
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<Product> 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<ProductPage>
),
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<ProductPage>
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<ProductPage>
});
}
Widget _buildEmptyState() {
if (filteredProducts.isNotEmpty) {
Widget _buildEmptyState(List<Product> 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,
});
}

View File

@ -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

View File

@ -88,6 +88,7 @@ class ErrorRoute extends _i18.PageRouteInfo<ErrorRouteArgs> {
_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<ErrorRouteArgs> {
onRetry: onRetry,
onBack: onBack,
errorCode: errorCode,
errorIcon: errorIcon,
),
initialChildren: children,
);
@ -117,6 +119,7 @@ class ErrorRoute extends _i18.PageRouteInfo<ErrorRouteArgs> {
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}';
}
}