@@ -4,10 +4,10 @@ import 'package:flutter_svg/flutter_svg.dart';
44
55import '../../services/cakepay/cakepay_service.dart' ;
66import '../../services/cakepay/src/models/card.dart' ;
7- import '../../services/cakepay/src/models/vendor.dart' ;
87import '../../themes/stack_colors.dart' ;
98import '../../utilities/assets.dart' ;
109import '../../utilities/constants.dart' ;
10+ import '../../utilities/logger.dart' ;
1111import '../../utilities/text_styles.dart' ;
1212import '../../utilities/util.dart' ;
1313import '../../widgets/background.dart' ;
@@ -16,6 +16,7 @@ import '../../widgets/custom_buttons/app_bar_icon_button.dart';
1616import '../../widgets/desktop/desktop_dialog.dart' ;
1717import '../../widgets/desktop/desktop_dialog_close_button.dart' ;
1818import '../../widgets/icon_widgets/credit_card_icon.dart' ;
19+ import '../../widgets/infinite_scroll_list_view.dart' ;
1920import '../../widgets/loading_indicator.dart' ;
2021import '../../widgets/rounded_container.dart' ;
2122import '../../widgets/stack_text_field.dart' ;
@@ -31,20 +32,21 @@ class CakePayVendorsView extends StatefulWidget {
3132}
3233
3334class _CakePayVendorsViewState extends State <CakePayVendorsView > {
34- List <CakePayVendor > _vendors = [];
3535 List <String > _countryNames = [];
3636 String ? _selectedCountry;
37+ String ? _searchQuery;
3738 bool _loading = true ;
38- String ? _error;
3939
4040 final _searchController = TextEditingController ();
4141 final _searchFocusNode = FocusNode ();
4242 final _countrySearchController = TextEditingController ();
4343
44+ final _listController = InfiniteScrollListController ();
45+
4446 @override
4547 void initState () {
4648 super .initState ();
47- _loadVendors ();
49+ _loadCountries ();
4850 }
4951
5052 @override
@@ -55,59 +57,60 @@ class _CakePayVendorsViewState extends State<CakePayVendorsView> {
5557 super .dispose ();
5658 }
5759
58- List <CakePayCard > _availableCards () =>
59- _vendors.expand ((v) => v.cards.where ((c) => c.available)).toList ();
60-
61- /// Derive a country list from the loaded vendors so we don't need the
62- /// broken /marketplace/countries/ endpoint.
63- Future <void > _deriveCountries () async {
64- // naive caching
65- if (_countryNames.isNotEmpty) return ;
66-
67- final response = await CakePayService .instance.client.getAllCountries ();
60+ Future <({List <CakePayCard > cards, int ? nextPage})> _fetchCards (
61+ int page,
62+ ) async {
63+ final response = await CakePayService .instance.client.getVendors (
64+ page: page,
65+ pageSize: 50 ,
66+ country: _selectedCountry,
67+ search: _searchQuery,
68+ );
6869
6970 if (response.hasError || response.value == null ) {
70- if (mounted) {
71- setState (() {
72- _error = response.exception? .message ?? "Failed to load countries" ;
73- });
74- }
75- } else {
76- _countryNames =
77- response.value!
78- .where ((e) => e.available)
79- .map ((e) => e.name)
80- .toSet ()
81- .toList (growable: false )
82- ..sort ();
71+ throw response.exception ??
72+ Exception ("Unknown exception with value is null????" );
8373 }
74+
75+ return (
76+ cards: response.value! .vendors
77+ .expand ((e) => e.cards.where ((e) => e.available))
78+ .toList (),
79+ nextPage: response.value! .nextPage,
80+ );
8481 }
8582
86- Future <void > _loadVendors () async {
83+ Future <void > _loadCountries () async {
84+ // naive caching
85+ if (_countryNames.isNotEmpty) return ;
86+
8787 setState (() {
8888 _loading = true ;
89- _error = null ;
9089 });
9190
92- final resp = await CakePayService .instance.client.getVendors (
93- country: _selectedCountry,
94- search: _searchController.text.trim ().isNotEmpty
95- ? _searchController.text.trim ()
96- : null ,
97- );
91+ try {
92+ final response = await CakePayService .instance.client.getAllCountries ();
9893
99- if (! mounted) return ;
100-
101- if (resp.hasError || resp.value == null ) {
102- setState (() {
103- _error = resp.exception? .message ?? "Failed to load gift cards" ;
104- });
105- } else {
106- _vendors = resp.value! ;
107- await _deriveCountries ();
94+ if (response.hasError || response.value == null ) {
95+ Logging .instance.e (
96+ response.exception? .message ?? "Failed to load countries" ,
97+ error: response.exception,
98+ stackTrace: StackTrace .current,
99+ );
100+ } else {
101+ setState (() {
102+ _countryNames =
103+ response.value!
104+ .where ((e) => e.available)
105+ .map ((e) => e.name)
106+ .toSet ()
107+ .toList (growable: false )
108+ ..sort ();
109+ });
110+ }
111+ } finally {
112+ if (mounted) setState (() => _loading = false );
108113 }
109-
110- if (mounted) setState (() => _loading = false );
111114 }
112115
113116 Future <void > _onCardTapped (CakePayCard card) async {
@@ -119,7 +122,6 @@ class _CakePayVendorsViewState extends State<CakePayVendorsView> {
119122 @override
120123 Widget build (BuildContext context) {
121124 final isDesktop = Util .isDesktop;
122- final cards = _availableCards ();
123125
124126 return ConditionalParent (
125127 condition: isDesktop,
@@ -180,7 +182,10 @@ class _CakePayVendorsViewState extends State<CakePayVendorsView> {
180182 _SearchField (
181183 controller: _searchController,
182184 focusNode: _searchFocusNode,
183- onSubmitted: (_) => _loadVendors (),
185+ onSubmitted: (value) {
186+ setState (() => _searchQuery = value);
187+ _listController.refresh ();
188+ },
184189 ),
185190 if (_countryNames.isNotEmpty) ...[
186191 SizedBox (height: isDesktop ? 12 : 12 ),
@@ -190,34 +195,33 @@ class _CakePayVendorsViewState extends State<CakePayVendorsView> {
190195 searchController: _countrySearchController,
191196 onChanged: (value) {
192197 setState (() => _selectedCountry = value);
193- _loadVendors ();
198+ _listController. refresh ();
194199 },
195200 ),
196201 ],
197202 SizedBox (height: isDesktop ? 16 : 12 ),
198203 Expanded (
199204 child: _loading
200205 ? const LoadingIndicator (width: 48 , height: 48 )
201- : cards.isEmpty
202- ? Center (
203- child: Text (
204- _error ?? "No gift cards found" ,
205- style: isDesktop
206- ? STextStyles .desktopTextSmall (context)
207- : STextStyles .itemSubtitle (context),
208- ),
209- )
210- : ListView .separated (
211- shrinkWrap: isDesktop,
212- primary: isDesktop ? false : null ,
213- itemCount: cards.length,
206+ : InfiniteScrollListView <CakePayCard , int >(
207+ controller: _listController,
214208 padding: .only (bottom: isDesktop ? 32 : 16 ),
215- separatorBuilder: (_, __) =>
209+ firstPageKey: 1 ,
210+ separatorBuilder: (_, _) =>
216211 SizedBox (height: isDesktop ? 16 : 12 ),
217- itemBuilder: (_, index) => _CardTile (
218- card: cards[index],
219- onTap: () => _onCardTapped (cards[index]),
220- ),
212+ fetchPage: (pageKey) async {
213+ final result = await _fetchCards (pageKey);
214+ return InfiniteScrollPage (
215+ items: result.cards,
216+ nextPageKey: result.nextPage,
217+ );
218+ },
219+ itemBuilder: (context, item, index) {
220+ return _CardTile (
221+ card: item,
222+ onTap: () => _onCardTapped (item),
223+ );
224+ },
221225 ),
222226 ),
223227 ],
0 commit comments