From ae08b946f6569dc21fc082985167a13e7c14ee5c Mon Sep 17 00:00:00 2001 From: AmirReza Jamali Date: Tue, 26 May 2026 18:30:46 +0330 Subject: [PATCH] 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. --- lib/data/repositories/tenders_repository.dart | 4 +- lib/data/services/api/tenders_api.dart | 10 +- lib/data/services/model/meta/meta.dart | 3 + .../services/model/meta/meta.freezed.dart | 49 +- lib/data/services/model/meta/meta.g.dart | 6 + lib/data/services/tenders_service.dart | 5 +- lib/main.dart | 9 +- lib/view_models/tenders_view_model.dart | 271 ++++---- lib/views/home/pages/d_home_page.dart | 2 +- lib/views/home/pages/m_home_page.dart | 2 +- lib/views/home/pages/t_home_page.dart | 2 +- .../pages/d_notification_page.dart | 53 +- .../pages/m_notification_page.dart | 51 +- .../pages/t_notification_page.dart | 53 +- lib/views/tenders/pages/d_tenders_page.dart | 103 +-- lib/views/tenders/pages/m_tenders_page.dart | 29 +- lib/views/tenders/pages/t_tenders_page.dart | 31 +- .../tenders/widgets/main_tenders_slider.dart | 32 - .../tenders/widgets/windowed_pagination.dart | 250 +++++++ pubspec.lock | 614 +++++++++--------- 20 files changed, 927 insertions(+), 652 deletions(-) create mode 100644 lib/views/tenders/widgets/windowed_pagination.dart diff --git a/lib/data/repositories/tenders_repository.dart b/lib/data/repositories/tenders_repository.dart index 567e010..d8a38d7 100644 --- a/lib/data/repositories/tenders_repository.dart +++ b/lib/data/repositories/tenders_repository.dart @@ -13,7 +13,8 @@ class TendersRepository { Future> 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, diff --git a/lib/data/services/api/tenders_api.dart b/lib/data/services/api/tenders_api.dart index 1b582e4..96dc88a 100644 --- a/lib/data/services/api/tenders_api.dart +++ b/lib/data/services/api/tenders_api.dart @@ -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)}'; } diff --git a/lib/data/services/model/meta/meta.dart b/lib/data/services/model/meta/meta.dart index 39ea5ca..650b869 100644 --- a/lib/data/services/model/meta/meta.dart +++ b/lib/data/services/model/meta/meta.dart @@ -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 json) => _$MetaFromJson(json); diff --git a/lib/data/services/model/meta/meta.freezed.dart b/lib/data/services/model/meta/meta.freezed.dart index 5926249..f408471 100644 --- a/lib/data/services/model/meta/meta.freezed.dart +++ b/lib/data/services/model/meta/meta.freezed.dart @@ -15,7 +15,7 @@ T _$identity(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 get copyWith => _$MetaCopyWithImpl(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 Function( int? limit, int? offset, int? page, int? pages, int? total)? $default,{required TResult orElse(),}) {final _that = this; +@optionalTypeArgs TResult maybeWhen(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 Function( int? limit, int? offset, int? page, int? pages, int? total) $default,) {final _that = this; +@optionalTypeArgs TResult when(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? Function( int? limit, int? offset, int? page, int? pages, int? total)? $default,) {final _that = this; +@optionalTypeArgs TResult? whenOrNull(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 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 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?, )); } diff --git a/lib/data/services/model/meta/meta.g.dart b/lib/data/services/model/meta/meta.g.dart index 6e38bc5..2610829 100644 --- a/lib/data/services/model/meta/meta.g.dart +++ b/lib/data/services/model/meta/meta.g.dart @@ -12,6 +12,9 @@ _Meta _$MetaFromJson(Map 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 _$MetaToJson(_Meta instance) => { @@ -20,4 +23,7 @@ Map _$MetaToJson(_Meta instance) => { 'page': instance.page, 'pages': instance.pages, 'total': instance.total, + 'has_more': instance.hasMore, + 'next_cursor': instance.nextCursor, + 'prev_cursor': instance.prevCursor, }; diff --git a/lib/data/services/tenders_service.dart b/lib/data/services/tenders_service.dart index 703813b..519a35b 100644 --- a/lib/data/services/tenders_service.dart +++ b/lib/data/services/tenders_service.dart @@ -16,7 +16,8 @@ class TendersService { Future> 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; } diff --git a/lib/main.dart b/lib/main.dart index 551e1e1..29c9d4b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'dart:ui'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -130,6 +131,9 @@ class _MyAppState extends State { Widget build(BuildContext context) { // Listen to theme changes final themeProvider = context.watch(); + final pageTransition = kIsWeb + ? const FadeUpwardsPageTransitionsBuilder() + : const CupertinoPageTransitionsBuilder(); return ToastificationWrapper( child: MaterialApp.router( @@ -147,10 +151,7 @@ class _MyAppState extends State { pageTransitionsTheme: PageTransitionsTheme( builders: { for (final platform in TargetPlatform.values) - platform: - kIsWeb - ? const FadeUpwardsPageTransitionsBuilder() - : const CupertinoPageTransitionsBuilder(), + platform: pageTransition, }, ), brightness: diff --git a/lib/view_models/tenders_view_model.dart b/lib/view_models/tenders_view_model.dart index baefb2c..47ff830 100644 --- a/lib/view_models/tenders_view_model.dart +++ b/lib/view_models/tenders_view_model.dart @@ -5,6 +5,7 @@ import 'package:tm_app/core/utils/logger.dart'; import 'package:tm_app/data/repositories/tenders_repository.dart'; import 'package:tm_app/data/services/model/feedback_data/feedback_data.dart'; import 'package:tm_app/data/services/model/feedback_response/feedback_response.dart'; +import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; import 'package:tm_app/data/services/model/tenders/tenders_response/tenders_response.dart'; import 'package:tm_app/views/tenders/strings/tenders_strings.dart'; @@ -36,15 +37,44 @@ class TendersViewModel with ChangeNotifier { List feedbacks = []; - int currentPage = 1; - int _currentOffset = 0; - int _totalPages = 0; - int get totalPages => _totalPages; static const int _pageSize = 10; - bool _hasMoreData = true; + // --- Offset pagination state --- + int _currentOffset = 0; + int _lastValidPages = 1; + int _lastValidLimit = _pageSize; + + bool _hasMoreData = true; bool get hasMoreData => _hasMoreData; + /// 0-based current page index. + int get currentPageIndex { + final meta = _tendersResponse?.meta; + final limit = meta?.limit ?? _lastValidLimit; + + if (limit > 0) { + return _currentOffset ~/ limit; + } + if (meta?.offset != null && limit > 0) { + return meta!.offset! ~/ limit; + } + if (meta?.page != null) { + return meta!.page! - 1; + } + return 0; + } + + /// 1-based current page (for display). + int get currentPage => currentPageIndex + 1; + + int get totalPages { + final pages = _tendersResponse?.meta?.pages; + if (pages != null && pages > 0) { + return pages; + } + return _lastValidPages; + } + // Filter States DateTimeRange? selectedDateRange; String selectedStatus = 'All'; @@ -80,15 +110,12 @@ class TendersViewModel with ChangeNotifier { } void setStatus(String status) { - // Clear data when status changes _clearAllData(); selectedStatus = status; - currentPage = 1; notifyListeners(); } void setSort(String sort) { - // Clear data when status changes _clearAllData(); selectedSort = sort; if (sort == TendersStrings.minimumTime) { @@ -98,16 +125,13 @@ class TendersViewModel with ChangeNotifier { sortBy = 'submission_deadline'; sortOrder = 'desc'; } - currentPage = 1; notifyListeners(); } void _clearAllData() { _tendersResponse = null; _errorMessage = null; - _currentOffset = 0; - _hasMoreData = true; - currentPage = 1; + _resetPagination(); sortBy = ''; sortOrder = ''; selectedSort = ''; @@ -116,6 +140,11 @@ class TendersViewModel with ChangeNotifier { notifyListeners(); } + void _resetPagination() { + _currentOffset = 0; + _hasMoreData = true; + } + void init(BuildContext context) async { _isLoading = true; notifyListeners(); @@ -151,10 +180,7 @@ class TendersViewModel with ChangeNotifier { } Future getTendersMobile({String? query}) async { - // Clear all data before fetching new tenders - - _currentOffset = 0; - _hasMoreData = true; + _resetPagination(); _searchQuery = query; final result = await _tendersRepository.getTenders( @@ -164,28 +190,7 @@ class TendersViewModel with ChangeNotifier { sortBy: '', sortOrder: '', ); - switch (result) { - case Ok(): - _tendersResponse = result.value; - // Check if we have more data - if (_tendersResponse?.data?.tenders != null) { - _hasMoreData = _tendersResponse!.data!.tenders!.length >= _pageSize; - } - if (_tendersResponse?.data?.tenders?.isNotEmpty == true) { - // Fetch feedback and approvals for the first tender only - getTenderFeedback(_tendersResponse!.data!.tenders!.first.id!); - } - if (_tendersResponse?.meta?.pages != null) { - _totalPages = _tendersResponse!.meta!.pages!; - } else { - _totalPages = 1; - } - notifyListeners(); - break; - case Error(): - _errorMessage = result.error.toString(); - break; - } + _handleListResult(result, fetchAllFeedbacks: false); _isLoading = false; notifyListeners(); _errorMessage = null; @@ -199,11 +204,9 @@ class TendersViewModel with ChangeNotifier { String? publicationDateFrom, String? publicationDateTo, }) async { - // Clear all data before fetching new tenders _isLoading = true; notifyListeners(); - _currentOffset = 0; - _hasMoreData = true; + _resetPagination(); _searchQuery = query; final result = await _tendersRepository.getTenders( @@ -215,35 +218,55 @@ class TendersViewModel with ChangeNotifier { publicationDateFrom: publicationDateFrom ?? '', publicationDateTo: publicationDateTo ?? '', ); - switch (result) { - case Ok(): - _tendersResponse = result.value; - // Check if we have more data - if (_tendersResponse?.data?.tenders != null) { - _hasMoreData = _tendersResponse!.data!.tenders!.length >= _pageSize; - } - if (_tendersResponse?.data?.tenders?.isNotEmpty == true) { - for (var tender in _tendersResponse!.data!.tenders!) { - getTenderFeedback(tender.id!); - } - } - if (_tendersResponse?.meta?.pages != null) { - _totalPages = _tendersResponse!.meta!.pages!; - } else { - _totalPages = 1; - } - notifyListeners(); - break; - case Error(): - _errorMessage = result.error.toString(); - break; - } + _handleListResult(result, fetchAllFeedbacks: true); _isLoading = false; notifyListeners(); _errorMessage = null; notifyListeners(); } + void _handleListResult( + Result result, { + required bool fetchAllFeedbacks, + }) { + switch (result) { + case Ok(): + _tendersResponse = result.value; + final meta = _tendersResponse?.meta; + if (meta?.pages != null && meta!.pages! > 0) { + _lastValidPages = meta.pages!; + } + if (meta?.limit != null && meta!.limit! > 0) { + _lastValidLimit = meta.limit!; + } + if (meta?.offset != null) { + _currentOffset = meta!.offset!; + } + final tenders = _tendersResponse?.data?.tenders ?? []; + _hasMoreData = + meta?.hasMore ?? (tenders.length >= _pageSize); + if (tenders.isNotEmpty) { + if (fetchAllFeedbacks) { + for (final tender in tenders) { + if (tender.id != null) { + getTenderFeedback(tender.id!); + } + } + } else { + getTenderFeedback(tenders.first.id!); + } + } + break; + case Error(): + _handleListError(result.error); + break; + } + } + + void _handleListError(Exception error) { + _errorMessage = error.toString(); + } + Future loadMoreTenders() async { if (_isLoadingMore || !_hasMoreData) { return; @@ -253,7 +276,10 @@ class TendersViewModel with ChangeNotifier { _errorMessage = null; notifyListeners(); - _currentOffset += _pageSize; + final meta = _tendersResponse?.meta; + final limit = meta?.limit ?? _pageSize; + _currentOffset += limit; + final result = await _tendersRepository.getTenders( limit: _pageSize, offset: _currentOffset, @@ -264,30 +290,31 @@ class TendersViewModel with ChangeNotifier { switch (result) { case Ok(): - if (result.value.data?.tenders != null && - result.value.data!.tenders!.isNotEmpty) { - // Create a new modifiable list by combining existing and new tenders - if (_tendersResponse?.data?.tenders != null) { - final existingTenders = _tendersResponse!.data!.tenders!; - final newTenders = result.value.data!.tenders!; - final combinedTenders = [...existingTenders, ...newTenders]; - - // Create a new TendersData with the combined list - _tendersResponse = _tendersResponse!.copyWith( - data: _tendersResponse!.data!.copyWith(tenders: combinedTenders), - ); - } else { - _tendersResponse = result.value; + final newTenders = result.value.data?.tenders ?? []; + if (newTenders.isNotEmpty) { + final existingTenders = + _tendersResponse?.data?.tenders ?? []; + final combined = [...existingTenders, ...newTenders]; + _tendersResponse = result.value.copyWith( + data: result.value.data?.copyWith(tenders: combined), + ); + final newMeta = result.value.meta; + if (newMeta?.pages != null && newMeta!.pages! > 0) { + _lastValidPages = newMeta.pages!; } - // Check if we have more data - _hasMoreData = result.value.data!.tenders!.length >= _pageSize; + if (newMeta?.limit != null && newMeta!.limit! > 0) { + _lastValidLimit = newMeta.limit!; + } + _hasMoreData = + newMeta?.hasMore ?? (newTenders.length >= _pageSize); } else { _hasMoreData = false; } break; case Error(): - _errorMessage = result.error.toString(); - _currentOffset -= _pageSize; // Revert offset on error + final limit = meta?.limit ?? _pageSize; + _currentOffset = (_currentOffset - limit).clamp(0, 1 << 30); + _handleListError(result.error); break; } @@ -298,7 +325,6 @@ class TendersViewModel with ChangeNotifier { } Future getTenderFeedback(String tenderId) async { - // _isLoading = true; _errorMessage = null; notifyListeners(); @@ -308,7 +334,6 @@ class TendersViewModel with ChangeNotifier { switch (result) { case Ok(): _feedbackData = result.value.data; - // Add or update _feedbackData in feedbacks list by tender id if (_feedbackData != null) { final index = feedbacks.indexWhere( (f) => f.tenderId == _feedbackData!.tenderId, @@ -331,7 +356,6 @@ class TendersViewModel with ChangeNotifier { _errorMessage = result.error.toString(); break; } - // _isLoading = false; notifyListeners(); _errorMessage = null; notifyListeners(); @@ -343,38 +367,32 @@ class TendersViewModel with ChangeNotifier { }) async { _feedbackErrorMessage = null; - // Get current feedback for this tender final existingFeedback = getFeedbackForTender(tenderId); final existingFeedbackIndex = feedbacks.indexWhere( (f) => f.tenderId == tenderId, ); - // Create optimistic feedback data FeedbackData? optimisticFeedback; if (existingFeedback == null) { - // No existing feedback - create new optimistic feedback optimisticFeedback = FeedbackData( - companyId: null, // Will be filled by server + companyId: null, createdAt: DateTime.now().millisecondsSinceEpoch, - customerId: null, // Will be filled by server + customerId: null, feedbackType: feedbackType, - id: null, // Will be filled by server + id: null, tenderId: tenderId, tender: null, updatedAt: DateTime.now().millisecondsSinceEpoch, ); feedbacks.add(optimisticFeedback); } else { - // Existing feedback - update optimistically if (existingFeedback.feedbackType == feedbackType) { - // Same feedback type - remove feedback (toggle off) if (existingFeedbackIndex != -1) { feedbacks.removeAt(existingFeedbackIndex); } optimisticFeedback = null; } else { - // Different feedback type - update to new type optimisticFeedback = existingFeedback.copyWith( feedbackType: feedbackType, updatedAt: DateTime.now().millisecondsSinceEpoch, @@ -385,10 +403,8 @@ class TendersViewModel with ChangeNotifier { } } - // Notify listeners immediately for optimistic update notifyListeners(); - // Call API final result = await _tendersRepository.toggleFeedback( tenderId: tenderId, feedbackType: feedbackType, @@ -398,7 +414,6 @@ class TendersViewModel with ChangeNotifier { case Ok(): _feedbackData = result.value.data; if (_feedbackData != null) { - // Update with complete data from server final index = feedbacks.indexWhere( (f) => f.tender == _feedbackData!.tender, ); @@ -408,17 +423,13 @@ class TendersViewModel with ChangeNotifier { feedbacks.add(_feedbackData!); } } else { - // Server returned null data - remove feedback feedbacks.removeWhere((f) => f.tenderId == tenderId); } break; case Error(): - // Revert optimistic update on error if (existingFeedback == null) { - // Remove the optimistic feedback we added feedbacks.removeWhere((f) => f.tenderId == tenderId); } else { - // Restore the original feedback - find current index and update, or add if not found final currentIndex = feedbacks.indexWhere( (f) => f.tenderId == tenderId, ); @@ -437,7 +448,6 @@ class TendersViewModel with ChangeNotifier { notifyListeners(); } - /// Clear all feedbacks void clearFeedbacks() { feedbacks.clear(); _feedbackData = null; @@ -445,13 +455,10 @@ class TendersViewModel with ChangeNotifier { notifyListeners(); } - /// Clear all tenders void clearTenders() { _tendersResponse = null; _errorMessage = null; - _currentOffset = 0; - _hasMoreData = true; - currentPage = 1; + _resetPagination(); notifyListeners(); } @@ -464,7 +471,6 @@ class TendersViewModel with ChangeNotifier { ); } - /// Search tenders with query Future searchTenders(String query) async { await getTendersDesktop( query: query.trim(), @@ -475,7 +481,6 @@ class TendersViewModel with ChangeNotifier { ); } - /// Clear all data (tenders, feedbacks, and approvals) void clearAll() { clearTenders(); clearFeedbacks(); @@ -488,17 +493,32 @@ class TendersViewModel with ChangeNotifier { notifyListeners(); } - void jumpToPage(int page) async { - final meta = tendersResponse?.meta; - if (meta?.pages == null) { + /// [selected] is the absolute 0-based target page index. + Future handlePaginationChange(int selected) async { + if (selected < 0) { return; } - if (page < 1 || page > meta!.pages!) { + final maxPage = totalPages - 1; + if (maxPage >= 0 && selected > maxPage) { return; } - currentPage = page; - _currentOffset = (page - 1) * _pageSize; + if (selected == currentPageIndex) { + return; + } + + await _goToOffsetPage(selected); + } + + Future _goToOffsetPage(int pageIndex) async { + final limit = _tendersResponse?.meta?.limit ?? _pageSize; + _currentOffset = pageIndex * limit; + await _fetchPage(); + } + + Future _fetchPage() async { + _isLoading = true; + notifyListeners(); final result = await _tendersRepository.getTenders( limit: _pageSize, @@ -506,17 +526,20 @@ class TendersViewModel with ChangeNotifier { query: _searchQuery, sortBy: sortBy, sortOrder: sortOrder, + publicationDateFrom: startDateUnix, + publicationDateTo: endDateUnix, ); - switch (result) { - case Ok(): - _tendersResponse = result.value; - break; - case Error(): - _errorMessage = result.error.toString(); - break; - } - + _handleListResult(result, fetchAllFeedbacks: true); + _isLoading = false; + notifyListeners(); + _errorMessage = null; notifyListeners(); } + + /// Kept for backwards compatibility with existing callers. + /// Accepts a 1-based page number. + void jumpToPage(int page) { + handlePaginationChange(page - 1); + } } diff --git a/lib/views/home/pages/d_home_page.dart b/lib/views/home/pages/d_home_page.dart index e4a552c..6d6a967 100644 --- a/lib/views/home/pages/d_home_page.dart +++ b/lib/views/home/pages/d_home_page.dart @@ -182,7 +182,7 @@ class DesktopHomePage extends StatelessWidget { title: HomeStrings.likedTenders, amount: homeViewModel.userLikedTendersCount.toString(), textColor: AppColors.grey50, - enableTap: homeViewModel.userLikedTendersCount != 0, + enableTap: true, width: 178, height: 148, onTap: () { diff --git a/lib/views/home/pages/m_home_page.dart b/lib/views/home/pages/m_home_page.dart index 6d7a17e..82563f8 100644 --- a/lib/views/home/pages/m_home_page.dart +++ b/lib/views/home/pages/m_home_page.dart @@ -157,7 +157,7 @@ class MobileHomePage extends StatelessWidget { title: HomeStrings.likedTenders, amount: homeViewModel.userLikedTendersCount.toString(), textColor: AppColors.grey50, - enableTap: homeViewModel.userLikedTendersCount != 0, + enableTap: true, onTap: () { const LikedTendersRouteData().push(context).then((value) { homeViewModel.init(); diff --git a/lib/views/home/pages/t_home_page.dart b/lib/views/home/pages/t_home_page.dart index 0919a13..feb700f 100644 --- a/lib/views/home/pages/t_home_page.dart +++ b/lib/views/home/pages/t_home_page.dart @@ -187,7 +187,7 @@ class TabletHomePage extends StatelessWidget { title: HomeStrings.likedTenders, amount: homeViewModel.userLikedTendersCount.toString(), textColor: AppColors.grey50, - enableTap: homeViewModel.userLikedTendersCount != 0, + enableTap: true, width: double.infinity, height: 148, onTap: () { diff --git a/lib/views/notification/pages/d_notification_page.dart b/lib/views/notification/pages/d_notification_page.dart index 17c180e..d3e3a96 100644 --- a/lib/views/notification/pages/d_notification_page.dart +++ b/lib/views/notification/pages/d_notification_page.dart @@ -87,29 +87,42 @@ class _DesktopNotificationPageState extends State padding: EdgeInsetsDirectional.only(end: 24.0.w()), child: Align( alignment: Alignment.centerRight, - child: InkWell( - onTap: () { - viewModel.markAllAsRead(context); - }, - borderRadius: BorderRadius.circular(99), - child: Container( - width: 132.0.w(), - height: 32.0.h(), - decoration: BoxDecoration( - color: AppColors.primary20, + child: Builder( + builder: (context) { + final hasNotifications = (viewModel + .allNotificationResponse + ?.data + ?.isNotEmpty ?? + false); + return InkWell( + onTap: hasNotifications + ? () => viewModel.markAllAsRead(context) + : null, borderRadius: BorderRadius.circular(99), - ), - child: Center( - child: Text( - NotificationStrings.markAllAsReadButton, - style: TextStyle( - fontSize: 14.0.sp(), - fontWeight: FontWeight.w500, - color: AppColors.mainBlue, + child: Container( + width: 132.0.w(), + height: 32.0.h(), + decoration: BoxDecoration( + color: hasNotifications + ? AppColors.primary20 + : AppColors.grey20, + borderRadius: BorderRadius.circular(99), + ), + child: Center( + child: Text( + NotificationStrings.markAllAsReadButton, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w500, + color: hasNotifications + ? AppColors.mainBlue + : AppColors.grey50, + ), + ), ), ), - ), - ), + ); + }, ), ), ), diff --git a/lib/views/notification/pages/m_notification_page.dart b/lib/views/notification/pages/m_notification_page.dart index 7108af5..86c1f5e 100644 --- a/lib/views/notification/pages/m_notification_page.dart +++ b/lib/views/notification/pages/m_notification_page.dart @@ -101,29 +101,40 @@ class _MobileNotificationPageState extends State padding: EdgeInsetsDirectional.only(end: 24.0.w()), child: Align( alignment: Alignment.centerRight, - child: InkWell( - onTap: () { - viewModel.markAllAsRead(context); - }, - borderRadius: BorderRadius.circular(99), - child: Container( - width: 132.0.w(), - height: 32.0.h(), - decoration: BoxDecoration( - color: AppColors.primary20, + child: Builder( + builder: (context) { + final hasNotifications = + (viewModel.allNotificationResponse?.data?.isNotEmpty ?? + false); + return InkWell( + onTap: hasNotifications + ? () => viewModel.markAllAsRead(context) + : null, borderRadius: BorderRadius.circular(99), - ), - child: Center( - child: Text( - NotificationStrings.markAllAsReadButton, - style: TextStyle( - fontSize: 14.0.sp(), - fontWeight: FontWeight.w500, - color: AppColors.mainBlue, + child: Container( + width: 132.0.w(), + height: 32.0.h(), + decoration: BoxDecoration( + color: hasNotifications + ? AppColors.primary20 + : AppColors.grey20, + borderRadius: BorderRadius.circular(99), + ), + child: Center( + child: Text( + NotificationStrings.markAllAsReadButton, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w500, + color: hasNotifications + ? AppColors.mainBlue + : AppColors.grey50, + ), + ), ), ), - ), - ), + ); + }, ), ), ), diff --git a/lib/views/notification/pages/t_notification_page.dart b/lib/views/notification/pages/t_notification_page.dart index b96ff97..ca4653b 100644 --- a/lib/views/notification/pages/t_notification_page.dart +++ b/lib/views/notification/pages/t_notification_page.dart @@ -90,29 +90,42 @@ class _TabletNotificationPageState extends State padding: EdgeInsetsDirectional.only(end: 24.0.w()), child: Align( alignment: Alignment.centerRight, - child: InkWell( - onTap: () { - viewModel.markAllAsRead(context); - }, - borderRadius: BorderRadius.circular(99), - child: Container( - width: 132.0.w(), - height: 32.0.h(), - decoration: BoxDecoration( - color: AppColors.primary20, + child: Builder( + builder: (context) { + final hasNotifications = (viewModel + .allNotificationResponse + ?.data + ?.isNotEmpty ?? + false); + return InkWell( + onTap: hasNotifications + ? () => viewModel.markAllAsRead(context) + : null, borderRadius: BorderRadius.circular(99), - ), - child: Center( - child: Text( - NotificationStrings.markAllAsReadButton, - style: TextStyle( - fontSize: 14.0.sp(), - fontWeight: FontWeight.w500, - color: AppColors.mainBlue, + child: Container( + width: 132.0.w(), + height: 32.0.h(), + decoration: BoxDecoration( + color: hasNotifications + ? AppColors.primary20 + : AppColors.grey20, + borderRadius: BorderRadius.circular(99), + ), + child: Center( + child: Text( + NotificationStrings.markAllAsReadButton, + style: TextStyle( + fontSize: 14.0.sp(), + fontWeight: FontWeight.w500, + color: hasNotifications + ? AppColors.mainBlue + : AppColors.grey50, + ), + ), ), ), - ), - ), + ); + }, ), ), ), diff --git a/lib/views/tenders/pages/d_tenders_page.dart b/lib/views/tenders/pages/d_tenders_page.dart index 7a7bb85..cdfce5b 100644 --- a/lib/views/tenders/pages/d_tenders_page.dart +++ b/lib/views/tenders/pages/d_tenders_page.dart @@ -9,11 +9,11 @@ import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/view_models/tenders_view_model.dart'; import 'package:tm_app/views/shared/base_button.dart'; import 'package:tm_app/views/shared/desktop_navigation_widget.dart'; -import 'package:tm_app/views/shared/page_selection_dialog.dart'; import 'package:tm_app/views/tenders/strings/tenders_strings.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; import 'package:tm_app/views/tenders/widgets/tenders_filter_dialog.dart'; import 'package:tm_app/views/tenders/widgets/tenders_sort_dialog.dart'; +import 'package:tm_app/views/tenders/widgets/windowed_pagination.dart'; import '../../../core/constants/common_strings.dart'; import '../../../core/utils/app_toast.dart'; @@ -95,10 +95,13 @@ class _DesktopTendersPageState extends State { ), ), ), - _DesktopPagination( - totalPages: vm.totalPages, - currentPage: vm.currentPage, - onPageSelected: vm.jumpToPage, + SizedBox( + width: 740, + child: WindowedPagination( + currentPage: vm.currentPageIndex, + totalPages: vm.totalPages, + onPageChanged: vm.handlePaginationChange, + ), ), ], ); @@ -264,93 +267,3 @@ class _ActionButtons extends StatelessWidget { } } -class _DesktopPagination extends StatelessWidget { - final int totalPages; - final int currentPage; - final ValueChanged onPageSelected; - - const _DesktopPagination({ - required this.totalPages, - required this.currentPage, - required this.onPageSelected, - }); - - @override - Widget build(BuildContext context) { - return SizedBox( - width: 680, - child: Padding( - padding: EdgeInsets.symmetric(vertical: 12.0.h()), - child: Row( - children: [ - const Spacer(), - Text( - TendersStrings.page, - style: TextStyle( - fontSize: 13.0.sp(), - fontWeight: FontWeight.w300, - color: AppColors.grey60, - ), - ), - SizedBox(width: 10.0.w()), - InkWell( - onTap: () async { - final selectedPage = await showDialog( - context: context, - builder: - (context) => PageSelectionDialog( - totalPages: totalPages, - currentPage: currentPage, - ), - ); - if (selectedPage != null) { - onPageSelected(selectedPage); - } - }, - child: Container( - decoration: BoxDecoration( - border: Border.all(color: AppColors.grey30, width: 1), - borderRadius: BorderRadius.circular(4), - ), - child: Row( - children: [ - SizedBox(width: 5.0.w()), - Text( - '$currentPage', - style: TextStyle( - fontSize: 14.0.sp(), - fontWeight: FontWeight.w500, - color: AppColors.grey70, - ), - ), - SizedBox(width: 5.0.w()), - Icon(Icons.arrow_drop_down, color: AppColors.grey80), - SizedBox(width: 5.0.w()), - ], - ), - ), - ), - SizedBox(width: 5.0.w()), - Text( - TendersStrings.of, - style: TextStyle( - fontSize: 14.0.sp(), - fontWeight: FontWeight.w400, - color: AppColors.grey60, - ), - ), - SizedBox(width: 5.0.w()), - Text( - '$totalPages', - style: TextStyle( - fontSize: 13.0.sp(), - fontWeight: FontWeight.w400, - color: AppColors.grey60, - ), - ), - ], - ), - ), - ); - } -} diff --git a/lib/views/tenders/pages/m_tenders_page.dart b/lib/views/tenders/pages/m_tenders_page.dart index 5325e16..f29d6a1 100644 --- a/lib/views/tenders/pages/m_tenders_page.dart +++ b/lib/views/tenders/pages/m_tenders_page.dart @@ -9,6 +9,7 @@ import '../../../core/utils/app_toast.dart'; import '../../shared/tender_app_bar.dart'; import '../strings/tenders_strings.dart'; import '../widgets/main_tenders_slider.dart'; +import '../widgets/windowed_pagination.dart'; class MobileTendersPage extends StatefulWidget { const MobileTendersPage({super.key}); @@ -61,13 +62,29 @@ class _MobileTendersPageState extends State { return const Center(child: Text(CommonStrings.noData)); } - return SingleChildScrollView( - child: Padding( - padding: EdgeInsets.symmetric(vertical: 18.0.h()), - child: MainTendersSlider( - tenders: viewModel.tendersResponse?.data?.tenders ?? [], + return Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 18.0.h()), + child: MainTendersSlider( + key: ValueKey( + 'tenders-slider-${viewModel.currentPageIndex}', + ), + tenders: + viewModel.tendersResponse?.data?.tenders ?? [], + ), + ), + ), ), - ), + WindowedPagination( + currentPage: viewModel.currentPageIndex, + totalPages: viewModel.totalPages, + onPageChanged: viewModel.handlePaginationChange, + compact: true, + ), + ], ); }, ), diff --git a/lib/views/tenders/pages/t_tenders_page.dart b/lib/views/tenders/pages/t_tenders_page.dart index e3b38de..4129f0a 100644 --- a/lib/views/tenders/pages/t_tenders_page.dart +++ b/lib/views/tenders/pages/t_tenders_page.dart @@ -5,6 +5,7 @@ import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/view_models/tenders_view_model.dart'; import 'package:tm_app/views/shared/tablet_navigation_widget.dart'; import 'package:tm_app/views/tenders/widgets/main_tenders_slider.dart'; +import 'package:tm_app/views/tenders/widgets/windowed_pagination.dart'; import '../../../core/constants/common_strings.dart'; import '../../shared/tender_app_bar.dart'; @@ -53,16 +54,30 @@ class _TabletTendersPageState extends State { viewModel.isLoading == false) { return const Center(child: Text(CommonStrings.noData)); } - return SingleChildScrollView( - child: Padding( - padding: EdgeInsetsDirectional.only( - end: 24.0.w(), - top: 128.0.h(), + return Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsetsDirectional.only( + end: 24.0.w(), + top: 128.0.h(), + ), + child: MainTendersSlider( + key: ValueKey( + 'tenders-slider-${viewModel.currentPageIndex}', + ), + tenders: viewModel.tendersResponse?.data?.tenders ?? [], + ), + ), + ), ), - child: MainTendersSlider( - tenders: viewModel.tendersResponse?.data?.tenders ?? [], + WindowedPagination( + currentPage: viewModel.currentPageIndex, + totalPages: viewModel.totalPages, + onPageChanged: viewModel.handlePaginationChange, ), - ), + ], ); }, ), diff --git a/lib/views/tenders/widgets/main_tenders_slider.dart b/lib/views/tenders/widgets/main_tenders_slider.dart index b0c2b70..66cde15 100644 --- a/lib/views/tenders/widgets/main_tenders_slider.dart +++ b/lib/views/tenders/widgets/main_tenders_slider.dart @@ -27,31 +27,6 @@ class _MainTendersSliderState extends State { final PageController pageController = PageController(initialPage: 0); int currentPage = 1; - ScrollController? _scrollController; - - @override - void initState() { - super.initState(); - - if (widget.isDesktop) { - _scrollController = ScrollController(); - _scrollController!.addListener(_onScroll); - } - } - - void _onScroll() { - if (!widget.isDesktop) { - return; - } - final viewModel = context.read(); - if (_scrollController!.position.pixels >= - _scrollController!.position.maxScrollExtent - 200 && - viewModel.hasMoreData && - !viewModel.isLoadingMore) { - viewModel.loadMoreTenders(); - } - } - void _goToPreviousPage() { if (currentPage == 1) { return; @@ -107,7 +82,6 @@ class _MainTendersSliderState extends State { @override void dispose() { pageController.dispose(); - _scrollController?.dispose(); super.dispose(); } @@ -141,12 +115,6 @@ class _MainTendersSliderState extends State { if (!viewModel.hasFeedbackForTender(id)) { viewModel.getTenderFeedback(id); } - - if (index >= widget.tenders.length - 1 && - viewModel.hasMoreData && - !viewModel.isLoadingMore) { - viewModel.loadMoreTenders(); - } }, itemBuilder: (context, index) { final tender = widget.tenders[index]; diff --git a/lib/views/tenders/widgets/windowed_pagination.dart b/lib/views/tenders/widgets/windowed_pagination.dart new file mode 100644 index 0000000..3514267 --- /dev/null +++ b/lib/views/tenders/widgets/windowed_pagination.dart @@ -0,0 +1,250 @@ +import 'package:flutter/material.dart'; +import 'package:tm_app/core/theme/colors.dart'; + +/// A windowed numbered pagination widget that mirrors the Next.js +/// implementation: ±10 page-button window around the current page, +/// 1-based labels, prev/next chevrons. +class WindowedPagination extends StatelessWidget { + const WindowedPagination({ + super.key, + required this.currentPage, + required this.totalPages, + required this.onPageChanged, + this.maxPageJump = 10, + this.compact = false, + }); + + /// 0-based absolute current page index. + final int currentPage; + + /// Total number of pages (1-based count). + final int totalPages; + + /// Called with the 0-based target page index. + final ValueChanged onPageChanged; + + /// Half-width of the page-button window (web uses 10). + final int maxPageJump; + + /// If true, renders smaller buttons (for mobile). + final bool compact; + + @override + Widget build(BuildContext context) { + if (totalPages <= 1) { + return const SizedBox.shrink(); + } + + final clampedCurrent = currentPage.clamp(0, totalPages - 1); + final windowStart = (clampedCurrent - maxPageJump).clamp(0, totalPages - 1); + final windowEnd = (clampedCurrent + maxPageJump).clamp(0, totalPages - 1); + + final buttons = []; + + buttons.add( + _NavButton( + icon: Icons.chevron_left, + enabled: clampedCurrent > 0, + compact: compact, + onTap: () => onPageChanged(clampedCurrent - 1), + ), + ); + + if (windowStart > 0) { + buttons.add(_buildPageButton(0, clampedCurrent)); + if (windowStart > 1) { + buttons.add(const _Ellipsis()); + } + } + + for (var i = windowStart; i <= windowEnd; i++) { + buttons.add(_buildPageButton(i, clampedCurrent)); + } + + if (windowEnd < totalPages - 1) { + if (windowEnd < totalPages - 2) { + buttons.add(const _Ellipsis()); + } + buttons.add(_buildPageButton(totalPages - 1, clampedCurrent)); + } + + buttons.add( + _NavButton( + icon: Icons.chevron_right, + enabled: clampedCurrent < totalPages - 1, + compact: compact, + onTap: () => onPageChanged(clampedCurrent + 1), + ), + ); + + final gap = compact ? 4.0 : 6.0; + return Padding( + padding: EdgeInsets.symmetric(vertical: compact ? 8 : 12), + child: LayoutBuilder( + builder: (context, constraints) { + return SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: constraints.maxWidth), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: _withSpacing(buttons, gap), + ), + ), + ); + }, + ), + ); + } + + Widget _buildPageButton(int pageIndex, int currentPageIndex) { + final isActive = pageIndex == currentPageIndex; + return _PageButton( + label: '${pageIndex + 1}', + isActive: isActive, + compact: compact, + onTap: isActive ? null : () => onPageChanged(pageIndex), + ); + } + + static List _withSpacing(List items, double gap) { + final result = []; + for (var i = 0; i < items.length; i++) { + result.add(items[i]); + if (i != items.length - 1) { + result.add(SizedBox(width: gap)); + } + } + return result; + } +} + +class _PageButton extends StatelessWidget { + const _PageButton({ + required this.label, + required this.isActive, + required this.compact, + this.onTap, + }); + + final String label; + final bool isActive; + final bool compact; + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + final size = compact ? 32.0 : 38.0; + return SizedBox( + width: size, + height: size, + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: onTap, + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + gradient: isActive + ? const LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: [AppColors.mainBlue, AppColors.primaryColor], + ) + : null, + color: isActive ? null : AppColors.grey0, + border: Border.all( + color: isActive ? Colors.transparent : AppColors.grey30, + width: 1, + ), + boxShadow: isActive + ? [ + BoxShadow( + color: AppColors.mainBlue.withValues(alpha: 0.25), + blurRadius: 8, + offset: const Offset(0, 2), + ), + ] + : null, + ), + child: Text( + label, + style: TextStyle( + fontSize: compact ? 12 : 13, + fontWeight: isActive ? FontWeight.w600 : FontWeight.w500, + color: isActive ? Colors.white : AppColors.grey70, + ), + ), + ), + ), + ), + ); + } +} + +class _NavButton extends StatelessWidget { + const _NavButton({ + required this.icon, + required this.enabled, + required this.compact, + required this.onTap, + }); + + final IconData icon; + final bool enabled; + final bool compact; + final VoidCallback onTap; + + @override + Widget build(BuildContext context) { + final size = compact ? 32.0 : 38.0; + return SizedBox( + width: size, + height: size, + child: Material( + color: Colors.transparent, + child: InkWell( + borderRadius: BorderRadius.circular(12), + onTap: enabled ? onTap : null, + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: AppColors.grey0, + border: Border.all( + color: enabled ? AppColors.grey30 : AppColors.grey20, + width: 1, + ), + ), + child: Icon( + icon, + size: compact ? 18 : 20, + color: enabled ? AppColors.grey70 : AppColors.grey40, + ), + ), + ), + ), + ); + } +} + +class _Ellipsis extends StatelessWidget { + const _Ellipsis(); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 2), + child: Text( + '…', + style: TextStyle( + fontSize: 14, + color: AppColors.grey60, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 386acf0..97cd259 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -6,143 +6,143 @@ packages: description: name: _fe_analyzer_shared sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "85.0.0" _flutterfire_internals: dependency: transitive description: name: _flutterfire_internals - sha256: "948f7d74f41dd6f2d563ea9f4c21d7ea764f8e047d2b24138974c19c24d37eb6" - url: "https://pub.dev" + sha256: "8f89e371e2883de35cdc78f648e725fa4da5f3b6c927269f00fa68f1ea92b598" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.61" + version: "1.3.71" analyzer: dependency: transitive description: name: analyzer sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.7.1" archive: dependency: transitive description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" - url: "https://pub.dev" + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff + url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.7" + version: "4.0.9" args: dependency: transitive description: name: args sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.7.0" async: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 - url: "https://pub.dev" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 + url: "https://pub.flutter-io.cn" source: hosted - version: "2.12.0" + version: "2.13.1" boolean_selector: dependency: transitive description: name: boolean_selector sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" build: dependency: transitive description: name: build - sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7" - url: "https://pub.dev" + sha256: ce76b1d48875e3233fde17717c23d1f60a91cc631597e49a400c89b475395b1d + url: "https://pub.flutter-io.cn" source: hosted - version: "2.5.4" + version: "3.1.0" build_config: dependency: transitive description: name: build_config - sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33" - url: "https://pub.dev" + sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.2" + version: "1.2.0" build_daemon: dependency: transitive description: name: build_daemon - sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" - url: "https://pub.dev" + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.4" + version: "4.1.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62 - url: "https://pub.dev" + sha256: d1d57f7807debd7349b4726a19fd32ec8bc177c71ad0febf91a20f84cd2d4b46 + url: "https://pub.flutter-io.cn" source: hosted - version: "2.5.4" + version: "3.0.3" build_runner: dependency: "direct dev" description: name: build_runner - sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53" - url: "https://pub.dev" + sha256: b24597fceb695969d47025c958f3837f9f0122e237c6a22cb082a5ac66c3ca30 + url: "https://pub.flutter-io.cn" source: hosted - version: "2.5.4" + version: "2.7.1" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792" - url: "https://pub.dev" + sha256: "066dda7f73d8eb48ba630a55acb50c4a84a2e6b453b1cb4567f581729e794f7b" + url: "https://pub.flutter-io.cn" source: hosted - version: "9.1.2" + version: "9.3.1" built_collection: dependency: transitive description: name: built_collection sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" built_value: dependency: transitive description: name: built_value - sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d - url: "https://pub.dev" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + url: "https://pub.flutter-io.cn" source: hosted - version: "8.12.0" + version: "8.12.6" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 - url: "https://pub.dev" + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b + url: "https://pub.flutter-io.cn" source: hosted - version: "1.4.0" + version: "1.4.1" checked_yaml: dependency: transitive description: name: checked_yaml - sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff - url: "https://pub.dev" + sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.3" + version: "2.0.4" cli_util: dependency: transitive description: name: cli_util sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.4.2" clock: @@ -150,23 +150,31 @@ packages: description: name: clock sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" code_builder: dependency: transitive description: name: code_builder - sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" - url: "https://pub.dev" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.10.1" + version: "4.11.1" collection: dependency: transitive description: name: collection sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.19.1" color: @@ -174,7 +182,7 @@ packages: description: name: color sha256: ddcdf1b3badd7008233f5acffaf20ca9f5dc2cd0172b75f68f24526a5f5725cb - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" convert: @@ -182,39 +190,39 @@ packages: description: name: convert sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.2" cross_file: dependency: transitive description: name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" - url: "https://pub.dev" + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.3.4+2" + version: "0.3.5+2" crypto: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.dev" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.6" + version: "3.0.7" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.dev" + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.0.8" + version: "1.0.9" dart_style: dependency: transitive description: name: dart_style sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.1" dartx: @@ -222,135 +230,135 @@ packages: description: name: dartx sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.0" dbus: dependency: transitive description: name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.11" + version: "0.7.12" dio: dependency: "direct main" description: name: dio - sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 - url: "https://pub.dev" + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + url: "https://pub.flutter-io.cn" source: hosted - version: "5.9.0" + version: "5.9.2" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" - url: "https://pub.dev" + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.1" + version: "2.1.2" dotted_border: dependency: "direct main" description: name: dotted_border sha256: "99b091ec6891ba0c5331fdc2b502993c7c108f898995739a73c6845d71dad70c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" equatable: dependency: transitive description: name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" + sha256: "3e0141505477fd8ad55d6eb4e7776d3fe8430be8e497ccb1521370c3f21a3e2b" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.7" + version: "2.0.8" fake_async: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" - url: "https://pub.dev" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.2.0" file: dependency: transitive description: name: file sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "7.0.1" file_picker: dependency: "direct main" description: name: file_picker - sha256: f2d9f173c2c14635cc0e9b14c143c49ef30b4934e8d1d274d6206fcb0086a06f - url: "https://pub.dev" + sha256: "57d9a1dd5063f85fa3107fb42d1faffda52fdc948cefd5fe5ea85267a5fc7343" + url: "https://pub.flutter-io.cn" source: hosted - version: "10.3.3" + version: "10.3.10" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "967dae9a65f69377beb9f4ab292ea63ce5befa1ce24682cab1b69ca4b7a46927" - url: "https://pub.dev" + sha256: "93a5bde9775fd5adcc937f39dfa04ae0bc89c4d79bea6abc49de3f7b049d9ff6" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.1.0" + version: "4.9.0" firebase_core_platform_interface: dependency: transitive description: name: firebase_core_platform_interface - sha256: "5dbc900677dcbe5873d22ad7fbd64b047750124f1f9b7ebe2a33b9ddccc838eb" - url: "https://pub.dev" + sha256: "4a120366dbf7d5a8ee9438978530b664b855728fb8dcc3a201017660817e555b" + url: "https://pub.flutter-io.cn" source: hosted - version: "6.0.0" + version: "7.0.1" firebase_core_web: dependency: transitive description: name: firebase_core_web - sha256: f7ee08febc1c4451588ce58ffcf28edaee857e9a196fee88b85deb889990094a - url: "https://pub.dev" + sha256: "7c98f10b8c8e5adedc0b810b66a877120696675e2c22d9ca9caca092da0d9e57" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.1.0" + version: "3.7.0" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: aad5dcdea5698499b70d74d5a53b1f6a9972f85f97225e4b7ac006dd8d4f9bac - url: "https://pub.dev" + sha256: "8d0dc81a31cd030170508dc3e89bfd14355b20a1b991340af5f018e37daab5d7" + url: "https://pub.flutter-io.cn" source: hosted - version: "16.0.1" + version: "16.2.2" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "825bc11767bf50a43dccf49b3026f847ec31d0f176139bfc48d662cc128b5014" - url: "https://pub.dev" + sha256: "37abb0b0535c5497605ee94c12470e1ebbbe47e71a22d0c20bffcc912311f8cb" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.7.1" + version: "4.7.11" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: db8dbdd79921245c4de02407e33cae2d1868683be18a5ba948d2af5311e3ef5d - url: "https://pub.dev" + sha256: "54e22b43e2c26a2728a3f68c188de0f9011993ae19ae959a06d476dad935c776" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.0.1" + version: "4.1.7" fixnum: dependency: transitive description: name: fixnum sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" flutter: @@ -362,24 +370,24 @@ packages: dependency: transitive description: name: flutter_gen_core - sha256: eda54fdc5de08e7eeea663eb8442aafc8660b5a13fda4e0c9e572c64e50195fb - url: "https://pub.dev" + sha256: "454ec926fd0758667615942032e1e5308d240e309627bdf3f2bf55b17d54b23e" + url: "https://pub.flutter-io.cn" source: hosted - version: "5.11.0" + version: "5.14.0" flutter_gen_runner: dependency: "direct dev" description: name: flutter_gen_runner - sha256: "669bf8b7a9b4acbdcb7fcc5e12bf638aca19acedf43341714cbca3bf3a219521" - url: "https://pub.dev" + sha256: "2c926ab58103ea483963df9a000031e11958c7f4d1065ab021adc6829d9f6ddc" + url: "https://pub.flutter-io.cn" source: hosted - version: "5.11.0" + version: "5.14.0" flutter_launcher_icons: dependency: "direct main" description: name: flutter_launcher_icons sha256: "10f13781741a2e3972126fae08393d3c4e01fa4cd7473326b94b72cf594195e7" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.14.4" flutter_lints: @@ -387,25 +395,25 @@ packages: description: name: flutter_lints sha256: "5398f14efa795ffb7a33e9b6a08798b26a180edac4ad7db3f231e40f82ce11e1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.0" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 - url: "https://pub.dev" + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.30" + version: "2.0.34" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 - url: "https://pub.dev" + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.1" + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -420,16 +428,16 @@ packages: dependency: "direct main" description: name: freezed - sha256: "2d399f823b8849663744d2a9ddcce01c49268fb4170d0442a655bf6a2f47be22" - url: "https://pub.dev" + sha256: "13065f10e135263a4f5a4391b79a8efc5fb8106f8dd555a9e49b750b45393d77" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.1.0" + version: "3.2.3" freezed_annotation: dependency: "direct main" description: name: freezed_annotation sha256: "7294967ff0a6d98638e7acb774aac3af2550777accd8149c90af5b014e6d44d8" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0" frontend_server_client: @@ -437,7 +445,7 @@ packages: description: name: frontend_server_client sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.0.0" glob: @@ -445,31 +453,31 @@ packages: description: name: glob sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.3" go_router: dependency: "direct main" description: name: go_router - sha256: eb059dfe59f08546e9787f895bd01652076f996bcbf485a8609ef990419ad227 - url: "https://pub.dev" + sha256: d8f590a69729f719177ea68eb1e598295e8dbc41bbc247fed78b2c8a25660d7c + url: "https://pub.flutter-io.cn" source: hosted - version: "16.2.1" + version: "16.3.0" go_router_builder: dependency: "direct dev" description: name: go_router_builder - sha256: e436f0c69e717bd215639fbe27381b7e6605a255852b45cb244e28f01ab2e93a - url: "https://pub.dev" + sha256: "75200eb331e3bdfcaaa7df059c7db151c6efa56437f6715698cb3ad3d94c36e4" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.0" + version: "3.3.1" graphs: dependency: transitive description: name: graphs sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" hashcodes: @@ -477,23 +485,31 @@ packages: description: name: hashcodes sha256: "80f9410a5b3c8e110c4b7604546034749259f5d6dcca63e0d3c17c9258f1a651" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31 + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" http: dependency: "direct main" description: name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 - url: "https://pub.dev" + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.5.0" + version: "1.6.0" http_multi_server: dependency: transitive description: name: http_multi_server sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.2.2" http_parser: @@ -501,31 +517,23 @@ packages: description: name: http_parser sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.1.2" - iconsax_flutter: - dependency: transitive - description: - name: iconsax_flutter - sha256: d14b4cec8586025ac15276bdd40f6eea308cb85748135965bb6255f14beb2564 - url: "https://pub.dev" - source: hosted - version: "1.0.1" image: dependency: transitive description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" - url: "https://pub.dev" + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce + url: "https://pub.flutter-io.cn" source: hosted - version: "4.5.4" + version: "4.8.0" image_size_getter: dependency: transitive description: name: image_size_getter sha256: "7c26937e0ae341ca558b7556591fd0cc456fcc454583b7cb665d2f03e93e590f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" intl: @@ -533,7 +541,7 @@ packages: description: name: intl sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "0.20.2" io: @@ -541,111 +549,119 @@ packages: description: name: io sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.5" - js: + jni: dependency: transitive description: - name: js - sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 - url: "https://pub.dev" + name: jni + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.flutter-io.cn" source: hosted - version: "0.6.7" + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.0.1" json_annotation: dependency: "direct main" description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "4.9.0" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c - url: "https://pub.dev" + sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 + url: "https://pub.flutter-io.cn" source: hosted - version: "6.9.5" + version: "6.11.2" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec - url: "https://pub.dev" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.flutter-io.cn" source: hosted - version: "10.0.8" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 - url: "https://pub.dev" + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.9" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.dev" + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.1" + version: "3.0.2" lints: dependency: transitive description: name: lints sha256: c35bb79562d980e9a453fc715854e1ed39e24e7d0297a880ef54e17f9874a9d7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.1.1" logger: dependency: "direct main" description: name: logger - sha256: "55d6c23a6c15db14920e037fe7e0dc32e7cdaf3b64b4b25df2d541b5b6b81c0c" - url: "https://pub.dev" + sha256: "25aee487596a6257655a1e091ec2ae66bc30e7af663592cc3a27e6591e05035c" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.6.1" + version: "2.7.0" logging: dependency: transitive description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 - url: "https://pub.dev" + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 + url: "https://pub.flutter-io.cn" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.dev" + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c - url: "https://pub.dev" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.16.0" + version: "1.18.0" mime: dependency: transitive description: name: mime sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.0.0" nested: @@ -653,15 +669,23 @@ packages: description: name: nested sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.0" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + url: "https://pub.flutter-io.cn" + source: hosted + version: "9.4.1" package_config: dependency: transitive description: name: package_config sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" path: @@ -669,7 +693,7 @@ packages: description: name: path sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.9.1" path_parsing: @@ -677,7 +701,7 @@ packages: description: name: path_parsing sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" path_provider: @@ -685,31 +709,31 @@ packages: description: name: path_provider sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" - url: "https://pub.dev" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.18" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" - url: "https://pub.dev" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.2" + version: "2.6.0" path_provider_linux: dependency: transitive description: name: path_provider_linux sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.1" path_provider_platform_interface: @@ -717,7 +741,7 @@ packages: description: name: path_provider_platform_interface sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.2" path_provider_windows: @@ -725,7 +749,7 @@ packages: description: name: path_provider_windows sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.3.0" pausable_timer: @@ -733,23 +757,23 @@ packages: description: name: pausable_timer sha256: "6ef1a95441ec3439de6fb63f39a011b67e693198e7dae14e20675c3c00e86074" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.0+3" petitparser: dependency: transitive description: name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" - url: "https://pub.dev" + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" + url: "https://pub.flutter-io.cn" source: hosted - version: "6.1.0" + version: "7.0.2" pinput: dependency: "direct main" description: name: pinput sha256: c41f42ee301505ae2375ec32871c985d3717bf8aee845620465b286e0140aad2 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "5.0.2" platform: @@ -757,7 +781,7 @@ packages: description: name: platform sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.6" plugin_platform_interface: @@ -765,31 +789,31 @@ packages: description: name: plugin_platform_interface sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.8" pool: dependency: transitive description: name: pool - sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a" - url: "https://pub.dev" + sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.5.1" + version: "1.5.2" posix: dependency: transitive description: name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" - url: "https://pub.dev" + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + url: "https://pub.flutter-io.cn" source: hosted - version: "6.0.3" + version: "6.5.0" provider: dependency: "direct main" description: name: provider sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "6.1.5+1" pub_semver: @@ -797,7 +821,7 @@ packages: description: name: pub_semver sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.2.0" pubspec_parse: @@ -805,55 +829,63 @@ packages: description: name: pubspec_parse sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.5.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.6.0" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" - url: "https://pub.dev" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.flutter-io.cn" source: hosted - version: "2.5.3" + version: "2.5.5" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 - url: "https://pub.dev" + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.12" + version: "2.4.23" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" - url: "https://pub.dev" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.5.4" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" - url: "https://pub.dev" + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.4.1" + version: "2.4.2" shared_preferences_web: dependency: transitive description: name: shared_preferences_web sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.3" shared_preferences_windows: @@ -861,7 +893,7 @@ packages: description: name: shared_preferences_windows sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.4.1" shelf: @@ -869,7 +901,7 @@ packages: description: name: shelf sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.2" shelf_web_socket: @@ -877,7 +909,7 @@ packages: description: name: shelf_web_socket sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.0" sky_engine: @@ -889,40 +921,32 @@ packages: dependency: transitive description: name: source_gen - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" - url: "https://pub.dev" + sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.0" + version: "3.1.0" source_helper: dependency: transitive description: name: source_helper - sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca - url: "https://pub.dev" + sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.7" + version: "1.3.8" source_span: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" + version: "1.10.2" stack_trace: dependency: transitive description: name: stack_trace sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.12.1" stream_channel: @@ -930,7 +954,7 @@ packages: description: name: stream_channel sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.4" stream_transform: @@ -938,7 +962,7 @@ packages: description: name: stream_transform sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "2.1.1" string_scanner: @@ -946,7 +970,7 @@ packages: description: name: string_scanner sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.1" term_glyph: @@ -954,47 +978,47 @@ packages: description: name: term_glyph sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd - url: "https://pub.dev" + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" + url: "https://pub.flutter-io.cn" source: hosted - version: "0.7.4" + version: "0.7.11" time: dependency: transitive description: name: time - sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" - url: "https://pub.dev" + sha256: "46187cf30bffdab28c56be9a63861b36e4ab7347bf403297595d6a97e10c789f" + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.5" + version: "2.1.6" timing: dependency: transitive description: name: timing sha256: "62ee18aca144e4a9f29d212f5a4c6a053be252b895ab14b5821996cff4ed90fe" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.2" toastification: dependency: "direct main" description: name: toastification - sha256: "69db2bff425b484007409650d8bcd5ed1ce2e9666293ece74dcd917dacf23112" - url: "https://pub.dev" + sha256: "66c96678e3dece8ba24de3ea31634bd65a80aaecb8105f9bafe946e5f0d7590a" + url: "https://pub.flutter-io.cn" source: hosted - version: "3.0.3" + version: "3.2.0" typed_data: dependency: transitive description: name: typed_data sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.4.0" universal_platform: @@ -1002,71 +1026,71 @@ packages: description: name: universal_platform sha256: "64e16458a0ea9b99260ceb5467a214c1f298d647c659af1bff6d3bf82536b1ec" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" uuid: dependency: transitive description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.dev" + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + url: "https://pub.flutter-io.cn" source: hosted - version: "4.5.1" + version: "4.5.3" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 - url: "https://pub.dev" + sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.19" + version: "1.2.2" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec sha256: "99fd9fbd34d9f9a32efd7b6a6aae14125d8237b10403b422a6a6dfeac2806146" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.13" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc - url: "https://pub.dev" + sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e + url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.19" + version: "1.2.3" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.dev" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.4" + version: "2.2.0" vm_service: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" - url: "https://pub.dev" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" + url: "https://pub.flutter-io.cn" source: hosted - version: "14.3.1" + version: "15.2.0" watcher: dependency: transitive description: name: watcher - sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" - url: "https://pub.dev" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" + url: "https://pub.flutter-io.cn" source: hosted - version: "1.1.3" + version: "1.2.1" web: dependency: transitive description: name: web sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.1" web_socket: @@ -1074,7 +1098,7 @@ packages: description: name: web_socket sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.0.1" web_socket_channel: @@ -1082,41 +1106,41 @@ packages: description: name: web_socket_channel sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.0.3" win32: dependency: transitive description: name: win32 - sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba" - url: "https://pub.dev" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e + url: "https://pub.flutter-io.cn" source: hosted - version: "5.13.0" + version: "5.15.0" xdg_directories: dependency: transitive description: name: xdg_directories sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "1.1.0" xml: dependency: transitive description: name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" + sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + url: "https://pub.flutter-io.cn" source: hosted - version: "6.5.0" + version: "6.6.1" yaml: dependency: transitive description: name: yaml sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" + url: "https://pub.flutter-io.cn" source: hosted version: "3.1.3" sdks: - dart: ">=3.7.2 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4"