diff --git a/lib/main.dart b/lib/main.dart index d7b0743..f41f73a 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:enaklo_pos/data/datasources/table_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/user_remote_datasource.dart'; import 'package:enaklo_pos/presentation/customer/bloc/customer_form/customer_form_bloc.dart'; import 'package:enaklo_pos/presentation/customer/bloc/customer_loader/customer_loader_bloc.dart'; +import 'package:enaklo_pos/presentation/home/bloc/category_loader/category_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/order_form/order_form_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/outlet_loader/outlet_loader_bloc.dart'; @@ -275,6 +276,9 @@ class _MyAppState extends State { BlocProvider( create: (context) => UploadFileBloc(FileRemoteDataSource()), ), + BlocProvider( + create: (context) => CategoryLoaderBloc(CategoryRemoteDatasource()), + ), ], child: MaterialApp( debugShowCheckedModeBanner: false, diff --git a/lib/presentation/home/bloc/category_loader/category_loader_bloc.dart b/lib/presentation/home/bloc/category_loader/category_loader_bloc.dart new file mode 100644 index 0000000..cf06d82 --- /dev/null +++ b/lib/presentation/home/bloc/category_loader/category_loader_bloc.dart @@ -0,0 +1,38 @@ +import 'package:bloc/bloc.dart'; +import 'package:enaklo_pos/data/datasources/category_remote_datasource.dart'; +import 'package:enaklo_pos/data/models/response/category_response_model.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'category_loader_event.dart'; +part 'category_loader_state.dart'; +part 'category_loader_bloc.freezed.dart'; + +class CategoryLoaderBloc + extends Bloc { + final CategoryRemoteDatasource _datasource; + CategoryLoaderBloc(this._datasource) : super(CategoryLoaderState.initial()) { + on<_Get>((event, emit) async { + emit(const _Loading()); + final result = await _datasource.getCategories(limit: 50); + result.fold( + (l) => emit(_Error(l)), + (r) async { + List categories = r.data.categories; + categories.insert( + 0, + CategoryModel( + id: "", + name: 'Semua', + organizationId: '', + businessType: '', + metadata: {}, + createdAt: DateTime.now(), + updatedAt: DateTime.now(), + ), + ); + emit(_Loaded(categories)); + }, + ); + }); + } +} diff --git a/lib/presentation/home/bloc/category_loader/category_loader_bloc.freezed.dart b/lib/presentation/home/bloc/category_loader/category_loader_bloc.freezed.dart new file mode 100644 index 0000000..b480c1d --- /dev/null +++ b/lib/presentation/home/bloc/category_loader/category_loader_bloc.freezed.dart @@ -0,0 +1,790 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'category_loader_bloc.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); + +/// @nodoc +mixin _$CategoryLoaderEvent { + @optionalTypeArgs + TResult when({ + required TResult Function() get, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? get, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? get, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Get value) get, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Get value)? get, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Get value)? get, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CategoryLoaderEventCopyWith<$Res> { + factory $CategoryLoaderEventCopyWith( + CategoryLoaderEvent value, $Res Function(CategoryLoaderEvent) then) = + _$CategoryLoaderEventCopyWithImpl<$Res, CategoryLoaderEvent>; +} + +/// @nodoc +class _$CategoryLoaderEventCopyWithImpl<$Res, $Val extends CategoryLoaderEvent> + implements $CategoryLoaderEventCopyWith<$Res> { + _$CategoryLoaderEventCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$GetImplCopyWith<$Res> { + factory _$$GetImplCopyWith(_$GetImpl value, $Res Function(_$GetImpl) then) = + __$$GetImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$GetImplCopyWithImpl<$Res> + extends _$CategoryLoaderEventCopyWithImpl<$Res, _$GetImpl> + implements _$$GetImplCopyWith<$Res> { + __$$GetImplCopyWithImpl(_$GetImpl _value, $Res Function(_$GetImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderEvent + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$GetImpl implements _Get { + const _$GetImpl(); + + @override + String toString() { + return 'CategoryLoaderEvent.get()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$GetImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() get, + }) { + return get(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? get, + }) { + return get?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? get, + required TResult orElse(), + }) { + if (get != null) { + return get(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Get value) get, + }) { + return get(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Get value)? get, + }) { + return get?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Get value)? get, + required TResult orElse(), + }) { + if (get != null) { + return get(this); + } + return orElse(); + } +} + +abstract class _Get implements CategoryLoaderEvent { + const factory _Get() = _$GetImpl; +} + +/// @nodoc +mixin _$CategoryLoaderState { + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List categories) loaded, + required TResult Function(String message) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories)? loaded, + TResult? Function(String message)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $CategoryLoaderStateCopyWith<$Res> { + factory $CategoryLoaderStateCopyWith( + CategoryLoaderState value, $Res Function(CategoryLoaderState) then) = + _$CategoryLoaderStateCopyWithImpl<$Res, CategoryLoaderState>; +} + +/// @nodoc +class _$CategoryLoaderStateCopyWithImpl<$Res, $Val extends CategoryLoaderState> + implements $CategoryLoaderStateCopyWith<$Res> { + _$CategoryLoaderStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc +abstract class _$$InitialImplCopyWith<$Res> { + factory _$$InitialImplCopyWith( + _$InitialImpl value, $Res Function(_$InitialImpl) then) = + __$$InitialImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$InitialImplCopyWithImpl<$Res> + extends _$CategoryLoaderStateCopyWithImpl<$Res, _$InitialImpl> + implements _$$InitialImplCopyWith<$Res> { + __$$InitialImplCopyWithImpl( + _$InitialImpl _value, $Res Function(_$InitialImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$InitialImpl implements _Initial { + const _$InitialImpl(); + + @override + String toString() { + return 'CategoryLoaderState.initial()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$InitialImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List categories) loaded, + required TResult Function(String message) error, + }) { + return initial(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories)? loaded, + TResult? Function(String message)? error, + }) { + return initial?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) { + return initial(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) { + return initial?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (initial != null) { + return initial(this); + } + return orElse(); + } +} + +abstract class _Initial implements CategoryLoaderState { + const factory _Initial() = _$InitialImpl; +} + +/// @nodoc +abstract class _$$LoadingImplCopyWith<$Res> { + factory _$$LoadingImplCopyWith( + _$LoadingImpl value, $Res Function(_$LoadingImpl) then) = + __$$LoadingImplCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$LoadingImplCopyWithImpl<$Res> + extends _$CategoryLoaderStateCopyWithImpl<$Res, _$LoadingImpl> + implements _$$LoadingImplCopyWith<$Res> { + __$$LoadingImplCopyWithImpl( + _$LoadingImpl _value, $Res Function(_$LoadingImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. +} + +/// @nodoc + +class _$LoadingImpl implements _Loading { + const _$LoadingImpl(); + + @override + String toString() { + return 'CategoryLoaderState.loading()'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$LoadingImpl); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List categories) loaded, + required TResult Function(String message) error, + }) { + return loading(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories)? loaded, + TResult? Function(String message)? error, + }) { + return loading?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) { + return loading(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) { + return loading?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loading != null) { + return loading(this); + } + return orElse(); + } +} + +abstract class _Loading implements CategoryLoaderState { + const factory _Loading() = _$LoadingImpl; +} + +/// @nodoc +abstract class _$$LoadedImplCopyWith<$Res> { + factory _$$LoadedImplCopyWith( + _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = + __$$LoadedImplCopyWithImpl<$Res>; + @useResult + $Res call({List categories}); +} + +/// @nodoc +class __$$LoadedImplCopyWithImpl<$Res> + extends _$CategoryLoaderStateCopyWithImpl<$Res, _$LoadedImpl> + implements _$$LoadedImplCopyWith<$Res> { + __$$LoadedImplCopyWithImpl( + _$LoadedImpl _value, $Res Function(_$LoadedImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categories = null, + }) { + return _then(_$LoadedImpl( + null == categories + ? _value._categories + : categories // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc + +class _$LoadedImpl implements _Loaded { + const _$LoadedImpl(final List categories) + : _categories = categories; + + final List _categories; + @override + List get categories { + if (_categories is EqualUnmodifiableListView) return _categories; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_categories); + } + + @override + String toString() { + return 'CategoryLoaderState.loaded(categories: $categories)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadedImpl && + const DeepCollectionEquality() + .equals(other._categories, _categories)); + } + + @override + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_categories)); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoadedImplCopyWith<_$LoadedImpl> get copyWith => + __$$LoadedImplCopyWithImpl<_$LoadedImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List categories) loaded, + required TResult Function(String message) error, + }) { + return loaded(categories); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories)? loaded, + TResult? Function(String message)? error, + }) { + return loaded?.call(categories); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(categories); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) { + return loaded(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) { + return loaded?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (loaded != null) { + return loaded(this); + } + return orElse(); + } +} + +abstract class _Loaded implements CategoryLoaderState { + const factory _Loaded(final List categories) = _$LoadedImpl; + + List get categories; + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoadedImplCopyWith<_$LoadedImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$ErrorImplCopyWith<$Res> { + factory _$$ErrorImplCopyWith( + _$ErrorImpl value, $Res Function(_$ErrorImpl) then) = + __$$ErrorImplCopyWithImpl<$Res>; + @useResult + $Res call({String message}); +} + +/// @nodoc +class __$$ErrorImplCopyWithImpl<$Res> + extends _$CategoryLoaderStateCopyWithImpl<$Res, _$ErrorImpl> + implements _$$ErrorImplCopyWith<$Res> { + __$$ErrorImplCopyWithImpl( + _$ErrorImpl _value, $Res Function(_$ErrorImpl) _then) + : super(_value, _then); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? message = null, + }) { + return _then(_$ErrorImpl( + null == message + ? _value.message + : message // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$ErrorImpl implements _Error { + const _$ErrorImpl(this.message); + + @override + final String message; + + @override + String toString() { + return 'CategoryLoaderState.error(message: $message)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ErrorImpl && + (identical(other.message, message) || other.message == message)); + } + + @override + int get hashCode => Object.hash(runtimeType, message); + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + __$$ErrorImplCopyWithImpl<_$ErrorImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() initial, + required TResult Function() loading, + required TResult Function(List categories) loaded, + required TResult Function(String message) error, + }) { + return error(message); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? initial, + TResult? Function()? loading, + TResult? Function(List categories)? loaded, + TResult? Function(String message)? error, + }) { + return error?.call(message); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? initial, + TResult Function()? loading, + TResult Function(List categories)? loaded, + TResult Function(String message)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(message); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Initial value) initial, + required TResult Function(_Loading value) loading, + required TResult Function(_Loaded value) loaded, + required TResult Function(_Error value) error, + }) { + return error(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Initial value)? initial, + TResult? Function(_Loading value)? loading, + TResult? Function(_Loaded value)? loaded, + TResult? Function(_Error value)? error, + }) { + return error?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Initial value)? initial, + TResult Function(_Loading value)? loading, + TResult Function(_Loaded value)? loaded, + TResult Function(_Error value)? error, + required TResult orElse(), + }) { + if (error != null) { + return error(this); + } + return orElse(); + } +} + +abstract class _Error implements CategoryLoaderState { + const factory _Error(final String message) = _$ErrorImpl; + + String get message; + + /// Create a copy of CategoryLoaderState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ErrorImplCopyWith<_$ErrorImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/presentation/home/bloc/category_loader/category_loader_event.dart b/lib/presentation/home/bloc/category_loader/category_loader_event.dart new file mode 100644 index 0000000..1e561e4 --- /dev/null +++ b/lib/presentation/home/bloc/category_loader/category_loader_event.dart @@ -0,0 +1,6 @@ +part of 'category_loader_bloc.dart'; + +@freezed +class CategoryLoaderEvent with _$CategoryLoaderEvent { + const factory CategoryLoaderEvent.get() = _Get; +} diff --git a/lib/presentation/home/bloc/category_loader/category_loader_state.dart b/lib/presentation/home/bloc/category_loader/category_loader_state.dart new file mode 100644 index 0000000..218645e --- /dev/null +++ b/lib/presentation/home/bloc/category_loader/category_loader_state.dart @@ -0,0 +1,10 @@ +part of 'category_loader_bloc.dart'; + +@freezed +class CategoryLoaderState with _$CategoryLoaderState { + const factory CategoryLoaderState.initial() = _Initial; + const factory CategoryLoaderState.loading() = _Loading; + const factory CategoryLoaderState.loaded(List categories) = + _Loaded; + const factory CategoryLoaderState.error(String message) = _Error; +} diff --git a/lib/presentation/home/bloc/product_loader/product_loader_bloc.dart b/lib/presentation/home/bloc/product_loader/product_loader_bloc.dart index bff3d39..1a18a08 100644 --- a/lib/presentation/home/bloc/product_loader/product_loader_bloc.dart +++ b/lib/presentation/home/bloc/product_loader/product_loader_bloc.dart @@ -49,6 +49,7 @@ class ProductLoaderBloc extends Bloc { final result = await _productRemoteDatasource.getProducts( page: 1, limit: 10, + categoryId: event.categoryId, ); await result.fold( @@ -93,6 +94,7 @@ class ProductLoaderBloc extends Bloc { final result = await _productRemoteDatasource.getProducts( page: nextPage, limit: 10, + categoryId: event.categoryId, ); await result.fold( diff --git a/lib/presentation/home/bloc/product_loader/product_loader_bloc.freezed.dart b/lib/presentation/home/bloc/product_loader/product_loader_bloc.freezed.dart index bc82cbe..20d590e 100644 --- a/lib/presentation/home/bloc/product_loader/product_loader_bloc.freezed.dart +++ b/lib/presentation/home/bloc/product_loader/product_loader_bloc.freezed.dart @@ -18,22 +18,22 @@ final _privateConstructorUsedError = UnsupportedError( mixin _$ProductLoaderEvent { @optionalTypeArgs TResult when({ - required TResult Function() getProduct, - required TResult Function() loadMore, + required TResult Function(String? categoryId) getProduct, + required TResult Function(String? categoryId) loadMore, required TResult Function() refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function()? getProduct, - TResult? Function()? loadMore, + TResult? Function(String? categoryId)? getProduct, + TResult? Function(String? categoryId)? loadMore, TResult? Function()? refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function()? getProduct, - TResult Function()? loadMore, + TResult Function(String? categoryId)? getProduct, + TResult Function(String? categoryId)? loadMore, TResult Function()? refresh, required TResult orElse(), }) => @@ -88,6 +88,8 @@ abstract class _$$GetProductImplCopyWith<$Res> { factory _$$GetProductImplCopyWith( _$GetProductImpl value, $Res Function(_$GetProductImpl) then) = __$$GetProductImplCopyWithImpl<$Res>; + @useResult + $Res call({String? categoryId}); } /// @nodoc @@ -100,57 +102,83 @@ class __$$GetProductImplCopyWithImpl<$Res> /// Create a copy of ProductLoaderEvent /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categoryId = freezed, + }) { + return _then(_$GetProductImpl( + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + )); + } } /// @nodoc class _$GetProductImpl implements _GetProduct { - const _$GetProductImpl(); + const _$GetProductImpl({this.categoryId}); + + @override + final String? categoryId; @override String toString() { - return 'ProductLoaderEvent.getProduct()'; + return 'ProductLoaderEvent.getProduct(categoryId: $categoryId)'; } @override bool operator ==(Object other) { return identical(this, other) || - (other.runtimeType == runtimeType && other is _$GetProductImpl); + (other.runtimeType == runtimeType && + other is _$GetProductImpl && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId)); } @override - int get hashCode => runtimeType.hashCode; + 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') + _$$GetProductImplCopyWith<_$GetProductImpl> get copyWith => + __$$GetProductImplCopyWithImpl<_$GetProductImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function() getProduct, - required TResult Function() loadMore, + required TResult Function(String? categoryId) getProduct, + required TResult Function(String? categoryId) loadMore, required TResult Function() refresh, }) { - return getProduct(); + return getProduct(categoryId); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function()? getProduct, - TResult? Function()? loadMore, + TResult? Function(String? categoryId)? getProduct, + TResult? Function(String? categoryId)? loadMore, TResult? Function()? refresh, }) { - return getProduct?.call(); + return getProduct?.call(categoryId); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function()? getProduct, - TResult Function()? loadMore, + TResult Function(String? categoryId)? getProduct, + TResult Function(String? categoryId)? loadMore, TResult Function()? refresh, required TResult orElse(), }) { if (getProduct != null) { - return getProduct(); + return getProduct(categoryId); } return orElse(); } @@ -191,7 +219,15 @@ class _$GetProductImpl implements _GetProduct { } abstract class _GetProduct implements ProductLoaderEvent { - const factory _GetProduct() = _$GetProductImpl; + const factory _GetProduct({final String? categoryId}) = _$GetProductImpl; + + String? get categoryId; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$GetProductImplCopyWith<_$GetProductImpl> get copyWith => + throw _privateConstructorUsedError; } /// @nodoc @@ -199,6 +235,8 @@ abstract class _$$LoadMoreImplCopyWith<$Res> { factory _$$LoadMoreImplCopyWith( _$LoadMoreImpl value, $Res Function(_$LoadMoreImpl) then) = __$$LoadMoreImplCopyWithImpl<$Res>; + @useResult + $Res call({String? categoryId}); } /// @nodoc @@ -211,57 +249,83 @@ class __$$LoadMoreImplCopyWithImpl<$Res> /// Create a copy of ProductLoaderEvent /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? categoryId = freezed, + }) { + return _then(_$LoadMoreImpl( + categoryId: freezed == categoryId + ? _value.categoryId + : categoryId // ignore: cast_nullable_to_non_nullable + as String?, + )); + } } /// @nodoc class _$LoadMoreImpl implements _LoadMore { - const _$LoadMoreImpl(); + const _$LoadMoreImpl({this.categoryId}); + + @override + final String? categoryId; @override String toString() { - return 'ProductLoaderEvent.loadMore()'; + return 'ProductLoaderEvent.loadMore(categoryId: $categoryId)'; } @override bool operator ==(Object other) { return identical(this, other) || - (other.runtimeType == runtimeType && other is _$LoadMoreImpl); + (other.runtimeType == runtimeType && + other is _$LoadMoreImpl && + (identical(other.categoryId, categoryId) || + other.categoryId == categoryId)); } @override - int get hashCode => runtimeType.hashCode; + 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') + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + __$$LoadMoreImplCopyWithImpl<_$LoadMoreImpl>(this, _$identity); @override @optionalTypeArgs TResult when({ - required TResult Function() getProduct, - required TResult Function() loadMore, + required TResult Function(String? categoryId) getProduct, + required TResult Function(String? categoryId) loadMore, required TResult Function() refresh, }) { - return loadMore(); + return loadMore(categoryId); } @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function()? getProduct, - TResult? Function()? loadMore, + TResult? Function(String? categoryId)? getProduct, + TResult? Function(String? categoryId)? loadMore, TResult? Function()? refresh, }) { - return loadMore?.call(); + return loadMore?.call(categoryId); } @override @optionalTypeArgs TResult maybeWhen({ - TResult Function()? getProduct, - TResult Function()? loadMore, + TResult Function(String? categoryId)? getProduct, + TResult Function(String? categoryId)? loadMore, TResult Function()? refresh, required TResult orElse(), }) { if (loadMore != null) { - return loadMore(); + return loadMore(categoryId); } return orElse(); } @@ -302,7 +366,15 @@ class _$LoadMoreImpl implements _LoadMore { } abstract class _LoadMore implements ProductLoaderEvent { - const factory _LoadMore() = _$LoadMoreImpl; + const factory _LoadMore({final String? categoryId}) = _$LoadMoreImpl; + + String? get categoryId; + + /// Create a copy of ProductLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + throw _privateConstructorUsedError; } /// @nodoc @@ -346,8 +418,8 @@ class _$RefreshImpl implements _Refresh { @override @optionalTypeArgs TResult when({ - required TResult Function() getProduct, - required TResult Function() loadMore, + required TResult Function(String? categoryId) getProduct, + required TResult Function(String? categoryId) loadMore, required TResult Function() refresh, }) { return refresh(); @@ -356,8 +428,8 @@ class _$RefreshImpl implements _Refresh { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function()? getProduct, - TResult? Function()? loadMore, + TResult? Function(String? categoryId)? getProduct, + TResult? Function(String? categoryId)? loadMore, TResult? Function()? refresh, }) { return refresh?.call(); @@ -366,8 +438,8 @@ class _$RefreshImpl implements _Refresh { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function()? getProduct, - TResult Function()? loadMore, + TResult Function(String? categoryId)? getProduct, + TResult Function(String? categoryId)? loadMore, TResult Function()? refresh, required TResult orElse(), }) { diff --git a/lib/presentation/home/bloc/product_loader/product_loader_event.dart b/lib/presentation/home/bloc/product_loader/product_loader_event.dart index 9b3ac2c..9110233 100644 --- a/lib/presentation/home/bloc/product_loader/product_loader_event.dart +++ b/lib/presentation/home/bloc/product_loader/product_loader_event.dart @@ -2,7 +2,8 @@ part of 'product_loader_bloc.dart'; @freezed class ProductLoaderEvent with _$ProductLoaderEvent { - const factory ProductLoaderEvent.getProduct() = _GetProduct; - const factory ProductLoaderEvent.loadMore() = _LoadMore; + const factory ProductLoaderEvent.getProduct({String? categoryId}) = + _GetProduct; + const factory ProductLoaderEvent.loadMore({String? categoryId}) = _LoadMore; const factory ProductLoaderEvent.refresh() = _Refresh; } diff --git a/lib/presentation/home/pages/home_page.dart b/lib/presentation/home/pages/home_page.dart index 04e64fa..acf95a1 100644 --- a/lib/presentation/home/pages/home_page.dart +++ b/lib/presentation/home/pages/home_page.dart @@ -1,8 +1,10 @@ // ignore_for_file: public_member_api_docs, sort_constructors_first import 'package:enaklo_pos/core/components/flushbar.dart'; +import 'package:enaklo_pos/presentation/home/bloc/category_loader/category_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/current_outlet/current_outlet_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart'; import 'package:enaklo_pos/presentation/home/bloc/user_update_outlet/user_update_outlet_bloc.dart'; +import 'package:enaklo_pos/presentation/home/widgets/category_tab_bar.dart'; import 'package:enaklo_pos/presentation/home/widgets/home_right_title.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -18,7 +20,6 @@ import '../../../core/components/buttons.dart'; import '../../../core/components/spaces.dart'; import '../../../core/constants/colors.dart'; import '../bloc/checkout/checkout_bloc.dart'; -import '../widgets/custom_tab_bar.dart'; import '../widgets/home_title.dart'; import '../widgets/order_menu.dart'; import '../widgets/product_card.dart'; @@ -53,6 +54,14 @@ class _HomePageState extends State { super.initState(); } + @override + void dispose() { + // Properly dispose controllers + searchController.dispose(); + scrollController.dispose(); + super.dispose(); + } + void _syncAndLoadProducts() { // Trigger sync from API first // context.read().add(const SyncProductEvent.syncProduct()); @@ -68,6 +77,9 @@ class _HomePageState extends State { // Initialize checkout with tax and service charge settings context.read().add(const CheckoutEvent.started()); + // Get Category + context.read().add(CategoryLoaderEvent.get()); + // Get Outlets context.read().add(CurrentOutletEvent.currentOutlet()); } @@ -91,12 +103,20 @@ class _HomePageState extends State { }).toList(); } - List _filterProductsByCategory( - List products, int categoryId) { - final filteredBySearch = _filterProducts(products); - return filteredBySearch - .where((element) => element.price == categoryId) - .toList(); + bool _handleScrollNotification(ScrollNotification notification) { + // Check if the ScrollController is attached before accessing position + if (!scrollController.hasClients) { + return false; + } + + if (notification is ScrollEndNotification && + scrollController.position.extentAfter == 0) { + context + .read() + .add(const ProductLoaderEvent.loadMore()); + return true; + } + return false; } @override @@ -147,99 +167,58 @@ class _HomePageState extends State { orElse: () => false, loaded: (products, hasReachedMax, currentPage, isLoadingMore) { - if (notification is ScrollEndNotification && - scrollController.position.extentAfter == - 0) { - context.read().add( - const ProductLoaderEvent.loadMore()); - return true; - } - - return true; + return _handleScrollNotification( + notification); }, ); }, - child: Expanded( - child: CustomTabBarV2( - tabTitles: const [ - 'Semua', - 'Makanan', - 'Minuman', - 'Paket' - ], - tabViews: [ - // All Products Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products, hashasReachedMax, - currentPage, isLoadingMore) { - final filteredProducts = - _filterProducts(products); - if (filteredProducts.isEmpty) { - return const Center( - child: Text('No Items Found'), - ); - } - return GridView.builder( - itemCount: filteredProducts.length, - controller: scrollController, - padding: const EdgeInsets.all(16), - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 180, - mainAxisSpacing: 30, - crossAxisSpacing: 30, - childAspectRatio: 180 / 240, - ), - itemBuilder: (context, index) => - ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ), - ); - }), - ), - // Makanan Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products, hashasReachedMax, - currentPage, isLoadingMore) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - final filteredProducts = - _filterProductsByCategory( - products, 1); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( + child: Expanded(child: BlocBuilder< + CategoryLoaderBloc, CategoryLoaderState>( + builder: (contextCategory, stateCateogry) { + return stateCateogry.maybeWhen( + orElse: () => SizedBox.shrink(), + loading: () { + return const Center( + child: CircularProgressIndicator(), + ); + }, + loaded: (categories) { + return CategoryTabBar( + categories: categories, + tabViews: categories.map((category) { + return SizedBox( + child: state.maybeWhen(orElse: () { + return const Center( + child: + CircularProgressIndicator(), + ); + }, loading: () { + return const Center( + child: + CircularProgressIndicator(), + ); + }, loaded: (products, + hashasReachedMax, + currentPage, + isLoadingMore) { + final filteredProducts = + _filterProducts(products); + if (filteredProducts.isEmpty) { + return const Center( + child: Text('No Items Found'), + ); + } + return GridView.builder( itemCount: filteredProducts.length, - padding: const EdgeInsets.all(16), controller: scrollController, + padding: const EdgeInsets.all(16), gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: - 200, // Lebar maksimal tiap item (bisa kamu ubah) + maxCrossAxisExtent: 180, mainAxisSpacing: 30, crossAxisSpacing: 30, - childAspectRatio: 0.85, + childAspectRatio: 180 / 240, ), itemBuilder: (context, index) => ProductCard( @@ -247,99 +226,14 @@ class _HomePageState extends State { onCartButton: () {}, ), ); - }), - ), - // Minuman Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products, hashasReachedMax, - currentPage, isLoadingMore) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), + }), ); - } - final filteredProducts = - _filterProductsByCategory( - products, 2); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( - itemCount: - filteredProducts.length, - padding: const EdgeInsets.all(16), - controller: scrollController, - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: - 200, // Lebar maksimal tiap item (bisa kamu ubah) - mainAxisSpacing: 30, - crossAxisSpacing: 30, - childAspectRatio: 0.85, - ), - itemBuilder: (context, index) { - return ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ); - }, - ); - }), - ), - // Snack Tab - SizedBox( - child: state.maybeWhen(orElse: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loading: () { - return const Center( - child: CircularProgressIndicator(), - ); - }, loaded: (products, hashasReachedMax, - currentPage, isLoadingMore) { - if (products.isEmpty) { - return const Center( - child: Text('No Items'), - ); - } - final filteredProducts = - _filterProductsByCategory( - products, 3); - return filteredProducts.isEmpty - ? const _IsEmpty() - : GridView.builder( - itemCount: - filteredProducts.length, - padding: const EdgeInsets.all(16), - controller: scrollController, - gridDelegate: - SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: - 200, // Lebar maksimal tiap item (bisa kamu ubah) - mainAxisSpacing: 30, - crossAxisSpacing: 30, - childAspectRatio: 0.85, - ), - itemBuilder: (context, index) { - return ProductCard( - data: filteredProducts[index], - onCartButton: () {}, - ); - }, - ); - }), - ), - ], - ), - ), + }).toList(), + ); + }, + ); + }, + )), ); }, ), @@ -611,6 +505,7 @@ class _HomePageState extends State { } } +// ignore: unused_element class _IsEmpty extends StatelessWidget { const _IsEmpty(); diff --git a/lib/presentation/home/widgets/category_tab_bar.dart b/lib/presentation/home/widgets/category_tab_bar.dart new file mode 100644 index 0000000..ea4fd25 --- /dev/null +++ b/lib/presentation/home/widgets/category_tab_bar.dart @@ -0,0 +1,96 @@ +import 'package:enaklo_pos/core/constants/colors.dart'; +import 'package:enaklo_pos/data/models/response/category_response_model.dart'; +import 'package:enaklo_pos/presentation/home/bloc/product_loader/product_loader_bloc.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; + +class CategoryTabBar extends StatefulWidget { + final List categories; + final List tabViews; + const CategoryTabBar( + {super.key, required this.categories, required this.tabViews}); + + @override + State createState() => _CategoryTabBarState(); +} + +class _CategoryTabBarState extends State + with SingleTickerProviderStateMixin { + late TabController _tabController; + String? selectedCategoryId; + + @override + void initState() { + super.initState(); + _tabController = + TabController(length: widget.categories.length, vsync: this); + + _tabController.addListener(() { + if (_tabController.indexIsChanging) { + if (_tabController.index == 0) { + context.read().add( + ProductLoaderEvent.getProduct(), + ); + } else { + selectedCategoryId = widget.categories[_tabController.index].id; + context.read().add( + ProductLoaderEvent.getProduct(categoryId: selectedCategoryId), + ); + } + } + }); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return DefaultTabController( + length: widget.categories.length, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Material( + elevation: 0, + color: Colors.white, + borderOnForeground: false, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16), + child: TabBar( + controller: _tabController, + isScrollable: true, + tabAlignment: TabAlignment.start, + labelColor: AppColors.primary, + labelStyle: TextStyle( + fontWeight: FontWeight.bold, + ), + dividerColor: AppColors.primary, + unselectedLabelColor: AppColors.primary, + indicatorSize: TabBarIndicatorSize.label, + indicatorWeight: 4, + indicatorColor: AppColors.primary, + tabs: widget.categories + .map((category) => Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: Tab(text: category.name), + )) + .toList(), + ), + ), + ), + Expanded( + // ✅ ini bagian penting + child: TabBarView( + controller: _tabController, + children: widget.tabViews, + ), + ), + ], + ), + ); + } +}