pagination addedr

This commit is contained in:
komekh 2024-08-13 19:09:44 +05:00
parent 7285b8f104
commit 0ad7d44642
16 changed files with 281 additions and 100 deletions

View File

@ -12,11 +12,19 @@ part 'order_state.dart';
class OrderBloc extends Bloc<OrderEvent, OrderState> { class OrderBloc extends Bloc<OrderEvent, OrderState> {
final GetOrderUseCase _getOrdersUseCase; final GetOrderUseCase _getOrdersUseCase;
OrderBloc(this._getOrdersUseCase) OrderBloc(this._getOrdersUseCase)
: super(const OrderInitial( : super(
orders: [], OrderInitial(
params: FilterProductParams(), orders: const [],
)) { params: const FilterProductParams(),
metaData: PaginationMetaData(
pageSize: 10,
limit: 0,
total: 0,
),
),
) {
on<GetOrders>(_onGetOrders); on<GetOrders>(_onGetOrders);
on<GetMoreOrders>(_onLoadMoreOrders);
} }
FutureOr<void> _onGetOrders( FutureOr<void> _onGetOrders(
@ -26,26 +34,73 @@ class OrderBloc extends Bloc<OrderEvent, OrderState> {
try { try {
emit(OrderLoading( emit(OrderLoading(
orders: const [], orders: const [],
metaData: state.metaData,
params: event.params, params: event.params,
)); ));
final result = await _getOrdersUseCase(event.params); final result = await _getOrdersUseCase(event.params);
result.fold( result.fold(
(failure) => emit(OrderError( (failure) => emit(OrderError(
orders: state.orders, orders: state.orders,
metaData: state.metaData,
failure: failure, failure: failure,
params: event.params, params: event.params,
)), )),
(orders) => emit(OrderLoaded( (response) => emit(OrderLoaded(
orders: orders, metaData: response.paginationMetaData,
orders: response.orders,
params: event.params, params: event.params,
)), )),
); );
} catch (e) { } catch (e) {
emit(OrderError( emit(OrderError(
orders: state.orders, orders: state.orders,
metaData: state.metaData,
failure: ExceptionFailure(), failure: ExceptionFailure(),
params: event.params, params: event.params,
)); ));
} }
} }
void _onLoadMoreOrders(GetMoreOrders event, Emitter<OrderState> emit) async {
var state = this.state;
var limit = state.metaData.limit;
var total = state.metaData.total;
var loadedProductsLength = state.orders.length;
// check state and loaded products amount[loadedProductsLength] compare with
// number of results total[total] results available in server
if (state is OrderLoaded && (loadedProductsLength < total)) {
try {
emit(OrderLoading(
orders: state.orders,
metaData: state.metaData,
params: state.params,
));
final result = await _getOrdersUseCase(FilterProductParams(limit: limit + 10));
result.fold(
(failure) => emit(OrderError(
orders: state.orders,
metaData: state.metaData,
failure: failure,
params: state.params,
)),
(response) {
List<OrderEntity> products = state.orders;
products.addAll(response.orders);
emit(OrderLoaded(
metaData: state.metaData,
orders: products,
params: state.params,
));
},
);
} catch (e) {
emit(OrderError(
orders: state.orders,
metaData: state.metaData,
failure: ExceptionFailure(),
params: state.params,
));
}
}
}
} }

View File

@ -2,13 +2,19 @@ part of 'order_bloc.dart';
abstract class OrderState extends Equatable { abstract class OrderState extends Equatable {
final List<OrderEntity> orders; final List<OrderEntity> orders;
final PaginationMetaData metaData;
final FilterProductParams params; final FilterProductParams params;
const OrderState({required this.orders, required this.params}); const OrderState({
required this.orders,
required this.metaData,
required this.params,
});
} }
class OrderInitial extends OrderState { class OrderInitial extends OrderState {
const OrderInitial({ const OrderInitial({
required super.orders, required super.orders,
required super.metaData,
required super.params, required super.params,
}); });
@override @override
@ -18,6 +24,7 @@ class OrderInitial extends OrderState {
class OrderEmpty extends OrderState { class OrderEmpty extends OrderState {
const OrderEmpty({ const OrderEmpty({
required super.orders, required super.orders,
required super.metaData,
required super.params, required super.params,
}); });
@override @override
@ -27,6 +34,7 @@ class OrderEmpty extends OrderState {
class OrderLoading extends OrderState { class OrderLoading extends OrderState {
const OrderLoading({ const OrderLoading({
required super.orders, required super.orders,
required super.metaData,
required super.params, required super.params,
}); });
@override @override
@ -36,6 +44,7 @@ class OrderLoading extends OrderState {
class OrderLoaded extends OrderState { class OrderLoaded extends OrderState {
const OrderLoaded({ const OrderLoaded({
required super.orders, required super.orders,
required super.metaData,
required super.params, required super.params,
}); });
@override @override
@ -46,6 +55,7 @@ class OrderError extends OrderState {
final Failure failure; final Failure failure;
const OrderError({ const OrderError({
required super.orders, required super.orders,
required super.metaData,
required super.params, required super.params,
required this.failure, required this.failure,
}); });

View File

@ -1,5 +1,3 @@
import 'dart:convert';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import '../../../core/core.dart'; import '../../../core/core.dart';
@ -7,7 +5,7 @@ import '../../../domain/domain.dart';
import '../../data.dart'; import '../../data.dart';
abstract class OrderRemoteDataSource { abstract class OrderRemoteDataSource {
Future<List<OrderModel>> getOrders(FilterProductParams params, String token); Future<OrderResponseModel> getOrders(FilterProductParams params, String token);
} }
class OrderRemoteDataSourceImpl implements OrderRemoteDataSource { class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {
@ -15,9 +13,9 @@ class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {
OrderRemoteDataSourceImpl({required this.client}); OrderRemoteDataSourceImpl({required this.client});
@override @override
Future<List<OrderModel>> getOrders(FilterProductParams params, String token) async { Future<OrderResponseModel> getOrders(FilterProductParams params, String token) async {
final response = await client.get( final response = await client.get(
Uri.parse('$baseUrl/Goods'), Uri.parse('$baseUrl/Goods?pageNumber=${params.offset}&pageSize=${params.limit}'),
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'accept': '*/*', 'accept': '*/*',
@ -26,9 +24,7 @@ class OrderRemoteDataSourceImpl implements OrderRemoteDataSource {
); );
if (response.statusCode == 200) { if (response.statusCode == 200) {
List<dynamic> jsonData = json.decode(response.body); return orderResponseModelFromJson(response.body);
final list = jsonData.map((item) => OrderModel.fromJson(item)).toList();
return list;
} else { } else {
throw ServerException(); throw ServerException();
} }

View File

@ -2,3 +2,4 @@ export 'language.dart';
export 'user/authentication_response_model.dart'; export 'user/authentication_response_model.dart';
export 'user/user_model.dart'; export 'user/user_model.dart';
export 'order/order_model.dart'; export 'order/order_model.dart';
export 'order/order_response_model.dart';

View File

@ -0,0 +1,30 @@
import 'dart:convert';
import '../../../domain/domain.dart';
import 'order_model.dart';
import 'pagination_data_model.dart';
OrderResponseModel orderResponseModelFromJson(String str) => OrderResponseModel.fromJson(json.decode(str));
// String orderResponseModelToJson(OrderResponseModel data) => json.encode(data.toJson());
class OrderResponseModel extends OrderResponse {
OrderResponseModel({
required PaginationMetaData meta,
required List<OrderEntity> data,
}) : super(orders: data, paginationMetaData: meta);
factory OrderResponseModel.fromJson(Map<String, dynamic> json) => OrderResponseModel(
meta: PaginationMetaDataModel(
page: json['PageNumber'],
pageSize: json['PageSize'],
total: json['TotalRecords'],
),
data: List<OrderModel>.from(json['Data'].map((x) => OrderModel.fromJson(x))),
);
/* Map<String, dynamic> toJson() => {
'meta': (paginationMetaData as PaginationMetaDataModel).toJson(),
'Data': List<dynamic>.from((orders as List<OrderModel>).map((x) => x.toJson())),
}; */
}

View File

@ -0,0 +1,21 @@
import 'package:cargo/domain/entities/order/pagination_meta_data.dart';
class PaginationMetaDataModel extends PaginationMetaData {
PaginationMetaDataModel({
required int page,
required super.pageSize,
required super.total,
}) : super(limit: page);
factory PaginationMetaDataModel.fromJson(Map<String, dynamic> json) => PaginationMetaDataModel(
page: json['PageNumber'],
pageSize: json['PageSize'],
total: json['TotalRecords'],
);
Map<String, dynamic> toJson() => {
'PageNumber': limit,
'PageSize': pageSize,
'TotalRecords': total,
};
}

View File

@ -3,6 +3,7 @@ import 'package:dartz/dartz.dart';
import '../../core/core.dart'; import '../../core/core.dart';
import '../../domain/domain.dart'; import '../../domain/domain.dart';
import '../data_sources/data_sources.dart'; import '../data_sources/data_sources.dart';
import '../models/order/order_response_model.dart';
class OrderRepositoryImpl extends OrderRepository { class OrderRepositoryImpl extends OrderRepository {
final OrderRemoteDataSource remoteDataSource; final OrderRemoteDataSource remoteDataSource;
@ -16,7 +17,7 @@ class OrderRepositoryImpl extends OrderRepository {
}); });
@override @override
Future<Either<Failure, List<OrderEntity>>> getOrders(FilterProductParams params) async { Future<Either<Failure, OrderResponseModel>> getOrders(FilterProductParams params) async {
if (!await networkInfo.isConnected) { if (!await networkInfo.isConnected) {
return Left(NetworkFailure()); return Left(NetworkFailure());
} }
@ -27,8 +28,8 @@ class OrderRepositoryImpl extends OrderRepository {
try { try {
final String token = await localDataSource.getToken(); final String token = await localDataSource.getToken();
final orders = await remoteDataSource.getOrders(params, token); final response = await remoteDataSource.getOrders(params, token);
return Right(orders); return Right(response);
} on Failure catch (failure) { } on Failure catch (failure) {
return Left(failure); return Left(failure);
} }

View File

@ -1,3 +1,5 @@
export 'user/user.dart'; export 'user/user.dart';
export 'order/order.dart'; export 'order/order.dart';
export 'order/filter_params_model.dart'; export 'order/filter_params_model.dart';
export 'order/order_response.dart';
export 'order/pagination_meta_data.dart';

View File

@ -1,19 +1,19 @@
class FilterProductParams { class FilterProductParams {
final int? limit; final int offset;
final int? pageSize; final int limit;
const FilterProductParams({ const FilterProductParams({
this.limit = 0, this.offset = 1,
this.pageSize = 10, this.limit = 10,
}); });
FilterProductParams copyWith({ FilterProductParams copyWith({
int? skip, int? offset,
int? limit, int? limit,
int? pageSize, }) {
}) => return FilterProductParams(
FilterProductParams( offset: offset ?? this.offset,
limit: skip ?? this.limit, limit: limit ?? this.limit,
pageSize: pageSize ?? this.pageSize,
); );
} }
}

View File

@ -0,0 +1,9 @@
import 'pagination_meta_data.dart';
import 'order.dart';
class OrderResponse {
final List<OrderEntity> orders;
final PaginationMetaData paginationMetaData;
OrderResponse({required this.orders, required this.paginationMetaData});
}

View File

@ -0,0 +1,11 @@
class PaginationMetaData {
final int limit;
final int pageSize;
final int total;
PaginationMetaData({
required this.limit,
required this.pageSize,
required this.total,
});
}

View File

@ -1,8 +1,9 @@
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import '../../core/errors/failures.dart'; import '../../core/errors/failures.dart';
import '../../data/models/order/order_response_model.dart';
import '../domain.dart'; import '../domain.dart';
abstract class OrderRepository { abstract class OrderRepository {
Future<Either<Failure, List<OrderEntity>>> getOrders(FilterProductParams params); Future<Either<Failure, OrderResponseModel>> getOrders(FilterProductParams params);
} }

View File

@ -1,14 +1,15 @@
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import '../../../core/core.dart'; import '../../../core/core.dart';
import '../../../data/models/order/order_response_model.dart';
import '../../domain.dart'; import '../../domain.dart';
class GetOrderUseCase implements UseCase<List<OrderEntity>, FilterProductParams> { class GetOrderUseCase implements UseCase<OrderResponseModel, FilterProductParams> {
final OrderRepository repository; final OrderRepository repository;
GetOrderUseCase(this.repository); GetOrderUseCase(this.repository);
@override @override
Future<Either<Failure, List<OrderEntity>>> call(FilterProductParams params) async { Future<Either<Failure, OrderResponseModel>> call(FilterProductParams params) async {
return await repository.getOrders(params); return await repository.getOrders(params);
} }
} }

View File

@ -23,10 +23,16 @@ class _OrdersScreenState extends State<OrdersScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
App.init(context); App.init(context);
// Provide the OrderBloc
return Scaffold( return Scaffold(
backgroundColor: AppColors.surface, backgroundColor: AppColors.surface,
body: CustomScrollView( body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
context.read<OrderBloc>().add(const GetMoreOrders());
}
return false;
},
child: CustomScrollView(
slivers: [ slivers: [
const SliverToBoxAdapter( const SliverToBoxAdapter(
child: OrderHeader(), child: OrderHeader(),
@ -54,39 +60,39 @@ class _OrdersScreenState extends State<OrdersScreen> {
), ),
), ),
), ),
// Use BlocBuilder to respond to state changes
BlocBuilder<OrderBloc, OrderState>( BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) { builder: (context, state) {
if (state is OrderLoading) { if (state is OrderLoading && state.orders.isEmpty) {
// Display a loading indicator while fetching orders
return const SliverToBoxAdapter( return const SliverToBoxAdapter(
child: Center( child: Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
); );
} else if (state is OrderError) { } else if (state is OrderError && state.orders.isEmpty) {
// Display an error message if there was a failure
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Center( child: Center(
child: Text( child: RetryWidget(onRetry: () {
'Failed to load orders: ${state.failure}', context.read<OrderBloc>().add(GetOrders(state.params));
style: const TextStyle(color: Colors.red), }),
),
), ),
); );
} else if (state is OrderLoaded) { } else if (state is OrderLoaded) {
// Display the list of orders
return SliverList( return SliverList(
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
final order = state.orders[index]; final order = state.orders[index];
if (index == state.orders.length - 1) {
// Trigger loading more orders when reaching the bottom
context.read<OrderBloc>().add(const GetMoreOrders());
}
return OrderCard(order: order); return OrderCard(order: order);
}, },
childCount: state.orders.length, childCount: state.orders.length,
), ),
); );
} else { } else {
// Default case (initial state)
return const SliverToBoxAdapter( return const SliverToBoxAdapter(
child: Center( child: Center(
child: Text('No orders available'), child: Text('No orders available'),
@ -97,6 +103,7 @@ class _OrdersScreenState extends State<OrdersScreen> {
), ),
], ],
), ),
),
); );
} }
} }

View File

@ -0,0 +1,35 @@
import 'package:flutter/material.dart';
class RetryWidget extends StatelessWidget {
final VoidCallback onRetry;
final String message;
const RetryWidget({
super.key,
required this.onRetry,
this.message = 'Something went wrong. Please try again.',
});
@override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
message,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 16, color: Colors.black54),
),
),
ElevatedButton(
onPressed: onRetry,
child: const Text('Retry'),
),
],
),
);
}
}

View File

@ -1,12 +1,13 @@
export 'auth_error_dialog.dart'; export 'auth_error_dialog.dart';
export 'bottom_navbar.dart'; export 'bottom_navbar.dart';
export 'button.dart'; export 'button.dart';
export 'dashed_line.dart';
export 'error_dialog.dart'; export 'error_dialog.dart';
export 'info_card.dart'; export 'info_card.dart';
export 'lang_selection.dart';
export 'location_card.dart'; export 'location_card.dart';
export 'order_card.dart'; export 'order_card.dart';
export 'order_header.dart'; export 'order_header.dart';
export 'retry_widget.dart';
export 'successful_auth_dialog.dart'; export 'successful_auth_dialog.dart';
export 'vertical_line.dart'; export 'vertical_line.dart';
export 'dashed_line.dart';
export 'lang_selection.dart';