Skip to content

Commit bc63d62

Browse files
authored
Connectivity check (#1026)
* Applied connectivity check and timeout in news, signing, wallet * fix workflow * make news screen consistent with the other pages * fix workflow
1 parent f8f7c95 commit bc63d62

3 files changed

Lines changed: 277 additions & 82 deletions

File tree

app/lib/apps/news/news_screen.dart

Lines changed: 92 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
1+
import 'dart:async';
12
import 'dart:convert';
3+
import 'package:connectivity_plus/connectivity_plus.dart';
24
import 'package:flutter/material.dart';
35
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
46
import 'package:http/http.dart' as http;
57
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
68
import 'package:threebotlogin/helpers/globals.dart';
9+
import 'package:threebotlogin/helpers/logger.dart';
710
import 'package:threebotlogin/widgets/layout_drawer.dart';
811
import 'package:xml2json/xml2json.dart';
912
import 'package:url_launcher/url_launcher.dart';
@@ -22,6 +25,7 @@ class _NewsScreenState extends State<NewsScreen> {
2225
final PagingController<int, Map<String, dynamic>> _pagingController =
2326
PagingController(firstPageKey: 0);
2427
final String newsUrl = Globals().newsUrl;
28+
bool _isLoading = false;
2529

2630
@override
2731
void initState() {
@@ -30,8 +34,25 @@ class _NewsScreenState extends State<NewsScreen> {
3034
}
3135

3236
Future<void> getArticles(int pageKey) async {
37+
if (_isLoading) return;
38+
39+
_isLoading = true;
3340
try {
34-
final response = await http.get(Uri.parse(newsUrl));
41+
final connectivityResult = await Connectivity().checkConnectivity();
42+
if (connectivityResult.contains(ConnectivityResult.none)) {
43+
throw Exception('No internet connection. Please check your network.');
44+
}
45+
46+
final response = await http.get(Uri.parse(newsUrl)).timeout(
47+
const Duration(seconds: 30),
48+
onTimeout: () {
49+
throw TimeoutException('Loading news feed timed out');
50+
},
51+
);
52+
53+
if (response.statusCode != 200) {
54+
throw Exception('Failed to load news feed: ${response.statusCode}');
55+
}
3556
xml2json.parse(response.body);
3657

3758
var data = json.decode(xml2json.toGData());
@@ -47,11 +68,48 @@ class _NewsScreenState extends State<NewsScreen> {
4768
isLastPage
4869
? _pagingController.appendLastPage(newArticles)
4970
: _pagingController.appendPage(newArticles, pageKey + 1);
50-
} catch (e) {
51-
_pagingController.error = e;
71+
72+
_isLoading = false;
73+
} on TimeoutException catch (e) {
74+
_handleError(
75+
'Loading news feed timed out. Please check your connection.', e);
76+
} on Exception catch (e) {
77+
_handleError(
78+
e.toString().contains('No internet connection')
79+
? 'No internet connection. Please check your network.'
80+
: 'Failed to load news feed. Please try again.',
81+
e);
82+
}
83+
}
84+
85+
void _handleError(String message, Exception error) {
86+
logger.e('News feed error: $message', error: error);
87+
88+
_isLoading = false;
89+
90+
_pagingController.error = message;
91+
92+
if (mounted && context.mounted) {
93+
ScaffoldMessenger.of(context).showSnackBar(
94+
SnackBar(
95+
content: Text(
96+
message,
97+
style: Theme.of(context)
98+
.textTheme
99+
.bodyMedium!
100+
.copyWith(color: Theme.of(context).colorScheme.errorContainer),
101+
),
102+
duration: const Duration(seconds: 3),
103+
),
104+
);
52105
}
53106
}
54107

108+
Future<void> _refreshNews() async {
109+
_pagingController.refresh();
110+
return Future.delayed(const Duration(milliseconds: 300));
111+
}
112+
55113
@override
56114
void dispose() {
57115
_pagingController.dispose();
@@ -63,7 +121,7 @@ class _NewsScreenState extends State<NewsScreen> {
63121
return LayoutDrawer(
64122
titleText: 'News',
65123
content: RefreshIndicator(
66-
onRefresh: () async => _pagingController.refresh(),
124+
onRefresh: _refreshNews,
67125
child: PagedListView<int, Map<String, dynamic>>(
68126
pagingController: _pagingController,
69127
builderDelegate: PagedChildBuilderDelegate<Map<String, dynamic>>(
@@ -82,6 +140,36 @@ class _NewsScreenState extends State<NewsScreen> {
82140
],
83141
),
84142
),
143+
firstPageErrorIndicatorBuilder: (context) => Center(
144+
child: Column(
145+
mainAxisAlignment: MainAxisAlignment.center,
146+
children: [
147+
ElevatedButton.icon(
148+
onPressed: _refreshNews,
149+
icon: const Icon(Icons.refresh),
150+
label: const Text('Try Again'),
151+
),
152+
],
153+
),
154+
),
155+
noItemsFoundIndicatorBuilder: (context) => Center(
156+
child: Column(
157+
mainAxisAlignment: MainAxisAlignment.center,
158+
children: [
159+
Icon(
160+
Icons.article_outlined,
161+
size: 48,
162+
color: Theme.of(context).colorScheme.onSurfaceVariant,
163+
),
164+
const SizedBox(height: 8),
165+
Text(
166+
'No articles found',
167+
style: Theme.of(context).textTheme.bodyLarge!.copyWith(
168+
color: Theme.of(context).colorScheme.onSurface),
169+
),
170+
],
171+
),
172+
),
85173
),
86174
),
87175
),

app/lib/screens/signing/signing_mixin.dart

Lines changed: 79 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import 'dart:async';
2+
3+
import 'package:connectivity_plus/connectivity_plus.dart';
14
import 'package:flutter/material.dart';
25
import 'package:flutter/services.dart';
36
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -14,6 +17,7 @@ mixin SigningMixin<T extends ConsumerStatefulWidget> on ConsumerState<T> {
1417
bool isLoading = false;
1518
bool isLoadingWallets = false;
1619
bool loadingFailed = false;
20+
String? walletLoadingError;
1721
String? walletError;
1822
String? destUrlError;
1923

@@ -132,20 +136,41 @@ mixin SigningMixin<T extends ConsumerStatefulWidget> on ConsumerState<T> {
132136
}
133137

134138
Future<void> checkWalletsListed() async {
139+
final connectivityResult = await Connectivity().checkConnectivity();
140+
141+
if (connectivityResult.contains(ConnectivityResult.none)) {
142+
_handleWalletLoadingFailure(
143+
'No internet connection. Please check your network.');
144+
return;
145+
}
146+
135147
final walletsNotifierRef = ref.read(walletsNotifier.notifier);
136148
if (!walletsNotifierRef.isListed) {
137149
setState(() {
138150
isLoadingWallets = true;
139151
loadingFailed = false;
152+
walletLoadingError = null;
140153
selectedWallet = null;
141154
});
155+
142156
try {
143-
await walletsNotifierRef.list();
157+
await walletsNotifierRef.list().timeout(
158+
const Duration(seconds: 30),
159+
onTimeout: () {
160+
throw TimeoutException('Loading wallets timed out');
161+
},
162+
);
163+
} on TimeoutException catch (e) {
164+
logger.e('Wallet loading timed out: $e');
165+
if (mounted) {
166+
_handleWalletLoadingFailure(
167+
'Loading wallets timed out. Please check your connection.');
168+
}
144169
} catch (e) {
170+
logger.e('Failed to load wallets: $e');
145171
if (mounted) {
146-
setState(() {
147-
loadingFailed = true;
148-
});
172+
_handleWalletLoadingFailure(
173+
'Failed to load wallets. Please try again.');
149174
}
150175
} finally {
151176
if (mounted) {
@@ -157,18 +182,56 @@ mixin SigningMixin<T extends ConsumerStatefulWidget> on ConsumerState<T> {
157182
}
158183
}
159184

185+
void _handleWalletLoadingFailure(String errorMessage) {
186+
setState(() {
187+
isLoadingWallets = false;
188+
loadingFailed = true;
189+
walletLoadingError = errorMessage;
190+
});
191+
192+
if (mounted && context.mounted) {
193+
ScaffoldMessenger.of(context).showSnackBar(
194+
SnackBar(
195+
content: Text(
196+
errorMessage,
197+
style: Theme.of(context)
198+
.textTheme
199+
.bodyMedium!
200+
.copyWith(color: Theme.of(context).colorScheme.errorContainer),
201+
),
202+
duration: const Duration(seconds: 3),
203+
),
204+
);
205+
}
206+
}
207+
160208
Future<void> retryLoadingWallets() async {
209+
final connectivityResult = await Connectivity().checkConnectivity();
210+
211+
if (connectivityResult.contains(ConnectivityResult.none)) {
212+
_handleWalletLoadingFailure(
213+
'No internet connection. Please check your network.');
214+
return;
215+
}
216+
161217
setState(() {
162218
isLoadingWallets = true;
163219
loadingFailed = false;
220+
walletLoadingError = null;
164221
selectedWallet = null;
165222
});
166223

167224
final walletsNotifierRef = ref.read(walletsNotifier.notifier);
168225
walletsNotifierRef.clear();
169226

170227
try {
171-
await walletsNotifierRef.list();
228+
await walletsNotifierRef.list().timeout(
229+
const Duration(seconds: 30),
230+
onTimeout: () {
231+
throw TimeoutException('Loading wallets timed out');
232+
},
233+
);
234+
172235
if (mounted && context.mounted) {
173236
ScaffoldMessenger.of(context).showSnackBar(
174237
SnackBar(
@@ -181,28 +244,17 @@ mixin SigningMixin<T extends ConsumerStatefulWidget> on ConsumerState<T> {
181244
),
182245
);
183246
}
247+
} on TimeoutException catch (e) {
248+
logger.e('Wallet loading timed out on retry: $e');
249+
if (mounted) {
250+
_handleWalletLoadingFailure(
251+
'Loading wallets timed out. Please check your connection.');
252+
}
184253
} catch (e) {
254+
logger.e('Failed to load wallets on retry: $e');
185255
if (mounted) {
186-
setState(() {
187-
loadingFailed = true;
188-
});
189-
ScaffoldMessenger.of(context).showSnackBar(
190-
SnackBar(
191-
content: Text(
192-
'Failed to load wallets. Please check your connection.',
193-
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
194-
color: Theme.of(context).colorScheme.errorContainer),
195-
),
196-
duration: const Duration(seconds: 3),
197-
action: SnackBarAction(
198-
label: 'Retry',
199-
textColor: Theme.of(context).colorScheme.errorContainer,
200-
onPressed: () {
201-
retryLoadingWallets();
202-
},
203-
),
204-
),
205-
);
256+
_handleWalletLoadingFailure(
257+
'Failed to load wallets. Please try again.');
206258
}
207259
} finally {
208260
if (mounted) {
@@ -258,8 +310,8 @@ mixin SigningMixin<T extends ConsumerStatefulWidget> on ConsumerState<T> {
258310
child: Text(wallet.name,
259311
style: Theme.of(context)
260312
.textTheme
261-
.bodyMedium
262-
!.copyWith(
313+
.bodyMedium!
314+
.copyWith(
263315
color:
264316
Theme.of(context).colorScheme.onSurface,
265317
)),

0 commit comments

Comments
 (0)