diff --git a/lib/presentation/home/dialog/payment_add_order_dialog.dart b/lib/presentation/home/dialog/payment_add_order_dialog.dart index 9821878..069c820 100644 --- a/lib/presentation/home/dialog/payment_add_order_dialog.dart +++ b/lib/presentation/home/dialog/payment_add_order_dialog.dart @@ -66,7 +66,13 @@ class _PaymentAddOrderDialogState extends State { Center(child: const CircularProgressIndicator()), loading: () => Center(child: const CircularProgressIndicator()), - loaded: (orders, totalOrder) { + loaded: ( + orders, + totalOrder, + _, + __, + ___, + ) { final availableOrders = orders; if (selectOrder == null && availableOrders.isNotEmpty) { diff --git a/lib/presentation/sales/blocs/order_loader/order_loader_bloc.dart b/lib/presentation/sales/blocs/order_loader/order_loader_bloc.dart index 727834e..a271e88 100644 --- a/lib/presentation/sales/blocs/order_loader/order_loader_bloc.dart +++ b/lib/presentation/sales/blocs/order_loader/order_loader_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:bloc/bloc.dart'; import 'package:enaklo_pos/data/datasources/order_remote_datasource.dart'; import 'package:enaklo_pos/data/models/response/order_response_model.dart'; @@ -9,24 +11,15 @@ part 'order_loader_bloc.freezed.dart'; class OrderLoaderBloc extends Bloc { final OrderRemoteDatasource _orderRemoteDatasource; + + Timer? _loadMoreDebounce; + bool _isLoadingMore = false; + OrderLoaderBloc(this._orderRemoteDatasource) : super(OrderLoaderState.initial()) { - on<_GetByStatus>((event, emit) async { - emit(const _Loading()); - final result = await _orderRemoteDatasource.getOrder( - status: event.status, - limit: 20, - dateFrom: event.dateFrom, - dateTo: event.dateTo, - ); - result.fold( - (l) => emit(_Error(l)), - (r) => emit(_Loaded( - r.data?.orders ?? [], - r.data?.totalCount ?? 0, - )), - ); - }); + on<_GetByStatus>(_onGetOrderByStatus); + on<_LoadMore>(_onLoadMore); + on<_Refresh>(_onRefresh); on<_GetById>((event, emit) async { emit(const _Loading()); final result = @@ -37,4 +30,138 @@ class OrderLoaderBloc extends Bloc { ); }); } + + @override + Future close() { + _loadMoreDebounce?.cancel(); + return super.close(); + } + + // Debounce transformer untuk load more + // EventTransformer _debounceTransformer() { + // return (events, mapper) { + // return events + // .debounceTime(const Duration(milliseconds: 300)) + // .asyncExpand(mapper); + // }; + // } + + // Initial load + Future _onGetOrderByStatus( + _GetByStatus event, + Emitter emit, + ) async { + emit(const _Loading()); + _isLoadingMore = false; // Reset loading state + + final result = await _orderRemoteDatasource.getOrder( + page: 1, + limit: 10, + status: event.status, + dateFrom: event.dateFrom, + dateTo: event.dateTo, + ); + + await result.fold( + (failure) async => emit(_Error(failure)), + (response) async { + final orders = response.data?.orders ?? []; + final hasReachedMax = orders.length < 10; + + emit(_Loaded( + orders: orders, + totalOrder: response.data?.totalCount ?? 0, + hasReachedMax: hasReachedMax, + currentPage: 1, + isLoadingMore: false, + )); + }, + ); + } + + // Load more with enhanced debouncing + Future _onLoadMore( + _LoadMore event, + Emitter emit, + ) async { + final currentState = state; + + // Enhanced validation + if (currentState is! _Loaded || + currentState.hasReachedMax || + _isLoadingMore || + currentState.isLoadingMore) { + return; + } + + _isLoadingMore = true; + + // Emit loading more state + emit(currentState.copyWith(isLoadingMore: true)); + + final nextPage = currentState.currentPage + 1; + + try { + final result = await _orderRemoteDatasource.getOrder( + page: nextPage, + limit: 10, + status: event.status, + dateFrom: event.dateFrom, + dateTo: event.dateTo, + ); + + await result.fold( + (failure) async { + // On error, revert loading state but don't show error + // Just silently fail and allow retry + emit(currentState.copyWith(isLoadingMore: false)); + _isLoadingMore = false; + }, + (response) async { + final newOrders = response.data?.orders ?? []; + + // Prevent duplicate orders + final currentOrderIds = currentState.orders.map((p) => p.id).toSet(); + final filteredNewOrders = newOrders + .where((order) => !currentOrderIds.contains(order.id)) + .toList(); + + final allOrders = List.from(currentState.orders) + ..addAll(filteredNewOrders); + + final hasReachedMax = newOrders.length < 10; + + emit(_Loaded( + orders: allOrders, + totalOrder: response.data?.totalCount ?? 0, + hasReachedMax: hasReachedMax, + currentPage: nextPage, + isLoadingMore: false, + )); + + _isLoadingMore = false; + }, + ); + } catch (e) { + // Handle unexpected errors + emit(currentState.copyWith(isLoadingMore: false)); + _isLoadingMore = false; + } + } + + // Refresh data + Future _onRefresh( + _Refresh event, + Emitter emit, + ) async { + _isLoadingMore = false; + _loadMoreDebounce?.cancel(); + add( + _GetByStatus( + event.status, + dateFrom: DateTime.now(), + dateTo: DateTime.now(), + ), + ); + } } diff --git a/lib/presentation/sales/blocs/order_loader/order_loader_bloc.freezed.dart b/lib/presentation/sales/blocs/order_loader/order_loader_bloc.freezed.dart index 79164f9..b28803b 100644 --- a/lib/presentation/sales/blocs/order_loader/order_loader_bloc.freezed.dart +++ b/lib/presentation/sales/blocs/order_loader/order_loader_bloc.freezed.dart @@ -21,6 +21,9 @@ mixin _$OrderLoaderEvent { required TResult Function(String status, DateTime dateFrom, DateTime dateTo) getByStatus, required TResult Function(String id) getById, + required TResult Function(String status, DateTime dateFrom, DateTime dateTo) + loadMore, + required TResult Function(String status) refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -28,6 +31,9 @@ mixin _$OrderLoaderEvent { TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? getByStatus, TResult? Function(String id)? getById, + TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult? Function(String status)? refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -35,6 +41,9 @@ mixin _$OrderLoaderEvent { TResult Function(String status, DateTime dateFrom, DateTime dateTo)? getByStatus, TResult Function(String id)? getById, + TResult Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult Function(String status)? refresh, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -42,18 +51,24 @@ mixin _$OrderLoaderEvent { TResult map({ required TResult Function(_GetByStatus value) getByStatus, required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ TResult? Function(_GetByStatus value)? getByStatus, TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ TResult Function(_GetByStatus value)? getByStatus, TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -169,6 +184,9 @@ class _$GetByStatusImpl implements _GetByStatus { required TResult Function(String status, DateTime dateFrom, DateTime dateTo) getByStatus, required TResult Function(String id) getById, + required TResult Function(String status, DateTime dateFrom, DateTime dateTo) + loadMore, + required TResult Function(String status) refresh, }) { return getByStatus(status, dateFrom, dateTo); } @@ -179,6 +197,9 @@ class _$GetByStatusImpl implements _GetByStatus { TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? getByStatus, TResult? Function(String id)? getById, + TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult? Function(String status)? refresh, }) { return getByStatus?.call(status, dateFrom, dateTo); } @@ -189,6 +210,9 @@ class _$GetByStatusImpl implements _GetByStatus { TResult Function(String status, DateTime dateFrom, DateTime dateTo)? getByStatus, TResult Function(String id)? getById, + TResult Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult Function(String status)? refresh, required TResult orElse(), }) { if (getByStatus != null) { @@ -202,6 +226,8 @@ class _$GetByStatusImpl implements _GetByStatus { TResult map({ required TResult Function(_GetByStatus value) getByStatus, required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, }) { return getByStatus(this); } @@ -211,6 +237,8 @@ class _$GetByStatusImpl implements _GetByStatus { TResult? mapOrNull({ TResult? Function(_GetByStatus value)? getByStatus, TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, }) { return getByStatus?.call(this); } @@ -220,6 +248,8 @@ class _$GetByStatusImpl implements _GetByStatus { TResult maybeMap({ TResult Function(_GetByStatus value)? getByStatus, TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, required TResult orElse(), }) { if (getByStatus != null) { @@ -316,6 +346,9 @@ class _$GetByIdImpl implements _GetById { required TResult Function(String status, DateTime dateFrom, DateTime dateTo) getByStatus, required TResult Function(String id) getById, + required TResult Function(String status, DateTime dateFrom, DateTime dateTo) + loadMore, + required TResult Function(String status) refresh, }) { return getById(id); } @@ -326,6 +359,9 @@ class _$GetByIdImpl implements _GetById { TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? getByStatus, TResult? Function(String id)? getById, + TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult? Function(String status)? refresh, }) { return getById?.call(id); } @@ -336,6 +372,9 @@ class _$GetByIdImpl implements _GetById { TResult Function(String status, DateTime dateFrom, DateTime dateTo)? getByStatus, TResult Function(String id)? getById, + TResult Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult Function(String status)? refresh, required TResult orElse(), }) { if (getById != null) { @@ -349,6 +388,8 @@ class _$GetByIdImpl implements _GetById { TResult map({ required TResult Function(_GetByStatus value) getByStatus, required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, }) { return getById(this); } @@ -358,6 +399,8 @@ class _$GetByIdImpl implements _GetById { TResult? mapOrNull({ TResult? Function(_GetByStatus value)? getByStatus, TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, }) { return getById?.call(this); } @@ -367,6 +410,8 @@ class _$GetByIdImpl implements _GetById { TResult maybeMap({ TResult Function(_GetByStatus value)? getByStatus, TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, required TResult orElse(), }) { if (getById != null) { @@ -388,13 +433,353 @@ abstract class _GetById implements OrderLoaderEvent { throw _privateConstructorUsedError; } +/// @nodoc +abstract class _$$LoadMoreImplCopyWith<$Res> { + factory _$$LoadMoreImplCopyWith( + _$LoadMoreImpl value, $Res Function(_$LoadMoreImpl) then) = + __$$LoadMoreImplCopyWithImpl<$Res>; + @useResult + $Res call({String status, DateTime dateFrom, DateTime dateTo}); +} + +/// @nodoc +class __$$LoadMoreImplCopyWithImpl<$Res> + extends _$OrderLoaderEventCopyWithImpl<$Res, _$LoadMoreImpl> + implements _$$LoadMoreImplCopyWith<$Res> { + __$$LoadMoreImplCopyWithImpl( + _$LoadMoreImpl _value, $Res Function(_$LoadMoreImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + Object? dateFrom = null, + Object? dateTo = null, + }) { + return _then(_$LoadMoreImpl( + null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + dateFrom: null == dateFrom + ? _value.dateFrom + : dateFrom // ignore: cast_nullable_to_non_nullable + as DateTime, + dateTo: null == dateTo + ? _value.dateTo + : dateTo // ignore: cast_nullable_to_non_nullable + as DateTime, + )); + } +} + +/// @nodoc + +class _$LoadMoreImpl implements _LoadMore { + const _$LoadMoreImpl(this.status, + {required this.dateFrom, required this.dateTo}); + + @override + final String status; + @override + final DateTime dateFrom; + @override + final DateTime dateTo; + + @override + String toString() { + return 'OrderLoaderEvent.loadMore(status: $status, dateFrom: $dateFrom, dateTo: $dateTo)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$LoadMoreImpl && + (identical(other.status, status) || other.status == status) && + (identical(other.dateFrom, dateFrom) || + other.dateFrom == dateFrom) && + (identical(other.dateTo, dateTo) || other.dateTo == dateTo)); + } + + @override + int get hashCode => Object.hash(runtimeType, status, dateFrom, dateTo); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + __$$LoadMoreImplCopyWithImpl<_$LoadMoreImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String status, DateTime dateFrom, DateTime dateTo) + getByStatus, + required TResult Function(String id) getById, + required TResult Function(String status, DateTime dateFrom, DateTime dateTo) + loadMore, + required TResult Function(String status) refresh, + }) { + return loadMore(status, dateFrom, dateTo); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? + getByStatus, + TResult? Function(String id)? getById, + TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult? Function(String status)? refresh, + }) { + return loadMore?.call(status, dateFrom, dateTo); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String status, DateTime dateFrom, DateTime dateTo)? + getByStatus, + TResult Function(String id)? getById, + TResult Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult Function(String status)? refresh, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(status, dateFrom, dateTo); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetByStatus value) getByStatus, + required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return loadMore(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetByStatus value)? getByStatus, + TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return loadMore?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetByStatus value)? getByStatus, + TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (loadMore != null) { + return loadMore(this); + } + return orElse(); + } +} + +abstract class _LoadMore implements OrderLoaderEvent { + const factory _LoadMore(final String status, + {required final DateTime dateFrom, + required final DateTime dateTo}) = _$LoadMoreImpl; + + String get status; + DateTime get dateFrom; + DateTime get dateTo; + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$LoadMoreImplCopyWith<_$LoadMoreImpl> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$RefreshImplCopyWith<$Res> { + factory _$$RefreshImplCopyWith( + _$RefreshImpl value, $Res Function(_$RefreshImpl) then) = + __$$RefreshImplCopyWithImpl<$Res>; + @useResult + $Res call({String status}); +} + +/// @nodoc +class __$$RefreshImplCopyWithImpl<$Res> + extends _$OrderLoaderEventCopyWithImpl<$Res, _$RefreshImpl> + implements _$$RefreshImplCopyWith<$Res> { + __$$RefreshImplCopyWithImpl( + _$RefreshImpl _value, $Res Function(_$RefreshImpl) _then) + : super(_value, _then); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? status = null, + }) { + return _then(_$RefreshImpl( + null == status + ? _value.status + : status // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc + +class _$RefreshImpl implements _Refresh { + const _$RefreshImpl(this.status); + + @override + final String status; + + @override + String toString() { + return 'OrderLoaderEvent.refresh(status: $status)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$RefreshImpl && + (identical(other.status, status) || other.status == status)); + } + + @override + int get hashCode => Object.hash(runtimeType, status); + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$RefreshImplCopyWith<_$RefreshImpl> get copyWith => + __$$RefreshImplCopyWithImpl<_$RefreshImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String status, DateTime dateFrom, DateTime dateTo) + getByStatus, + required TResult Function(String id) getById, + required TResult Function(String status, DateTime dateFrom, DateTime dateTo) + loadMore, + required TResult Function(String status) refresh, + }) { + return refresh(status); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? + getByStatus, + TResult? Function(String id)? getById, + TResult? Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult? Function(String status)? refresh, + }) { + return refresh?.call(status); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String status, DateTime dateFrom, DateTime dateTo)? + getByStatus, + TResult Function(String id)? getById, + TResult Function(String status, DateTime dateFrom, DateTime dateTo)? + loadMore, + TResult Function(String status)? refresh, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(status); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_GetByStatus value) getByStatus, + required TResult Function(_GetById value) getById, + required TResult Function(_LoadMore value) loadMore, + required TResult Function(_Refresh value) refresh, + }) { + return refresh(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_GetByStatus value)? getByStatus, + TResult? Function(_GetById value)? getById, + TResult? Function(_LoadMore value)? loadMore, + TResult? Function(_Refresh value)? refresh, + }) { + return refresh?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_GetByStatus value)? getByStatus, + TResult Function(_GetById value)? getById, + TResult Function(_LoadMore value)? loadMore, + TResult Function(_Refresh value)? refresh, + required TResult orElse(), + }) { + if (refresh != null) { + return refresh(this); + } + return orElse(); + } +} + +abstract class _Refresh implements OrderLoaderEvent { + const factory _Refresh(final String status) = _$RefreshImpl; + + String get status; + + /// Create a copy of OrderLoaderEvent + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + _$$RefreshImplCopyWith<_$RefreshImpl> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc mixin _$OrderLoaderState { @optionalTypeArgs TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List orders, int totalOrder) loaded, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, required TResult Function(String message) error, required TResult Function(Order order) loadedDetail, }) => @@ -403,7 +788,9 @@ mixin _$OrderLoaderState { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List orders, int totalOrder)? loaded, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult? Function(String message)? error, TResult? Function(Order order)? loadedDetail, }) => @@ -412,7 +799,9 @@ mixin _$OrderLoaderState { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List orders, int totalOrder)? loaded, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult Function(String message)? error, TResult Function(Order order)? loadedDetail, required TResult orElse(), @@ -512,7 +901,9 @@ class _$InitialImpl implements _Initial { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List orders, int totalOrder) loaded, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, required TResult Function(String message) error, required TResult Function(Order order) loadedDetail, }) { @@ -524,7 +915,9 @@ class _$InitialImpl implements _Initial { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List orders, int totalOrder)? loaded, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult? Function(String message)? error, TResult? Function(Order order)? loadedDetail, }) { @@ -536,7 +929,9 @@ class _$InitialImpl implements _Initial { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List orders, int totalOrder)? loaded, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult Function(String message)? error, TResult Function(Order order)? loadedDetail, required TResult orElse(), @@ -635,7 +1030,9 @@ class _$LoadingImpl implements _Loading { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List orders, int totalOrder) loaded, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, required TResult Function(String message) error, required TResult Function(Order order) loadedDetail, }) { @@ -647,7 +1044,9 @@ class _$LoadingImpl implements _Loading { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List orders, int totalOrder)? loaded, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult? Function(String message)? error, TResult? Function(Order order)? loadedDetail, }) { @@ -659,7 +1058,9 @@ class _$LoadingImpl implements _Loading { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List orders, int totalOrder)? loaded, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult Function(String message)? error, TResult Function(Order order)? loadedDetail, required TResult orElse(), @@ -721,7 +1122,12 @@ abstract class _$$LoadedImplCopyWith<$Res> { _$LoadedImpl value, $Res Function(_$LoadedImpl) then) = __$$LoadedImplCopyWithImpl<$Res>; @useResult - $Res call({List orders, int totalOrder}); + $Res call( + {List orders, + int totalOrder, + bool hasReachedMax, + int currentPage, + bool isLoadingMore}); } /// @nodoc @@ -739,16 +1145,31 @@ class __$$LoadedImplCopyWithImpl<$Res> $Res call({ Object? orders = null, Object? totalOrder = null, + Object? hasReachedMax = null, + Object? currentPage = null, + Object? isLoadingMore = null, }) { return _then(_$LoadedImpl( - null == orders + orders: null == orders ? _value._orders : orders // ignore: cast_nullable_to_non_nullable as List, - null == totalOrder + totalOrder: null == totalOrder ? _value.totalOrder : totalOrder // ignore: cast_nullable_to_non_nullable as int, + hasReachedMax: null == hasReachedMax + ? _value.hasReachedMax + : hasReachedMax // ignore: cast_nullable_to_non_nullable + as bool, + currentPage: null == currentPage + ? _value.currentPage + : currentPage // ignore: cast_nullable_to_non_nullable + as int, + isLoadingMore: null == isLoadingMore + ? _value.isLoadingMore + : isLoadingMore // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -756,7 +1177,12 @@ class __$$LoadedImplCopyWithImpl<$Res> /// @nodoc class _$LoadedImpl implements _Loaded { - const _$LoadedImpl(final List orders, this.totalOrder) + const _$LoadedImpl( + {required final List orders, + required this.totalOrder, + required this.hasReachedMax, + required this.currentPage, + required this.isLoadingMore}) : _orders = orders; final List _orders; @@ -769,10 +1195,16 @@ class _$LoadedImpl implements _Loaded { @override final int totalOrder; + @override + final bool hasReachedMax; + @override + final int currentPage; + @override + final bool isLoadingMore; @override String toString() { - return 'OrderLoaderState.loaded(orders: $orders, totalOrder: $totalOrder)'; + return 'OrderLoaderState.loaded(orders: $orders, totalOrder: $totalOrder, hasReachedMax: $hasReachedMax, currentPage: $currentPage, isLoadingMore: $isLoadingMore)'; } @override @@ -782,12 +1214,23 @@ class _$LoadedImpl implements _Loaded { other is _$LoadedImpl && const DeepCollectionEquality().equals(other._orders, _orders) && (identical(other.totalOrder, totalOrder) || - other.totalOrder == totalOrder)); + other.totalOrder == totalOrder) && + (identical(other.hasReachedMax, hasReachedMax) || + other.hasReachedMax == hasReachedMax) && + (identical(other.currentPage, currentPage) || + other.currentPage == currentPage) && + (identical(other.isLoadingMore, isLoadingMore) || + other.isLoadingMore == isLoadingMore)); } @override int get hashCode => Object.hash( - runtimeType, const DeepCollectionEquality().hash(_orders), totalOrder); + runtimeType, + const DeepCollectionEquality().hash(_orders), + totalOrder, + hasReachedMax, + currentPage, + isLoadingMore); /// Create a copy of OrderLoaderState /// with the given fields replaced by the non-null parameter values. @@ -802,11 +1245,14 @@ class _$LoadedImpl implements _Loaded { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List orders, int totalOrder) loaded, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, required TResult Function(String message) error, required TResult Function(Order order) loadedDetail, }) { - return loaded(orders, totalOrder); + return loaded( + orders, totalOrder, hasReachedMax, currentPage, isLoadingMore); } @override @@ -814,11 +1260,14 @@ class _$LoadedImpl implements _Loaded { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List orders, int totalOrder)? loaded, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult? Function(String message)? error, TResult? Function(Order order)? loadedDetail, }) { - return loaded?.call(orders, totalOrder); + return loaded?.call( + orders, totalOrder, hasReachedMax, currentPage, isLoadingMore); } @override @@ -826,13 +1275,16 @@ class _$LoadedImpl implements _Loaded { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List orders, int totalOrder)? loaded, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult Function(String message)? error, TResult Function(Order order)? loadedDetail, required TResult orElse(), }) { if (loaded != null) { - return loaded(orders, totalOrder); + return loaded( + orders, totalOrder, hasReachedMax, currentPage, isLoadingMore); } return orElse(); } @@ -879,11 +1331,18 @@ class _$LoadedImpl implements _Loaded { } abstract class _Loaded implements OrderLoaderState { - const factory _Loaded(final List orders, final int totalOrder) = - _$LoadedImpl; + const factory _Loaded( + {required final List orders, + required final int totalOrder, + required final bool hasReachedMax, + required final int currentPage, + required final bool isLoadingMore}) = _$LoadedImpl; List get orders; int get totalOrder; + bool get hasReachedMax; + int get currentPage; + bool get isLoadingMore; /// Create a copy of OrderLoaderState /// with the given fields replaced by the non-null parameter values. @@ -962,7 +1421,9 @@ class _$ErrorImpl implements _Error { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List orders, int totalOrder) loaded, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, required TResult Function(String message) error, required TResult Function(Order order) loadedDetail, }) { @@ -974,7 +1435,9 @@ class _$ErrorImpl implements _Error { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List orders, int totalOrder)? loaded, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult? Function(String message)? error, TResult? Function(Order order)? loadedDetail, }) { @@ -986,7 +1449,9 @@ class _$ErrorImpl implements _Error { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List orders, int totalOrder)? loaded, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult Function(String message)? error, TResult Function(Order order)? loadedDetail, required TResult orElse(), @@ -1120,7 +1585,9 @@ class _$LoadedDetailImpl implements _LoadedDetail { TResult when({ required TResult Function() initial, required TResult Function() loading, - required TResult Function(List orders, int totalOrder) loaded, + required TResult Function(List orders, int totalOrder, + bool hasReachedMax, int currentPage, bool isLoadingMore) + loaded, required TResult Function(String message) error, required TResult Function(Order order) loadedDetail, }) { @@ -1132,7 +1599,9 @@ class _$LoadedDetailImpl implements _LoadedDetail { TResult? whenOrNull({ TResult? Function()? initial, TResult? Function()? loading, - TResult? Function(List orders, int totalOrder)? loaded, + TResult? Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult? Function(String message)? error, TResult? Function(Order order)? loadedDetail, }) { @@ -1144,7 +1613,9 @@ class _$LoadedDetailImpl implements _LoadedDetail { TResult maybeWhen({ TResult Function()? initial, TResult Function()? loading, - TResult Function(List orders, int totalOrder)? loaded, + TResult Function(List orders, int totalOrder, bool hasReachedMax, + int currentPage, bool isLoadingMore)? + loaded, TResult Function(String message)? error, TResult Function(Order order)? loadedDetail, required TResult orElse(), diff --git a/lib/presentation/sales/blocs/order_loader/order_loader_event.dart b/lib/presentation/sales/blocs/order_loader/order_loader_event.dart index 8381aeb..e47f374 100644 --- a/lib/presentation/sales/blocs/order_loader/order_loader_event.dart +++ b/lib/presentation/sales/blocs/order_loader/order_loader_event.dart @@ -8,4 +8,10 @@ class OrderLoaderEvent with _$OrderLoaderEvent { required DateTime dateTo, }) = _GetByStatus; const factory OrderLoaderEvent.getById(String id) = _GetById; + const factory OrderLoaderEvent.loadMore( + String status, { + required DateTime dateFrom, + required DateTime dateTo, + }) = _LoadMore; + const factory OrderLoaderEvent.refresh(String status) = _Refresh; } diff --git a/lib/presentation/sales/blocs/order_loader/order_loader_state.dart b/lib/presentation/sales/blocs/order_loader/order_loader_state.dart index 40a4907..5d868bf 100644 --- a/lib/presentation/sales/blocs/order_loader/order_loader_state.dart +++ b/lib/presentation/sales/blocs/order_loader/order_loader_state.dart @@ -4,8 +4,13 @@ part of 'order_loader_bloc.dart'; class OrderLoaderState with _$OrderLoaderState { const factory OrderLoaderState.initial() = _Initial; const factory OrderLoaderState.loading() = _Loading; - const factory OrderLoaderState.loaded(List orders, int totalOrder) = - _Loaded; + const factory OrderLoaderState.loaded({ + required List orders, + required int totalOrder, + required bool hasReachedMax, + required int currentPage, + required bool isLoadingMore, + }) = _Loaded; const factory OrderLoaderState.error(String message) = _Error; const factory OrderLoaderState.loadedDetail(Order order) = _LoadedDetail; } diff --git a/lib/presentation/sales/pages/sales_page.dart b/lib/presentation/sales/pages/sales_page.dart index 56c1eb0..6c88a23 100644 --- a/lib/presentation/sales/pages/sales_page.dart +++ b/lib/presentation/sales/pages/sales_page.dart @@ -29,6 +29,7 @@ class SalesPage extends StatefulWidget { } class _SalesPageState extends State { + ScrollController scrollController = ScrollController(); DateTime startDate = DateTime.now(); DateTime endDate = DateTime.now(); Order? orderDetail; @@ -65,69 +66,82 @@ class _SalesPageState extends State { children: [ Expanded( flex: 2, - child: Material( - color: AppColors.white, - child: Column( - children: [ - SalesTitle( - title: widget.status == 'pending' - ? "Pending Pesanan" - : "Daftar Pesanan", - startDate: startDate, - endDate: endDate, - onChanged: (value) { - setState(() { - searchQuery = value; - }); - }, - onDateRangeChanged: (start, end) { - setState(() { - startDate = start; - endDate = end; - }); + child: NotificationListener( + onNotification: (notification) { + if (notification is ScrollEndNotification && + scrollController.position.extentAfter == 0) { + context.read().add( + OrderLoaderEvent.loadMore(widget.status, + dateFrom: startDate, dateTo: endDate)); + return true; + } - context.read().add( - OrderLoaderEvent.getByStatus(widget.status, - dateFrom: startDate, dateTo: endDate)); - }, - ), - Expanded( - child: BlocBuilder( - builder: (context, state) { - return state.maybeWhen( - orElse: () => const Center( - child: CircularProgressIndicator(), - ), - loading: () => const Center( - child: CircularProgressIndicator(), - ), - error: (message) => Center( - child: Text( - message, - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, + return true; + }, + child: Material( + color: AppColors.white, + child: Column( + children: [ + SalesTitle( + title: widget.status == 'pending' + ? "Pending Pesanan" + : "Daftar Pesanan", + startDate: startDate, + endDate: endDate, + onChanged: (value) { + setState(() { + searchQuery = value; + }); + }, + onDateRangeChanged: (start, end) { + setState(() { + startDate = start; + endDate = end; + }); + + context.read().add( + OrderLoaderEvent.getByStatus(widget.status, + dateFrom: startDate, dateTo: endDate)); + }, + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + orElse: () => const Center( + child: CircularProgressIndicator(), + ), + loading: () => const Center( + child: CircularProgressIndicator(), + ), + error: (message) => Center( + child: Text( + message, + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), ), ), - ), - loaded: (orders, totalOrder) { - final filtered = _filterOrders(orders); - if (filtered.isEmpty) { - return Center( - child: Text( - "Belum ada transaksi saat ini. ", - style: TextStyle( - fontSize: 16.0, - fontWeight: FontWeight.bold, + loaded: (orders, totalOrder, hasReachedMax, + currentPage, isLoadingMore) { + final filtered = _filterOrders(orders); + if (filtered.isEmpty) { + return Center( + child: Text( + "Belum ada transaksi saat ini. ", + style: TextStyle( + fontSize: 16.0, + fontWeight: FontWeight.bold, + ), ), - ), - ); - } else { - return SingleChildScrollView( - child: Column( - children: List.generate( - filtered.length, - (index) => GestureDetector( + ); + } else { + return ListView.builder( + itemCount: filtered.length, + controller: scrollController, + itemBuilder: (context, index) { + return GestureDetector( onTap: () { setState(() { orderDetail = filtered[index]; @@ -141,17 +155,17 @@ class _SalesPageState extends State { isActive: orders[index] == orderDetail, ), - ), - ), - ), - ); - } - }, - ); - }, + ); + }, + ); + } + }, + ); + }, + ), ), - ), - ], + ], + ), ), ), ), @@ -202,18 +216,6 @@ class _SalesPageState extends State { ); }), SpaceWidth(8), - Button.outlined( - onPressed: () { - context.push( - PaymentPage( - order: orderDetail!, - ), - ); - }, - label: 'Bayar', - icon: Icon(Icons.payment), - ), - SpaceWidth(8), Button.outlined( onPressed: () { context.push( @@ -223,7 +225,22 @@ class _SalesPageState extends State { ); }, label: 'Split Bill', - icon: Icon(Icons.payment), + icon: Icon( + Icons.calculate_outlined, + ), + ), + SpaceWidth(8), + Button.filled( + width: 120, + onPressed: () { + context.push( + PaymentPage( + order: orderDetail!, + ), + ); + }, + label: 'Bayar', + icon: Icon(Icons.payment, color: Colors.white), ), ], if (widget.status == 'completed') diff --git a/lib/presentation/sales/widgets/sales_title.dart b/lib/presentation/sales/widgets/sales_title.dart index 82cbd1f..0700828 100644 --- a/lib/presentation/sales/widgets/sales_title.dart +++ b/lib/presentation/sales/widgets/sales_title.dart @@ -91,7 +91,7 @@ class SalesTitle extends StatelessWidget { builder: (context, state) { return state.maybeWhen( orElse: () => const SizedBox.shrink(), - loaded: (orders, totalOrder) => Text( + loaded: (orders, totalOrder, _, __, ___) => Text( '$totalOrder Pesanan', style: TextStyle( color: AppColors.black,