dev #1

Merged
aefril merged 128 commits from dev into main 2025-08-13 17:19:48 +00:00
5 changed files with 551 additions and 199 deletions
Showing only changes of commit f0ff078e0e - Show all commits

View File

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart'; import 'package:enaklo_pos/data/datasources/product_remote_datasource.dart';
import 'package:enaklo_pos/data/models/response/product_response_model.dart'; import 'package:enaklo_pos/data/models/response/product_response_model.dart';
@ -8,19 +10,137 @@ part 'get_products_state.dart';
part 'get_products_bloc.freezed.dart'; part 'get_products_bloc.freezed.dart';
class GetProductsBloc extends Bloc<GetProductsEvent, GetProductsState> { class GetProductsBloc extends Bloc<GetProductsEvent, GetProductsState> {
final ProductRemoteDatasource datasource; final ProductRemoteDatasource _productRemoteDatasource;
GetProductsBloc(
this.datasource, // Debouncing untuk mencegah multiple load more calls
) : super(const _Initial()) { Timer? _loadMoreDebounce;
on<_Fetch>((event, emit) async { bool _isLoadingMore = false;
GetProductsBloc(this._productRemoteDatasource)
: super(GetProductsState.initial()) {
on<_Fetch>(_onGetProduct);
on<_LoadMore>(_onLoadMore);
on<_Refresh>(_onRefresh);
}
@override
Future<void> close() {
_loadMoreDebounce?.cancel();
return super.close();
}
// Debounce transformer untuk load more
// EventTransformer<T> _debounceTransformer<T>() {
// return (events, mapper) {
// return events
// .debounceTime(const Duration(milliseconds: 300))
// .asyncExpand(mapper);
// };
// }
// Initial load
Future<void> _onGetProduct(
_Fetch event,
Emitter<GetProductsState> emit,
) async {
emit(const _Loading()); emit(const _Loading());
final response = await datasource.getProducts(); _isLoadingMore = false; // Reset loading state
response.fold(
(l) => emit(_Error(l)), final result = await _productRemoteDatasource.getProducts(
(r) { page: 1,
emit(_Success(r.data!.products!)); limit: 10,
);
await result.fold(
(failure) async => emit(_Error(failure)),
(response) async {
final products = response.data?.products ?? [];
final hasReachedMax = products.length < 10;
emit(_Success(
products: products,
hasReachedMax: hasReachedMax,
currentPage: 1,
isLoadingMore: false,
));
}, },
); );
}); }
// Load more with enhanced debouncing
Future<void> _onLoadMore(
_LoadMore event,
Emitter<GetProductsState> emit,
) async {
final currentState = state;
// Enhanced validation
if (currentState is! _Success ||
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 _productRemoteDatasource.getProducts(
page: nextPage,
limit: 10,
);
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 newProducts = response.data?.products ?? [];
// Prevent duplicate products
final currentProductIds =
currentState.products.map((p) => p.id).toSet();
final filteredNewProducts = newProducts
.where((product) => !currentProductIds.contains(product.id))
.toList();
final allProducts = List<Product>.from(currentState.products)
..addAll(filteredNewProducts);
final hasReachedMax = newProducts.length < 10;
emit(_Success(
products: allProducts,
hasReachedMax: hasReachedMax,
currentPage: nextPage,
isLoadingMore: false,
));
_isLoadingMore = false;
},
);
} catch (e) {
// Handle unexpected errors
emit(currentState.copyWith(isLoadingMore: false));
_isLoadingMore = false;
}
}
// Refresh data
Future<void> _onRefresh(
_Refresh event,
Emitter<GetProductsState> emit,
) async {
_isLoadingMore = false;
_loadMoreDebounce?.cancel();
add(const _Fetch());
} }
} }

View File

