Refactor tenders pagination and enhance API integration

- Updated TendersRepository and TendersService to support cursor-based pagination.
- Modified TendersViewModel to manage pagination state and handle API responses more effectively.
- Replaced existing pagination UI components with a new WindowedPagination widget across desktop, mobile, and tablet views.
- Improved notification handling in the UI to reflect the presence of notifications dynamically.
- Adjusted main tenders slider to ensure proper rendering based on the current page index.
This commit is contained in:
AmirReza Jamali
2026-05-26 18:30:46 +03:30
parent 02057988dc
commit ae08b946f6
20 changed files with 927 additions and 652 deletions
@@ -13,7 +13,8 @@ class TendersRepository {
Future<Result<TendersResponse>> getTenders({
required int limit,
required int offset,
int? offset,
String? cursor,
String? query,
String? sortBy,
String? sortOrder,
@@ -23,6 +24,7 @@ class TendersRepository {
return _tendersService.getTenders(
limit: limit,
offset: offset,
cursor: cursor,
query: query,
sortBy: sortBy,
sortOrder: sortOrder,
+8 -2
View File
@@ -4,14 +4,20 @@ class TendersApi {
static const String tenders = '/api/v1/tenders';
static String getTenders({
required int limit,
required int offset,
int? offset,
String? cursor,
String? query,
String? sortBy,
String? sortOrder,
String? publicationDateFrom,
String? publicationDateTo,
}) {
String url = '$tenders?limit=$limit&offset=$offset';
String url = '$tenders?limit=$limit';
if (cursor != null && cursor.isNotEmpty) {
url += '&cursor=${Uri.encodeComponent(cursor)}';
} else if (offset != null) {
url += '&offset=$offset';
}
if (query != null && query.isNotEmpty) {
url += '&q=${Uri.encodeComponent(query)}';
}
+3
View File
@@ -13,6 +13,9 @@ abstract class Meta with _$Meta {
required int? page,
required int? pages,
required int? total,
@JsonKey(name: 'has_more') bool? hasMore,
@JsonKey(name: 'next_cursor') String? nextCursor,
@JsonKey(name: 'prev_cursor') String? prevCursor,
}) = _Meta;
factory Meta.fromJson(Map<String, dynamic> json) => _$MetaFromJson(json);
+29 -20
View File
@@ -15,7 +15,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$Meta {
int? get limit; int? get offset; int? get page; int? get pages; int? get total;
int? get limit; int? get offset; int? get page; int? get pages; int? get total;@JsonKey(name: 'has_more') bool? get hasMore;@JsonKey(name: 'next_cursor') String? get nextCursor;@JsonKey(name: 'prev_cursor') String? get prevCursor;
/// Create a copy of Meta
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -28,16 +28,16 @@ $MetaCopyWith<Meta> get copyWith => _$MetaCopyWithImpl<Meta>(this as Meta, _$ide
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is Meta&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages)&&(identical(other.total, total) || other.total == total));
return identical(this, other) || (other.runtimeType == runtimeType&&other is Meta&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages)&&(identical(other.total, total) || other.total == total)&&(identical(other.hasMore, hasMore) || other.hasMore == hasMore)&&(identical(other.nextCursor, nextCursor) || other.nextCursor == nextCursor)&&(identical(other.prevCursor, prevCursor) || other.prevCursor == prevCursor));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,limit,offset,page,pages,total);
int get hashCode => Object.hash(runtimeType,limit,offset,page,pages,total,hasMore,nextCursor,prevCursor);
@override
String toString() {
return 'Meta(limit: $limit, offset: $offset, page: $page, pages: $pages, total: $total)';
return 'Meta(limit: $limit, offset: $offset, page: $page, pages: $pages, total: $total, hasMore: $hasMore, nextCursor: $nextCursor, prevCursor: $prevCursor)';
}
@@ -48,7 +48,7 @@ abstract mixin class $MetaCopyWith<$Res> {
factory $MetaCopyWith(Meta value, $Res Function(Meta) _then) = _$MetaCopyWithImpl;
@useResult
$Res call({
int? limit, int? offset, int? page, int? pages, int? total
int? limit, int? offset, int? page, int? pages, int? total,@JsonKey(name: 'has_more') bool? hasMore,@JsonKey(name: 'next_cursor') String? nextCursor,@JsonKey(name: 'prev_cursor') String? prevCursor
});
@@ -65,14 +65,17 @@ class _$MetaCopyWithImpl<$Res>
/// Create a copy of Meta
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? limit = freezed,Object? offset = freezed,Object? page = freezed,Object? pages = freezed,Object? total = freezed,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? limit = freezed,Object? offset = freezed,Object? page = freezed,Object? pages = freezed,Object? total = freezed,Object? hasMore = freezed,Object? nextCursor = freezed,Object? prevCursor = freezed,}) {
return _then(_self.copyWith(
limit: freezed == limit ? _self.limit : limit // ignore: cast_nullable_to_non_nullable
as int?,offset: freezed == offset ? _self.offset : offset // ignore: cast_nullable_to_non_nullable
as int?,page: freezed == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
as int?,pages: freezed == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
as int?,total: freezed == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
as int?,
as int?,hasMore: freezed == hasMore ? _self.hasMore : hasMore // ignore: cast_nullable_to_non_nullable
as bool?,nextCursor: freezed == nextCursor ? _self.nextCursor : nextCursor // ignore: cast_nullable_to_non_nullable
as String?,prevCursor: freezed == prevCursor ? _self.prevCursor : prevCursor // ignore: cast_nullable_to_non_nullable
as String?,
));
}
@@ -157,10 +160,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? limit, int? offset, int? page, int? pages, int? total)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( int? limit, int? offset, int? page, int? pages, int? total, @JsonKey(name: 'has_more') bool? hasMore, @JsonKey(name: 'next_cursor') String? nextCursor, @JsonKey(name: 'prev_cursor') String? prevCursor)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _Meta() when $default != null:
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);case _:
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total,_that.hasMore,_that.nextCursor,_that.prevCursor);case _:
return orElse();
}
@@ -178,10 +181,10 @@ return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);cas
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? limit, int? offset, int? page, int? pages, int? total) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( int? limit, int? offset, int? page, int? pages, int? total, @JsonKey(name: 'has_more') bool? hasMore, @JsonKey(name: 'next_cursor') String? nextCursor, @JsonKey(name: 'prev_cursor') String? prevCursor) $default,) {final _that = this;
switch (_that) {
case _Meta():
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);case _:
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total,_that.hasMore,_that.nextCursor,_that.prevCursor);case _:
throw StateError('Unexpected subclass');
}
@@ -198,10 +201,10 @@ return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);cas
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? limit, int? offset, int? page, int? pages, int? total)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( int? limit, int? offset, int? page, int? pages, int? total, @JsonKey(name: 'has_more') bool? hasMore, @JsonKey(name: 'next_cursor') String? nextCursor, @JsonKey(name: 'prev_cursor') String? prevCursor)? $default,) {final _that = this;
switch (_that) {
case _Meta() when $default != null:
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);case _:
return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total,_that.hasMore,_that.nextCursor,_that.prevCursor);case _:
return null;
}
@@ -213,7 +216,7 @@ return $default(_that.limit,_that.offset,_that.page,_that.pages,_that.total);cas
@JsonSerializable()
class _Meta implements Meta {
const _Meta({required this.limit, required this.offset, required this.page, required this.pages, required this.total});
const _Meta({required this.limit, required this.offset, required this.page, required this.pages, required this.total, @JsonKey(name: 'has_more') this.hasMore, @JsonKey(name: 'next_cursor') this.nextCursor, @JsonKey(name: 'prev_cursor') this.prevCursor});
factory _Meta.fromJson(Map<String, dynamic> json) => _$MetaFromJson(json);
@override final int? limit;
@@ -221,6 +224,9 @@ class _Meta implements Meta {
@override final int? page;
@override final int? pages;
@override final int? total;
@override@JsonKey(name: 'has_more') final bool? hasMore;
@override@JsonKey(name: 'next_cursor') final String? nextCursor;
@override@JsonKey(name: 'prev_cursor') final String? prevCursor;
/// Create a copy of Meta
/// with the given fields replaced by the non-null parameter values.
@@ -235,16 +241,16 @@ Map<String, dynamic> toJson() {
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Meta&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages)&&(identical(other.total, total) || other.total == total));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Meta&&(identical(other.limit, limit) || other.limit == limit)&&(identical(other.offset, offset) || other.offset == offset)&&(identical(other.page, page) || other.page == page)&&(identical(other.pages, pages) || other.pages == pages)&&(identical(other.total, total) || other.total == total)&&(identical(other.hasMore, hasMore) || other.hasMore == hasMore)&&(identical(other.nextCursor, nextCursor) || other.nextCursor == nextCursor)&&(identical(other.prevCursor, prevCursor) || other.prevCursor == prevCursor));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType,limit,offset,page,pages,total);
int get hashCode => Object.hash(runtimeType,limit,offset,page,pages,total,hasMore,nextCursor,prevCursor);
@override
String toString() {
return 'Meta(limit: $limit, offset: $offset, page: $page, pages: $pages, total: $total)';
return 'Meta(limit: $limit, offset: $offset, page: $page, pages: $pages, total: $total, hasMore: $hasMore, nextCursor: $nextCursor, prevCursor: $prevCursor)';
}
@@ -255,7 +261,7 @@ abstract mixin class _$MetaCopyWith<$Res> implements $MetaCopyWith<$Res> {
factory _$MetaCopyWith(_Meta value, $Res Function(_Meta) _then) = __$MetaCopyWithImpl;
@override @useResult
$Res call({
int? limit, int? offset, int? page, int? pages, int? total
int? limit, int? offset, int? page, int? pages, int? total,@JsonKey(name: 'has_more') bool? hasMore,@JsonKey(name: 'next_cursor') String? nextCursor,@JsonKey(name: 'prev_cursor') String? prevCursor
});
@@ -272,14 +278,17 @@ class __$MetaCopyWithImpl<$Res>
/// Create a copy of Meta
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? limit = freezed,Object? offset = freezed,Object? page = freezed,Object? pages = freezed,Object? total = freezed,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? limit = freezed,Object? offset = freezed,Object? page = freezed,Object? pages = freezed,Object? total = freezed,Object? hasMore = freezed,Object? nextCursor = freezed,Object? prevCursor = freezed,}) {
return _then(_Meta(
limit: freezed == limit ? _self.limit : limit // ignore: cast_nullable_to_non_nullable
as int?,offset: freezed == offset ? _self.offset : offset // ignore: cast_nullable_to_non_nullable
as int?,page: freezed == page ? _self.page : page // ignore: cast_nullable_to_non_nullable
as int?,pages: freezed == pages ? _self.pages : pages // ignore: cast_nullable_to_non_nullable
as int?,total: freezed == total ? _self.total : total // ignore: cast_nullable_to_non_nullable
as int?,
as int?,hasMore: freezed == hasMore ? _self.hasMore : hasMore // ignore: cast_nullable_to_non_nullable
as bool?,nextCursor: freezed == nextCursor ? _self.nextCursor : nextCursor // ignore: cast_nullable_to_non_nullable
as String?,prevCursor: freezed == prevCursor ? _self.prevCursor : prevCursor // ignore: cast_nullable_to_non_nullable
as String?,
));
}
+6
View File
@@ -12,6 +12,9 @@ _Meta _$MetaFromJson(Map<String, dynamic> json) => _Meta(
page: (json['page'] as num?)?.toInt(),
pages: (json['pages'] as num?)?.toInt(),
total: (json['total'] as num?)?.toInt(),
hasMore: json['has_more'] as bool?,
nextCursor: json['next_cursor'] as String?,
prevCursor: json['prev_cursor'] as String?,
);
Map<String, dynamic> _$MetaToJson(_Meta instance) => <String, dynamic>{
@@ -20,4 +23,7 @@ Map<String, dynamic> _$MetaToJson(_Meta instance) => <String, dynamic>{
'page': instance.page,
'pages': instance.pages,
'total': instance.total,
'has_more': instance.hasMore,
'next_cursor': instance.nextCursor,
'prev_cursor': instance.prevCursor,
};
+3 -2
View File
@@ -16,7 +16,8 @@ class TendersService {
Future<Result<TendersResponse>> getTenders({
required int limit,
required int offset,
int? offset,
String? cursor,
String? query,
String? sortBy,
String? sortOrder,
@@ -27,6 +28,7 @@ class TendersService {
TendersApi.getTenders(
limit: limit,
offset: offset,
cursor: cursor,
query: query,
sortBy: sortBy,
sortOrder: sortOrder,
@@ -37,7 +39,6 @@ class TendersService {
method: 'GET',
(json) => TendersResponse.fromJson(json),
);
print(result);
return result;
}