1+ import 'dart:async' ;
12import 'dart:convert' ;
3+ import 'package:connectivity_plus/connectivity_plus.dart' ;
24import 'package:flutter/material.dart' ;
35import 'package:flutter_widget_from_html/flutter_widget_from_html.dart' ;
46import 'package:http/http.dart' as http;
57import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart' ;
68import 'package:threebotlogin/helpers/globals.dart' ;
9+ import 'package:threebotlogin/helpers/logger.dart' ;
710import 'package:threebotlogin/widgets/layout_drawer.dart' ;
811import 'package:xml2json/xml2json.dart' ;
912import '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 ),
0 commit comments