diff --git a/lib/src/visual/views/settings/help/news.view.dart b/lib/src/visual/views/settings/help/news.view.dart index fcc59eb8..c0ad7a66 100644 --- a/lib/src/visual/views/settings/help/news.view.dart +++ b/lib/src/visual/views/settings/help/news.view.dart @@ -19,6 +19,15 @@ class _NewsViewState extends State { super.initState(); // Mark all as read when entering the page newsService.markAllAsRead(); + _reloadNews(); + } + + Future _reloadNews() async { + await newsService.fetchFeed(); + await newsService.markAllAsRead(); + if (mounted) { + setState(() {}); + } } @override @@ -29,105 +38,117 @@ class _NewsViewState extends State { appBar: AppBar( title: Text(context.lang.settingsHelpNews), ), - body: entries.isEmpty - ? Center( - child: Text( - 'No news articles found.', - style: Theme.of(context).textTheme.bodyLarge?.copyWith( - color: Colors.grey, - ), - ), - ) - : ListView.builder( - padding: const EdgeInsets.symmetric(vertical: 8), - itemCount: entries.length, - itemBuilder: (context, index) { - final entry = entries[index]; - final isDark = isDarkMode(context); - - return ReactiveTapFeedback( - onTap: () => launchUrl( - Uri.parse(entry.link), - mode: LaunchMode.externalApplication, + body: RefreshIndicator( + onRefresh: _reloadNews, + child: entries.isEmpty + ? SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: Container( + height: MediaQuery.of(context).size.height * 0.7, + alignment: Alignment.center, + child: Text( + 'No news articles found.', + style: Theme.of(context).textTheme.bodyLarge?.copyWith( + color: Colors.grey, + ), ), - child: Card( - color: isDark ? Colors.grey[800] : Colors.grey[200], - clipBehavior: Clip.antiAlias, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), + ), + ) + : ListView.builder( + physics: const AlwaysScrollableScrollPhysics(), + padding: const EdgeInsets.symmetric(vertical: 8), + itemCount: entries.length, + itemBuilder: (context, index) { + final entry = entries[index]; + final isDark = isDarkMode(context); + + return ReactiveTapFeedback( + onTap: () => launchUrl( + Uri.parse(entry.link), + mode: LaunchMode.externalApplication, ), - elevation: 0, - margin: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 8, - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (entry.imageUrl.isNotEmpty) - CachedNetworkImage( - imageUrl: entry.imageUrl, - height: 180, - fit: BoxFit.cover, - placeholder: (context, url) => Container( + child: Card( + color: isDark ? Colors.grey[800] : Colors.grey[200], + clipBehavior: Clip.antiAlias, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + margin: const EdgeInsets.symmetric( + horizontal: 16, + vertical: 8, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + if (entry.imageUrl.isNotEmpty) + CachedNetworkImage( + imageUrl: entry.imageUrl, height: 180, - color: Colors.grey.withValues(alpha: 0.1), - ), - errorWidget: (context, url, error) => Container( - height: 180, - color: Colors.grey.withValues(alpha: 0.1), - child: const Icon( - Icons.broken_image, - size: 50, - color: Colors.grey, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + height: 180, + color: Colors.grey.withValues(alpha: 0.1), + ), + errorWidget: (context, url, error) => Container( + height: 180, + color: Colors.grey.withValues(alpha: 0.1), + child: const Icon( + Icons.broken_image, + size: 50, + color: Colors.grey, + ), ), ), - ), - Padding( - padding: const EdgeInsets.all(16), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (entry.pubDate != null) ...[ + Padding( + padding: const EdgeInsets.all(16), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (entry.pubDate != null) ...[ + Text( + DateFormat.yMMMMd( + Localizations.localeOf( + context, + ).toString(), + ).format(entry.pubDate!), + style: Theme.of(context).textTheme.bodySmall + ?.copyWith( + color: Colors.grey, + ), + ), + const SizedBox(height: 8), + ], Text( - DateFormat.yMMMMd( - Localizations.localeOf(context).toString(), - ).format(entry.pubDate!), - style: Theme.of(context).textTheme.bodySmall + entry.title, + style: Theme.of(context).textTheme.titleLarge ?.copyWith( - color: Colors.grey, + fontWeight: FontWeight.bold, ), ), const SizedBox(height: 8), - ], - Text( - entry.title, - style: Theme.of(context).textTheme.titleLarge - ?.copyWith( - fontWeight: FontWeight.bold, - ), - ), - const SizedBox(height: 8), - Text( - entry.description, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: Theme.of(context).textTheme.bodyMedium - ?.copyWith( - color: context.color.onSurface.withValues( - alpha: 0.8, + Text( + entry.description, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium + ?.copyWith( + color: context.color.onSurface + .withValues( + alpha: 0.8, + ), ), - ), - ), - ], + ), + ], + ), ), - ), - ], + ], + ), ), - ), - ); - }, - ), + ); + }, + ), + ), ); } }