From 95e7b516b3dd7dfd7b6ac21bfa82e06584a3f25f Mon Sep 17 00:00:00 2001 From: llsajjad Date: Mon, 18 Aug 2025 15:30:22 +0330 Subject: [PATCH] Added pagination to recommand in home --- lib/data/repositories/home_repository.dart | 9 +++-- lib/data/services/home_service.dart | 9 +++-- lib/view_models/home_view_model.dart | 45 ++++++++++++++++++---- lib/views/home/pages/d_home_page.dart | 35 ++++++++++++----- lib/views/home/pages/m_home_page.dart | 36 ++++++++++++----- lib/views/home/pages/t_home_page.dart | 30 ++++++++++++--- 6 files changed, 127 insertions(+), 37 deletions(-) diff --git a/lib/data/repositories/home_repository.dart b/lib/data/repositories/home_repository.dart index ad0e13d..794743b 100644 --- a/lib/data/repositories/home_repository.dart +++ b/lib/data/repositories/home_repository.dart @@ -6,7 +6,7 @@ import 'package:tm_app/data/services/model/recommended_tenders_response/recommen class HomeRepository { HomeRepository({required HomeService homeService}) - : _homeService = homeService; + : _homeService = homeService; final HomeService _homeService; Future> getHome({ @@ -15,7 +15,10 @@ class HomeRepository { return _homeService.getHome(homeRequestModel: homeRequestModel); } - Future> getHomeRecommendedTenders() async { - return _homeService.getRecommendedTenders(); + Future> getHomeRecommendedTenders({ + required int limit, + required int offset, + }) async { + return _homeService.getRecommendedTenders(limit: limit, offset: offset); } } diff --git a/lib/data/services/home_service.dart b/lib/data/services/home_service.dart index 0dda149..0e1dd77 100644 --- a/lib/data/services/home_service.dart +++ b/lib/data/services/home_service.dart @@ -12,7 +12,7 @@ import '../../core/network/network_manager.dart'; class HomeService { HomeService({required NetworkManager networkManager}) - : _networkManager = networkManager; + : _networkManager = networkManager; final NetworkManager _networkManager; Future> getHome({ @@ -39,9 +39,12 @@ class HomeService { } } - Future> getRecommendedTenders() async { + Future> getRecommendedTenders({ + required int limit, + required int offset, + }) async { final result = await _networkManager.makeRequest( - '/api/v1/tenders/recommend', + '/api/v1/tenders/recommend?limit=$limit&offset=$offset', method: 'GET', (json) => RecommendedTendersResponse.fromJson(json), ); diff --git a/lib/view_models/home_view_model.dart b/lib/view_models/home_view_model.dart index 7b6daff..68f28a2 100644 --- a/lib/view_models/home_view_model.dart +++ b/lib/view_models/home_view_model.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:tm_app/core/utils/result.dart'; import 'package:tm_app/data/services/model/home/home_request/home_request_model.dart'; import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; +import 'package:tm_app/data/services/model/tender_data/tender_data.dart'; import '../data/repositories/home_repository.dart'; import '../data/services/model/home/home_response/home_response_model.dart'; @@ -10,9 +11,9 @@ class HomeViewModel with ChangeNotifier { final HomeRepository _homeRepository; HomeViewModel({required HomeRepository homeRepository}) - : _homeRepository = homeRepository { + : _homeRepository = homeRepository { getHome(homeRequestModel: HomeRequestModel(id: '1')); - getHomeRecommandTenders(); + getHomeRecommandTenders(reset: true); } bool _isLoading = false; @@ -24,8 +25,14 @@ class HomeViewModel with ChangeNotifier { HomeResponseModel? get homeResponse => _homeResponse; RecommendedTendersResponse? _recommendedTendersResponse; - RecommendedTendersResponse? get recommendedTendersResponse => - _recommendedTendersResponse; + final List _tenders = []; + final int _pageSize = 20; + int _currentPage = 0; + bool _isLoadingMore = false; + bool _hasMore = true; + List get tenders => _tenders; + bool get isLoadingMore => _isLoadingMore; + bool get hasMore => _hasMore; Future getHome({HomeRequestModel? homeRequestModel}) async { _isLoading = true; @@ -49,23 +56,45 @@ class HomeViewModel with ChangeNotifier { notifyListeners(); } - Future getHomeRecommandTenders() async { - _isLoading = true; + Future getHomeRecommandTenders({bool reset = false}) async { + if (_isLoadingMore || (!_hasMore && !reset)) { + return; + } + + if (reset) { + _currentPage = 0; + _hasMore = true; + _tenders.clear(); + } + + _isLoadingMore = true; _errorMessage = null; notifyListeners(); - final result = await _homeRepository.getHomeRecommendedTenders(); + final result = await _homeRepository.getHomeRecommendedTenders( + limit: _pageSize, + offset: _currentPage * _pageSize, + ); switch (result) { case Ok(): _recommendedTendersResponse = result.value; + + if (_recommendedTendersResponse?.data?.tenders.isNotEmpty ?? false) { + _tenders.addAll( + _recommendedTendersResponse!.data!.tenders.whereType(), + ); + _currentPage++; + } else { + _hasMore = false; + } break; case Error(): _errorMessage = result.error.toString(); break; } - _isLoading = false; + _isLoadingMore = false; notifyListeners(); } } diff --git a/lib/views/home/pages/d_home_page.dart b/lib/views/home/pages/d_home_page.dart index 66ca54d..570d379 100644 --- a/lib/views/home/pages/d_home_page.dart +++ b/lib/views/home/pages/d_home_page.dart @@ -5,7 +5,6 @@ import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart'; -import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/views/home/widgets/notification_card.dart'; @@ -42,7 +41,7 @@ class DesktopHomePage extends StatelessWidget { SizedBox(height: 32.0.h()), _yourTenderText(), SizedBox(height: 8.0.h()), - _bottomListView(homeViewModel.recommendedTendersResponse!), + _bottomListView(homeViewModel), ], ), ); @@ -162,20 +161,38 @@ class DesktopHomePage extends StatelessWidget { ); } - Widget _bottomListView( - RecommendedTendersResponse recommendedTendersResponse, - ) { + Widget _bottomListView(HomeViewModel homeViewModel) { + final controller = ScrollController(); + + controller.addListener(() { + if (controller.position.pixels >= + controller.position.maxScrollExtent - 200) { + if (!homeViewModel.isLoadingMore && homeViewModel.hasMore) { + homeViewModel.getHomeRecommandTenders(); + } + } + }); return SizedBox( width: 740, height: 387.0, child: ListView.builder( + controller: controller, padding: EdgeInsets.only(bottom: 20), - itemCount: recommendedTendersResponse.data!.tenders.length, + itemCount: + homeViewModel.tenders.length + + (homeViewModel.isLoadingMore ? 1 : 0), scrollDirection: Axis.horizontal, itemBuilder: (context, index) { - return TendersListItem( - tender: recommendedTendersResponse.data!.tenders[index]!, - ); + if (index < homeViewModel.tenders.length) { + return TendersListItem(tender: homeViewModel.tenders[index]); + } else { + return Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(color: AppColors.secondary50), + ), + ); + } }, ), ); diff --git a/lib/views/home/pages/m_home_page.dart b/lib/views/home/pages/m_home_page.dart index 16dad8d..c625dce 100644 --- a/lib/views/home/pages/m_home_page.dart +++ b/lib/views/home/pages/m_home_page.dart @@ -6,7 +6,6 @@ import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart'; -import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/views/home/widgets/notification_card.dart'; import 'package:tm_app/views/shared/tender_app_bar.dart'; @@ -46,7 +45,7 @@ class MobileHomePage extends StatelessWidget { _secondTenderCardsRow(homeViewModel.homeResponse!), SizedBox(height: 40.0.h()), _yourTenderText(), - _bottomListView(homeViewModel.recommendedTendersResponse!), + _bottomListView(homeViewModel), ], ), ); @@ -164,20 +163,39 @@ class MobileHomePage extends StatelessWidget { ); } - Widget _bottomListView( - RecommendedTendersResponse recommendedTendersResponse, - ) { + Widget _bottomListView(HomeViewModel homeViewModel) { + final controller = ScrollController(); + + controller.addListener(() { + if (controller.position.pixels >= + controller.position.maxScrollExtent - 200) { + if (!homeViewModel.isLoadingMore && homeViewModel.hasMore) { + homeViewModel.getHomeRecommandTenders(); + } + } + }); + return Container( constraints: BoxConstraints(maxHeight: 387.0.h()), width: double.infinity, child: ListView.builder( + controller: controller, padding: EdgeInsets.symmetric(horizontal: 24.0.w(), vertical: 15.0.h()), - itemCount: recommendedTendersResponse.data!.tenders.length, + itemCount: + homeViewModel.tenders.length + + (homeViewModel.isLoadingMore ? 1 : 0), scrollDirection: Axis.horizontal, itemBuilder: (context, index) { - return TendersListItem( - tender: recommendedTendersResponse.data!.tenders[index]!, - ); + if (index < homeViewModel.tenders.length) { + return TendersListItem(tender: homeViewModel.tenders[index]); + } else { + return Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(color: AppColors.secondary50), + ), + ); + } }, ), ); diff --git a/lib/views/home/pages/t_home_page.dart b/lib/views/home/pages/t_home_page.dart index 8c75bfd..ef8ef50 100644 --- a/lib/views/home/pages/t_home_page.dart +++ b/lib/views/home/pages/t_home_page.dart @@ -5,7 +5,6 @@ import 'package:tm_app/core/routes/app_routes.dart'; import 'package:tm_app/core/theme/colors.dart'; import 'package:tm_app/core/utils/size_config.dart'; import 'package:tm_app/data/services/model/home/home_response/home_response_model.dart'; -import 'package:tm_app/data/services/model/recommended_tenders_response/recommended_tenders_response.dart'; import 'package:tm_app/view_models/home_view_model.dart'; import 'package:tm_app/views/home/widgets/notification_card.dart'; @@ -50,7 +49,7 @@ class TabletHomePage extends StatelessWidget { SizedBox(height: 32.0.h()), _yourTenderText(), SizedBox(height: 8.0.h()), - _bottomListView(homeViewModel.recommendedTendersResponse!), + _bottomListView(homeViewModel), ], ), ), @@ -180,15 +179,36 @@ class TabletHomePage extends StatelessWidget { ); } - Widget _bottomListView(RecommendedTendersResponse recommendedTendersResponse) { + Widget _bottomListView(HomeViewModel homeViewModel) { + final controller = ScrollController(); + + controller.addListener(() { + if (controller.position.pixels >= + controller.position.maxScrollExtent - 200) { + if (!homeViewModel.isLoadingMore && homeViewModel.hasMore) { + homeViewModel.getHomeRecommandTenders(); + } + } + }); return SizedBox( width: double.infinity, height: 387.0.h(), child: ListView.builder( - itemCount: recommendedTendersResponse.data!.tenders.length, + itemCount: + homeViewModel.tenders.length + + (homeViewModel.isLoadingMore ? 1 : 0), scrollDirection: Axis.horizontal, itemBuilder: (context, index) { - return TendersListItem(tender: recommendedTendersResponse.data!.tenders[index]!); + if (index < homeViewModel.tenders.length) { + return TendersListItem(tender: homeViewModel.tenders[index]); + } else { + return Center( + child: Padding( + padding: EdgeInsets.all(16.0), + child: CircularProgressIndicator(color: AppColors.secondary50), + ), + ); + } }, ), );