Skip to content

Commit c458074

Browse files
committed
ui: "infinite" scrolling list view clean up and other little things
1 parent c7c1c87 commit c458074

4 files changed

Lines changed: 211 additions & 140 deletions

File tree

lib/pages/cakepay/cakepay_vendors_view.dart

Lines changed: 56 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import '../../services/cakepay/src/models/card.dart';
77
import '../../themes/stack_colors.dart';
88
import '../../utilities/assets.dart';
99
import '../../utilities/constants.dart';
10-
import '../../utilities/logger.dart';
1110
import '../../utilities/text_styles.dart';
1211
import '../../utilities/util.dart';
1312
import '../../widgets/background.dart';
1413
import '../../widgets/conditional_parent.dart';
1514
import '../../widgets/custom_buttons/app_bar_icon_button.dart';
1615
import '../../widgets/desktop/desktop_dialog.dart';
1716
import '../../widgets/desktop/desktop_dialog_close_button.dart';
17+
import '../../widgets/desktop/secondary_button.dart';
1818
import '../../widgets/icon_widgets/credit_card_icon.dart';
1919
import '../../widgets/infinite_scroll_list_view.dart';
2020
import '../../widgets/loading_indicator.dart';
@@ -46,7 +46,13 @@ class _CakePayVendorsViewState extends State<CakePayVendorsView> {
4646
@override
4747
void initState() {
4848
super.initState();
49-
_loadCountries();
49+
WidgetsBinding.instance.addPostFrameCallback((_) async {
50+
try {
51+
_countryNames = await CakePayService.instance.getCountryNames();
52+
} finally {
53+
if (mounted) setState(() => _loading = false);
54+
}
55+
});
5056
}
5157

5258
@override
@@ -80,39 +86,6 @@ class _CakePayVendorsViewState extends State<CakePayVendorsView> {
8086
);
8187
}
8288

83-
Future<void> _loadCountries() async {
84-
// naive caching
85-
if (_countryNames.isNotEmpty) return;
86-
87-
setState(() {
88-
_loading = true;
89-
});
90-
91-
try {
92-
final response = await CakePayService.instance.client.getAllCountries();
93-
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);
113-
}
114-
}
115-
11689
Future<void> _onCardTapped(CakePayCard card) async {
11790
await Navigator.of(
11891
context,
@@ -202,9 +175,10 @@ class _CakePayVendorsViewState extends State<CakePayVendorsView> {
202175
SizedBox(height: isDesktop ? 16 : 12),
203176
Expanded(
204177
child: _loading
205-
? const LoadingIndicator(width: 48, height: 48)
178+
? const LoadingIndicator(width: 64, height: 64)
206179
: InfiniteScrollListView<CakePayCard, int>(
207180
controller: _listController,
181+
prefetchThreshold: 300,
208182
padding: .only(bottom: isDesktop ? 32 : 16),
209183
firstPageKey: 1,
210184
separatorBuilder: (_, _) =>
@@ -222,6 +196,52 @@ class _CakePayVendorsViewState extends State<CakePayVendorsView> {
222196
onTap: () => _onCardTapped(item),
223197
);
224198
},
199+
firstPageProgressBuilder: (_) =>
200+
const LoadingIndicator(width: 64, height: 64),
201+
newPageProgressBuilder: (_) => const Center(
202+
child: Padding(
203+
padding: .all(16),
204+
child: LoadingIndicator(width: 48, height: 48),
205+
),
206+
),
207+
emptyBuilder: (_) => Center(
208+
child: Padding(
209+
padding: const .all(24),
210+
child: Text(
211+
"No items",
212+
style: STextStyles.w500_14(context).copyWith(
213+
color: Theme.of(
214+
context,
215+
).extension<StackColors>()!.textSubtitle1,
216+
),
217+
),
218+
),
219+
),
220+
newPageErrorBuilder: (context, error, retry) => Center(
221+
child: Padding(
222+
padding: const .all(16),
223+
child: Column(
224+
mainAxisSize: .min,
225+
children: [
226+
Text(
227+
error.toString(),
228+
style: STextStyles.w500_14(context).copyWith(
229+
color: Theme.of(
230+
context,
231+
).extension<StackColors>()!.textSubtitle1,
232+
),
233+
),
234+
const SizedBox(height: 16),
235+
SecondaryButton(
236+
label: "Retry",
237+
buttonHeight: isDesktop ? .s : .l,
238+
width: 100,
239+
onPressed: retry,
240+
),
241+
],
242+
),
243+
),
244+
),
225245
),
226246
),
227247
],

lib/services/cakepay/cakepay_service.dart

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:drift/drift.dart';
2+
import 'package:mutex/mutex.dart';
23

34
import '../../db/drift/shared_db/shared_database.dart';
45
import '../../external_api_keys.dart';
@@ -14,6 +15,43 @@ class CakePayService {
1415
return _client ??= CakePayClient(apiToken: kCakePayApiToken);
1516
}
1617

18+
// TODO clean this up some day
19+
// simple in memory cache
20+
DateTime? _countryNamesUpdated;
21+
List<String> _countryNames = [];
22+
final _countryNamesMutex = Mutex();
23+
Future<List<String>> getCountryNames({bool refreshCache = false}) async {
24+
return _countryNamesMutex.protect(() async {
25+
final isFresh =
26+
_countryNamesUpdated != null &&
27+
_countryNamesUpdated!
28+
.add(const Duration(hours: 12))
29+
.isAfter(DateTime.now());
30+
31+
if (!refreshCache && isFresh && _countryNames.isNotEmpty) {
32+
return _countryNames;
33+
}
34+
35+
final response = await client.getAllCountries();
36+
37+
if (response.hasError || response.value == null) {
38+
throw response.exception ?? Exception("Failed to fetch countries");
39+
}
40+
41+
_countryNames =
42+
response.value!
43+
.where((e) => e.available)
44+
.map((e) => e.name)
45+
.toSet()
46+
.toList()
47+
..sort();
48+
49+
_countryNamesUpdated = DateTime.now();
50+
51+
return _countryNames;
52+
});
53+
}
54+
1755
Future<void> addOrderId(String orderId) async {
1856
final db = SharedDrift.get();
1957

lib/services/cakepay/src/client.dart

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -444,11 +444,7 @@ class CakePayClient {
444444
final headers = overrideHeaders ?? _authHeaders;
445445
final proxy = _proxyInfo;
446446

447-
try {
448-
throw Exception(path);
449-
} catch (e, s) {
450-
Logging.instance.f("$_kTag $method $uri", error: e, stackTrace: s);
451-
}
447+
Logging.instance.t("$_kTag $method $uri");
452448

453449
switch (method) {
454450
case 'GET':

0 commit comments

Comments
 (0)