@ -18,39 +18,45 @@ final _privateConstructorUsedError = UnsupportedError(
mixin _$GetProductsEvent { mixin _$GetProductsEvent {
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() started,
required TResult Function() fetch, required TResult Function() fetch,
required TResult Function() loadMore,
required TResult Function() refresh,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? started,
TResult? Function()? fetch, TResult? Function()? fetch,
TResult? Function()? loadMore,
TResult? Function()? refresh,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? started,
TResult Function()? fetch, TResult Function()? fetch,
TResult Function()? loadMore,
TResult Function()? refresh,
required TResult orElse(), required TResult orElse(),
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult map<TResult extends Object?>({ TResult map<TResult extends Object?>({
required TResult Function(_Started value) started,
required TResult Function(_Fetch value) fetch, required TResult Function(_Fetch value) fetch,
required TResult Function(_LoadMore value) loadMore,
required TResult Function(_Refresh value) refresh,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({ TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Started value)? started,
TResult? Function(_Fetch value)? fetch, TResult? Function(_Fetch value)? fetch,
TResult? Function(_LoadMore value)? loadMore,
TResult? Function(_Refresh value)? refresh,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@optionalTypeArgs @optionalTypeArgs
TResult maybeMap<TResult extends Object?>({ TResult maybeMap<TResult extends Object?>({
TResult Function(_Started value)? started,
TResult Function(_Fetch value)? fetch, TResult Function(_Fetch value)? fetch,
TResult Function(_LoadMore value)? loadMore,
TResult Function(_Refresh value)? refresh,
required TResult orElse(), required TResult orElse(),
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@ -77,111 +83,6 @@ class _$GetProductsEventCopyWithImpl<$Res, $Val extends GetProductsEvent>
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
} }
/// @nodoc
abstract class _$$StartedImplCopyWith<$Res> {
factory _$$StartedImplCopyWith(
_$StartedImpl value, $Res Function(_$StartedImpl) then) =
__$$StartedImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$StartedImplCopyWithImpl<$Res>
extends _$GetProductsEventCopyWithImpl<$Res, _$StartedImpl>
implements _$$StartedImplCopyWith<$Res> {
__$$StartedImplCopyWithImpl(
_$StartedImpl _value, $Res Function(_$StartedImpl) _then)
: super(_value, _then);
/// Create a copy of GetProductsEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$StartedImpl implements _Started {
const _$StartedImpl();
@override
String toString() {
return 'GetProductsEvent.started()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$StartedImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() started,
required TResult Function() fetch,
}) {
return started();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? started,
TResult? Function()? fetch,
}) {
return started?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? started,
TResult Function()? fetch,
required TResult orElse(),
}) {
if (started != null) {
return started();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Started value) started,
required TResult Function(_Fetch value) fetch,
}) {
return started(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Started value)? started,
TResult? Function(_Fetch value)? fetch,
}) {
return started?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Started value)? started,
TResult Function(_Fetch value)? fetch,
required TResult orElse(),
}) {
if (started != null) {
return started(this);
}
return orElse();
}
}
abstract class _Started implements GetProductsEvent {
const factory _Started() = _$StartedImpl;
}
/// @nodoc /// @nodoc
abstract class _$$FetchImplCopyWith<$Res> { abstract class _$$FetchImplCopyWith<$Res> {
factory _$$FetchImplCopyWith( factory _$$FetchImplCopyWith(
@ -223,8 +124,9 @@ class _$FetchImpl implements _Fetch {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() started,
required TResult Function() fetch, required TResult Function() fetch,
required TResult Function() loadMore,
required TResult Function() refresh,
}) { }) {
return fetch(); return fetch();
} }
@ -232,8 +134,9 @@ class _$FetchImpl implements _Fetch {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? started,
TResult? Function()? fetch, TResult? Function()? fetch,
TResult? Function()? loadMore,
TResult? Function()? refresh,
}) { }) {
return fetch?.call(); return fetch?.call();
} }
@ -241,8 +144,9 @@ class _$FetchImpl implements _Fetch {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? started,
TResult Function()? fetch, TResult Function()? fetch,
TResult Function()? loadMore,
TResult Function()? refresh,
required TResult orElse(), required TResult orElse(),
}) { }) {
if (fetch != null) { if (fetch != null) {
@ -254,8 +158,9 @@ class _$FetchImpl implements _Fetch {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult map<TResult extends Object?>({ TResult map<TResult extends Object?>({
required TResult Function(_Started value) started,
required TResult Function(_Fetch value) fetch, required TResult Function(_Fetch value) fetch,
required TResult Function(_LoadMore value) loadMore,
required TResult Function(_Refresh value) refresh,
}) { }) {
return fetch(this); return fetch(this);
} }
@ -263,8 +168,9 @@ class _$FetchImpl implements _Fetch {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({ TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Started value)? started,
TResult? Function(_Fetch value)? fetch, TResult? Function(_Fetch value)? fetch,
TResult? Function(_LoadMore value)? loadMore,
TResult? Function(_Refresh value)? refresh,
}) { }) {
return fetch?.call(this); return fetch?.call(this);
} }
@ -272,8 +178,9 @@ class _$FetchImpl implements _Fetch {
@override @override
@optionalTypeArgs @optionalTypeArgs
TResult maybeMap<TResult extends Object?>({ TResult maybeMap<TResult extends Object?>({
TResult Function(_Started value)? started,
TResult Function(_Fetch value)? fetch, TResult Function(_Fetch value)? fetch,
TResult Function(_LoadMore value)? loadMore,
TResult Function(_Refresh value)? refresh,
required TResult orElse(), required TResult orElse(),
}) { }) {
if (fetch != null) { if (fetch != null) {
@ -287,13 +194,237 @@ abstract class _Fetch implements GetProductsEvent {
const factory _Fetch() = _$FetchImpl; const factory _Fetch() = _$FetchImpl;
} }
/// @nodoc
abstract class _$$LoadMoreImplCopyWith<$Res> {
factory _$$LoadMoreImplCopyWith(
_$LoadMoreImpl value, $Res Function(_$LoadMoreImpl) then) =
__$$LoadMoreImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$LoadMoreImplCopyWithImpl<$Res>
extends _$GetProductsEventCopyWithImpl<$Res, _$LoadMoreImpl>
implements _$$LoadMoreImplCopyWith<$Res> {
__$$LoadMoreImplCopyWithImpl(
_$LoadMoreImpl _value, $Res Function(_$LoadMoreImpl) _then)
: super(_value, _then);
/// Create a copy of GetProductsEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$LoadMoreImpl implements _LoadMore {
const _$LoadMoreImpl();
@override
String toString() {
return 'GetProductsEvent.loadMore()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$LoadMoreImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() fetch,
required TResult Function() loadMore,
required TResult Function() refresh,
}) {
return loadMore();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? fetch,
TResult? Function()? loadMore,
TResult? Function()? refresh,
}) {
return loadMore?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? fetch,
TResult Function()? loadMore,
TResult Function()? refresh,
required TResult orElse(),
}) {
if (loadMore != null) {
return loadMore();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Fetch value) fetch,
required TResult Function(_LoadMore value) loadMore,
required TResult Function(_Refresh value) refresh,
}) {
return loadMore(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Fetch value)? fetch,
TResult? Function(_LoadMore value)? loadMore,
TResult? Function(_Refresh value)? refresh,
}) {
return loadMore?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Fetch value)? fetch,
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 GetProductsEvent {
const factory _LoadMore() = _$LoadMoreImpl;
}
/// @nodoc
abstract class _$$RefreshImplCopyWith<$Res> {
factory _$$RefreshImplCopyWith(
_$RefreshImpl value, $Res Function(_$RefreshImpl) then) =
__$$RefreshImplCopyWithImpl<$Res>;
}
/// @nodoc
class __$$RefreshImplCopyWithImpl<$Res>
extends _$GetProductsEventCopyWithImpl<$Res, _$RefreshImpl>
implements _$$RefreshImplCopyWith<$Res> {
__$$RefreshImplCopyWithImpl(
_$RefreshImpl _value, $Res Function(_$RefreshImpl) _then)
: super(_value, _then);
/// Create a copy of GetProductsEvent
/// with the given fields replaced by the non-null parameter values.
}
/// @nodoc
class _$RefreshImpl implements _Refresh {
const _$RefreshImpl();
@override
String toString() {
return 'GetProductsEvent.refresh()';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType && other is _$RefreshImpl);
}
@override
int get hashCode => runtimeType.hashCode;
@override
@optionalTypeArgs
TResult when<TResult extends Object?>({
required TResult Function() fetch,
required TResult Function() loadMore,
required TResult Function() refresh,
}) {
return refresh();
}
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? fetch,
TResult? Function()? loadMore,
TResult? Function()? refresh,
}) {
return refresh?.call();
}
@override
@optionalTypeArgs
TResult maybeWhen<TResult extends Object?>({
TResult Function()? fetch,
TResult Function()? loadMore,
TResult Function()? refresh,
required TResult orElse(),
}) {
if (refresh != null) {
return refresh();
}
return orElse();
}
@override
@optionalTypeArgs
TResult map<TResult extends Object?>({
required TResult Function(_Fetch value) fetch,
required TResult Function(_LoadMore value) loadMore,
required TResult Function(_Refresh value) refresh,
}) {
return refresh(this);
}
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult? Function(_Fetch value)? fetch,
TResult? Function(_LoadMore value)? loadMore,
TResult? Function(_Refresh value)? refresh,
}) {
return refresh?.call(this);
}
@override
@optionalTypeArgs
TResult maybeMap<TResult extends Object?>({
TResult Function(_Fetch value)? fetch,
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 GetProductsEvent {
const factory _Refresh() = _$RefreshImpl;
}
/// @nodoc /// @nodoc
mixin _$GetProductsState { mixin _$GetProductsState {
@optionalTypeArgs @optionalTypeArgs
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() initial, required TResult Function() initial,
required TResult Function() loading, required TResult Function() loading,
required TResult Function(List<Product> products) success, required TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)
success,
required TResult Function(String message) error, required TResult Function(String message) error,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@ -301,7 +432,9 @@ mixin _$GetProductsState {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial, TResult? Function()? initial,
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function(List<Product> products)? success, TResult? Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult? Function(String message)? error, TResult? Function(String message)? error,
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
@ -309,7 +442,9 @@ mixin _$GetProductsState {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial, TResult Function()? initial,
TResult Function()? loading, TResult Function()? loading,
TResult Function(List<Product> products)? success, TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult Function(String message)? error, TResult Function(String message)? error,
required TResult orElse(), required TResult orElse(),
}) => }) =>
@ -405,7 +540,9 @@ class _$InitialImpl implements _Initial {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() initial, required TResult Function() initial,
required TResult Function() loading, required TResult Function() loading,
required TResult Function(List<Product> products) success, required TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)
success,
required TResult Function(String message) error, required TResult Function(String message) error,
}) { }) {
return initial(); return initial();
@ -416,7 +553,9 @@ class _$InitialImpl implements _Initial {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial, TResult? Function()? initial,
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function(List<Product> products)? success, TResult? Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult? Function(String message)? error, TResult? Function(String message)? error,
}) { }) {
return initial?.call(); return initial?.call();
@ -427,7 +566,9 @@ class _$InitialImpl implements _Initial {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial, TResult Function()? initial,
TResult Function()? loading, TResult Function()? loading,
TResult Function(List<Product> products)? success, TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult Function(String message)? error, TResult Function(String message)? error,
required TResult orElse(), required TResult orElse(),
}) { }) {
@ -522,7 +663,9 @@ class _$LoadingImpl implements _Loading {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() initial, required TResult Function() initial,
required TResult Function() loading, required TResult Function() loading,
required TResult Function(List<Product> products) success, required TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)
success,
required TResult Function(String message) error, required TResult Function(String message) error,
}) { }) {
return loading(); return loading();
@ -533,7 +676,9 @@ class _$LoadingImpl implements _Loading {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial, TResult? Function()? initial,
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function(List<Product> products)? success, TResult? Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult? Function(String message)? error, TResult? Function(String message)? error,
}) { }) {
return loading?.call(); return loading?.call();
@ -544,7 +689,9 @@ class _$LoadingImpl implements _Loading {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial, TResult Function()? initial,
TResult Function()? loading, TResult Function()? loading,
TResult Function(List<Product> products)? success, TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult Function(String message)? error, TResult Function(String message)? error,
required TResult orElse(), required TResult orElse(),
}) { }) {
@ -602,7 +749,11 @@ abstract class _$$SuccessImplCopyWith<$Res> {
_$SuccessImpl value, $Res Function(_$SuccessImpl) then) = _$SuccessImpl value, $Res Function(_$SuccessImpl) then) =
__$$SuccessImplCopyWithImpl<$Res>; __$$SuccessImplCopyWithImpl<$Res>;
@useResult @useResult
$Res call({List<Product> products}); $Res call(
{List<Product> products,
bool hasReachedMax,
int currentPage,
bool isLoadingMore});
} }
/// @nodoc /// @nodoc
@ -619,12 +770,27 @@ class __$$SuccessImplCopyWithImpl<$Res>
@override @override
$Res call({ $Res call({
Object? products = null, Object? products = null,
Object? hasReachedMax = null,
Object? currentPage = null,
Object? isLoadingMore = null,
}) { }) {
return _then(_$SuccessImpl( return _then(_$SuccessImpl(
null == products products: null == products
? _value._products ? _value._products
: products // ignore: cast_nullable_to_non_nullable : products // ignore: cast_nullable_to_non_nullable
as List<Product>, as List<Product>,
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,
)); ));
} }
} }
@ -632,7 +798,12 @@ class __$$SuccessImplCopyWithImpl<$Res>
/// @nodoc /// @nodoc
class _$SuccessImpl implements _Success { class _$SuccessImpl implements _Success {
const _$SuccessImpl(final List<Product> products) : _products = products; const _$SuccessImpl(
{required final List<Product> products,
required this.hasReachedMax,
required this.currentPage,
required this.isLoadingMore})
: _products = products;
final List<Product> _products; final List<Product> _products;
@override @override
@ -642,9 +813,16 @@ class _$SuccessImpl implements _Success {
return EqualUnmodifiableListView(_products); return EqualUnmodifiableListView(_products);
} }
@override
final bool hasReachedMax;
@override
final int currentPage;
@override
final bool isLoadingMore;
@override @override
String toString() { String toString() {
return 'GetProductsState.success(products: $products)'; return 'GetProductsState.success(products: $products, hasReachedMax: $hasReachedMax, currentPage: $currentPage, isLoadingMore: $isLoadingMore)';
} }
@override @override
@ -652,12 +830,22 @@ class _$SuccessImpl implements _Success {
return identical(this, other) || return identical(this, other) ||
(other.runtimeType == runtimeType && (other.runtimeType == runtimeType &&
other is _$SuccessImpl && other is _$SuccessImpl &&
const DeepCollectionEquality().equals(other._products, _products)); const DeepCollectionEquality().equals(other._products, _products) &&
(identical(other.hasReachedMax, hasReachedMax) ||
other.hasReachedMax == hasReachedMax) &&
(identical(other.currentPage, currentPage) ||
other.currentPage == currentPage) &&
(identical(other.isLoadingMore, isLoadingMore) ||
other.isLoadingMore == isLoadingMore));
} }
@override @override
int get hashCode => int get hashCode => Object.hash(
Object.hash(runtimeType, const DeepCollectionEquality().hash(_products)); runtimeType,
const DeepCollectionEquality().hash(_products),
hasReachedMax,
currentPage,
isLoadingMore);
/// Create a copy of GetProductsState /// Create a copy of GetProductsState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -672,10 +860,12 @@ class _$SuccessImpl implements _Success {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() initial, required TResult Function() initial,
required TResult Function() loading, required TResult Function() loading,
required TResult Function(List<Product> products) success, required TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)
success,
required TResult Function(String message) error, required TResult Function(String message) error,
}) { }) {
return success(products); return success(products, hasReachedMax, currentPage, isLoadingMore);
} }
@override @override
@ -683,10 +873,12 @@ class _$SuccessImpl implements _Success {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial, TResult? Function()? initial,
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function(List<Product> products)? success, TResult? Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult? Function(String message)? error, TResult? Function(String message)? error,
}) { }) {
return success?.call(products); return success?.call(products, hasReachedMax, currentPage, isLoadingMore);
} }
@override @override
@ -694,12 +886,14 @@ class _$SuccessImpl implements _Success {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial, TResult Function()? initial,
TResult Function()? loading, TResult Function()? loading,
TResult Function(List<Product> products)? success, TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult Function(String message)? error, TResult Function(String message)? error,
required TResult orElse(), required TResult orElse(),
}) { }) {
if (success != null) { if (success != null) {
return success(products); return success(products, hasReachedMax, currentPage, isLoadingMore);
} }
return orElse(); return orElse();
} }
@ -743,9 +937,16 @@ class _$SuccessImpl implements _Success {
} }
abstract class _Success implements GetProductsState { abstract class _Success implements GetProductsState {
const factory _Success(final List<Product> products) = _$SuccessImpl; const factory _Success(
{required final List<Product> products,
required final bool hasReachedMax,
required final int currentPage,
required final bool isLoadingMore}) = _$SuccessImpl;
List<Product> get products; List<Product> get products;
bool get hasReachedMax;
int get currentPage;
bool get isLoadingMore;
/// Create a copy of GetProductsState /// Create a copy of GetProductsState
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@ -824,7 +1025,9 @@ class _$ErrorImpl implements _Error {
TResult when<TResult extends Object?>({ TResult when<TResult extends Object?>({
required TResult Function() initial, required TResult Function() initial,
required TResult Function() loading, required TResult Function() loading,
required TResult Function(List<Product> products) success, required TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)
success,
required TResult Function(String message) error, required TResult Function(String message) error,
}) { }) {
return error(message); return error(message);
@ -835,7 +1038,9 @@ class _$ErrorImpl implements _Error {
TResult? whenOrNull<TResult extends Object?>({ TResult? whenOrNull<TResult extends Object?>({
TResult? Function()? initial, TResult? Function()? initial,
TResult? Function()? loading, TResult? Function()? loading,
TResult? Function(List<Product> products)? success, TResult? Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult? Function(String message)? error, TResult? Function(String message)? error,
}) { }) {
return error?.call(message); return error?.call(message);
@ -846,7 +1051,9 @@ class _$ErrorImpl implements _Error {
TResult maybeWhen<TResult extends Object?>({ TResult maybeWhen<TResult extends Object?>({
TResult Function()? initial, TResult Function()? initial,
TResult Function()? loading, TResult Function()? loading,
TResult Function(List<Product> products)? success, TResult Function(List<Product> products, bool hasReachedMax,
int currentPage, bool isLoadingMore)?
success,
TResult Function(String message)? error, TResult Function(String message)? error,
required TResult orElse(), required TResult orElse(),
}) { }) {

View File

@ -2,6 +2,7 @@ part of 'get_products_bloc.dart';
@freezed @freezed
class GetProductsEvent with _$GetProductsEvent { class GetProductsEvent with _$GetProductsEvent {
const factory GetProductsEvent.started() = _Started;
const factory GetProductsEvent.fetch() = _Fetch; const factory GetProductsEvent.fetch() = _Fetch;
const factory GetProductsEvent.loadMore() = _LoadMore;
const factory GetProductsEvent.refresh() = _Refresh;
} }

View File

@ -4,6 +4,11 @@ part of 'get_products_bloc.dart';
class GetProductsState with _$GetProductsState { class GetProductsState with _$GetProductsState {
const factory GetProductsState.initial() = _Initial; const factory GetProductsState.initial() = _Initial;
const factory GetProductsState.loading() = _Loading; const factory GetProductsState.loading() = _Loading;
const factory GetProductsState.success(List<Product> products) = _Success; const factory GetProductsState.success({
required List<Product> products,
required bool hasReachedMax,
required int currentPage,
required bool isLoadingMore,
}) = _Success;
const factory GetProductsState.error(String message) = _Error; const factory GetProductsState.error(String message) = _Error;
} }

View File

@ -19,6 +19,7 @@ class ProductPage extends StatefulWidget {
} }
class _ProductPageState extends State<ProductPage> { class _ProductPageState extends State<ProductPage> {
ScrollController scrollController = ScrollController();
@override @override
void initState() { void initState() {
context.read<GetProductsBloc>().add(const GetProductsEvent.fetch()); context.read<GetProductsBloc>().add(const GetProductsEvent.fetch());
@ -64,13 +65,30 @@ class _ProductPageState extends State<ProductPage> {
Expanded( Expanded(
child: BlocBuilder<GetProductsBloc, GetProductsState>( child: BlocBuilder<GetProductsBloc, GetProductsState>(
builder: (context, state) { builder: (context, state) {
return state.maybeWhen(orElse: () { return NotificationListener<ScrollNotification>(
onNotification: (notification) {
return state.maybeWhen(
orElse: () => false,
success: (products, hasReachedMax, currentPage, _) {
if (notification is ScrollEndNotification &&
scrollController.position.extentAfter == 0) {
context
.read<GetProductsBloc>()
.add(const GetProductsEvent.loadMore());
return true;
}
return true;
});
},
child: state.maybeWhen(orElse: () {
return const Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
}, success: (products) { }, success: (products, hasReachedMax, currentPage, _) {
return GridView.builder( return GridView.builder(
padding: EdgeInsets.all(16), padding: EdgeInsets.all(16),
controller: scrollController,
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 200, maxCrossAxisExtent: 200,
mainAxisSpacing: 30, mainAxisSpacing: 30,
@ -105,7 +123,8 @@ class _ProductPageState extends State<ProductPage> {
); );
}, },
); );
}); }),
);
}, },
), ),
), ),