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> {
final GetOrderUseCase _getOrdersUseCase;
OrderBloc(this._getOrdersUseCase)
: super(const OrderInitial(
orders: [],
params: FilterProductParams(),
)) {
: super(
OrderInitial(
orders: const [],
params: const FilterProductParams(),
metaData: PaginationMetaData(
pageSize: 10,
limit: 0,
total: 0,
),
),
) {
on<GetOrders>(_onGetOrders);
on<GetMoreOrders>(_onLoadMoreOrders);
}
FutureOr<void> _onGetOrders(
@ -26,26 +34,73 @@ class OrderBloc extends Bloc<OrderEvent, OrderState> {
try {
emit(OrderLoading(
orders: const [],
metaData: state.metaData,
params: event.params,
));
final result = await _getOrdersUseCase(event.params);
result.fold(
(failure) => emit(OrderError(
orders: state.orders,
metaData: state.metaData,
failure: failure,
params: event.params,
)),
(orders) => emit(OrderLoaded(
orders: orders,
(response) => emit(OrderLoaded(
metaData: response.paginationMetaData,
orders: response.orders,
params: event.params,
)),
);
} catch (e) {
emit(OrderError(
orders: state.orders,
metaData: state.metaData,
failure: ExceptionFailure(),
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 {
final List<OrderEntity> orders;
final PaginationMetaData metaData;
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 {
const OrderInitial({
required super.orders,
required super.metaData,
required super.params,
});
@override
@ -18,6 +24,7 @@ class OrderInitial extends OrderState {
class OrderEmpty extends OrderState {
const OrderEmpty({
required super.orders,
required super.metaData,
required super.params,
});
@override
@ -27,6 +34,7 @@ class OrderEmpty extends OrderState {
class OrderLoading extends OrderState {
const OrderLoading({
required super.orders,
required super.metaData,
required super.params,
});
@override
@ -36,6 +44,7 @@ class OrderLoading extends OrderState {
class OrderLoaded extends OrderState {
const OrderLoaded({
required super.orders,
required super.metaData,
required super.params,
});
@override
@ -46,6 +55,7 @@ class OrderError extends OrderState {
final Failure failure;
const OrderError({
required super.orders,
required super.metaData,
required super.params,
required this.failure,
});

View File

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

View File

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

View File

@ -1,3 +1,5 @@
export 'user/user.dart';
export 'order/order.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 {
final int? limit;
final int? pageSize;
final int offset;
final int limit;
const FilterProductParams({
this.limit = 0,
this.pageSize = 10,
this.offset = 1,
this.limit = 10,
});
FilterProductParams copyWith({
int? skip,
int? offset,
int? limit,
int? pageSize,
}) =>
FilterProductParams(
limit: skip ?? this.limit,
pageSize: pageSize ?? this.pageSize,
);
}) {
return FilterProductParams(
offset: offset ?? this.offset,
limit: limit ?? this.limit,
);
}
}

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 '../../core/errors/failures.dart';
import '../../data/models/order/order_response_model.dart';
import '../domain.dart';
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 '../../../core/core.dart';
import '../../../data/models/order/order_response_model.dart';
import '../../domain.dart';
class GetOrderUseCase implements UseCase<List<OrderEntity>, FilterProductParams> {
class GetOrderUseCase implements UseCase<OrderResponseModel, FilterProductParams> {
final OrderRepository repository;
GetOrderUseCase(this.repository);
@override
Future<Either<Failure, List<OrderEntity>>> call(FilterProductParams params) async {
Future<Either<Failure, OrderResponseModel>> call(FilterProductParams params) async {
return await repository.getOrders(params);
}
}

View File

@ -23,79 +23,86 @@ class _OrdersScreenState extends State<OrdersScreen> {
Widget build(BuildContext context) {
App.init(context);
// Provide the OrderBloc
return Scaffold(
backgroundColor: AppColors.surface,
body: CustomScrollView(
slivers: [
const SliverToBoxAdapter(
child: OrderHeader(),
),
SliverToBoxAdapter(
child: Padding(
padding: Space.all(1, 1),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Sargytlarym',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
body: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) {
context.read<OrderBloc>().add(const GetMoreOrders());
}
return false;
},
child: CustomScrollView(
slivers: [
const SliverToBoxAdapter(
child: OrderHeader(),
),
SliverToBoxAdapter(
child: Padding(
padding: Space.all(1, 1),
child: const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Sargytlarym',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
),
),
),
Text(
'öz ýüküňizi yzarlaň',
style: TextStyle(
color: Colors.grey,
Text(
'öz ýüküňizi yzarlaň',
style: TextStyle(
color: Colors.grey,
),
),
),
],
],
),
),
),
),
// Use BlocBuilder to respond to state changes
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
if (state is OrderLoading) {
// Display a loading indicator while fetching orders
return const SliverToBoxAdapter(
child: Center(
child: CircularProgressIndicator(),
),
);
} else if (state is OrderError) {
// Display an error message if there was a failure
return SliverToBoxAdapter(
child: Center(
child: Text(
'Failed to load orders: ${state.failure}',
style: const TextStyle(color: Colors.red),
BlocBuilder<OrderBloc, OrderState>(
builder: (context, state) {
if (state is OrderLoading && state.orders.isEmpty) {
return const SliverToBoxAdapter(
child: Center(
child: CircularProgressIndicator(),
),
),
);
} else if (state is OrderLoaded) {
// Display the list of orders
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
final order = state.orders[index];
return OrderCard(order: order);
},
childCount: state.orders.length,
),
);
} else {
// Default case (initial state)
return const SliverToBoxAdapter(
child: Center(
child: Text('No orders available'),
),
);
}
},
),
],
);
} else if (state is OrderError && state.orders.isEmpty) {
return SliverToBoxAdapter(
child: Center(
child: RetryWidget(onRetry: () {
context.read<OrderBloc>().add(GetOrders(state.params));
}),
),
);
} else if (state is OrderLoaded) {
return SliverList(
delegate: SliverChildBuilderDelegate(
(BuildContext context, int 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);
},
childCount: state.orders.length,
),
);
} else {
return const SliverToBoxAdapter(
child: Center(
child: Text('No orders available'),
),
);
}
},
),
],
),
),
);
}

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 'bottom_navbar.dart';
export 'button.dart';
export 'dashed_line.dart';
export 'error_dialog.dart';
export 'info_card.dart';
export 'lang_selection.dart';
export 'location_card.dart';
export 'order_card.dart';
export 'order_header.dart';
export 'retry_widget.dart';
export 'successful_auth_dialog.dart';
export 'vertical_line.dart';
export 'dashed_line.dart';
export 'lang_selection.dart';