From 2788a43dda2d4f75c7dd704502e6976fbc8c0022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bot=20of=20Thomas=20G=C3=BCttler?= Date: Fri, 5 Jun 2026 17:53:48 +0200 Subject: [PATCH] feat: dedicated page for allowed image-sender addresses (#420) --- lib/ui/router.dart | 7 +++ lib/ui/screens/email_detail_screen.dart | 7 ++- lib/ui/screens/thread_detail_screen.dart | 7 ++- .../screens/trusted_image_senders_screen.dart | 63 +++++++++++++++++++ lib/ui/screens/user_preferences_screen.dart | 41 +++--------- scripts/check_coverage.dart | 1 + 6 files changed, 90 insertions(+), 36 deletions(-) create mode 100644 lib/ui/screens/trusted_image_senders_screen.dart diff --git a/lib/ui/router.dart b/lib/ui/router.dart index caff49a..9b506df 100644 --- a/lib/ui/router.dart +++ b/lib/ui/router.dart @@ -21,6 +21,7 @@ import 'package:sharedinbox/ui/screens/sieve_script_edit_screen.dart'; import 'package:sharedinbox/ui/screens/sieve_scripts_screen.dart'; import 'package:sharedinbox/ui/screens/sync_log_screen.dart'; import 'package:sharedinbox/ui/screens/thread_detail_screen.dart'; +import 'package:sharedinbox/ui/screens/trusted_image_senders_screen.dart'; import 'package:sharedinbox/ui/screens/undo_log_screen.dart'; import 'package:sharedinbox/ui/screens/user_preferences_screen.dart'; import 'package:sharedinbox/ui/widgets/undo_shell.dart'; @@ -67,6 +68,12 @@ final router = GoRouter( path: 'preferences', builder: (ctx, state) => const UserPreferencesScreen(), ), + GoRoute( + path: 'trusted-senders', + builder: (ctx, state) => TrustedImageSendersScreen( + highlightedSender: state.extra as String?, + ), + ), GoRoute( path: ':accountId/edit', builder: (ctx, state) => EditAccountScreen( diff --git a/lib/ui/screens/email_detail_screen.dart b/lib/ui/screens/email_detail_screen.dart index d3589a9..2e7240a 100644 --- a/lib/ui/screens/email_detail_screen.dart +++ b/lib/ui/screens/email_detail_screen.dart @@ -229,11 +229,14 @@ class _EmailDetailScreenState extends ConsumerState { 'Images will be loaded automatically for this sender.', ), action: SnackBarAction( - label: 'Settings', + label: 'View', onPressed: () { if (mounted) { unawaited( - context.push('/accounts/preferences'), + context.push( + '/accounts/trusted-senders', + extra: senderEmail, + ), ); } }, diff --git a/lib/ui/screens/thread_detail_screen.dart b/lib/ui/screens/thread_detail_screen.dart index ef59980..905dc57 100644 --- a/lib/ui/screens/thread_detail_screen.dart +++ b/lib/ui/screens/thread_detail_screen.dart @@ -217,11 +217,14 @@ class _EmailMessageCardState extends ConsumerState<_EmailMessageCard> { 'Images will be loaded automatically for this sender.', ), action: SnackBarAction( - label: 'Settings', + label: 'View', onPressed: () { if (mounted) { unawaited( - context.push('/accounts/preferences'), + context.push( + '/accounts/trusted-senders', + extra: senderEmail, + ), ); } }, diff --git a/lib/ui/screens/trusted_image_senders_screen.dart b/lib/ui/screens/trusted_image_senders_screen.dart new file mode 100644 index 0000000..80d6e30 --- /dev/null +++ b/lib/ui/screens/trusted_image_senders_screen.dart @@ -0,0 +1,63 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +import 'package:sharedinbox/di.dart'; + +class TrustedImageSendersScreen extends ConsumerWidget { + const TrustedImageSendersScreen({super.key, this.highlightedSender}); + + final String? highlightedSender; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final trustedSendersAsync = ref.watch(trustedImageSendersProvider); + + return Scaffold( + appBar: AppBar(title: const Text('Allowed addresses for images')), + body: trustedSendersAsync.when( + loading: () => const Center(child: CircularProgressIndicator()), + error: (_, __) => + const Center(child: Text('Error loading trusted senders')), + data: (senders) { + if (senders.isEmpty) { + return const Padding( + padding: EdgeInsets.all(16), + child: Text( + 'No addresses added yet. ' + 'Tap "Load remote images" in an email to add the sender.', + ), + ); + } + return ListView.builder( + itemCount: senders.length, + itemBuilder: (context, index) { + final sender = senders[index]; + final isHighlighted = sender == highlightedSender; + return ListTile( + title: Text( + sender, + style: isHighlighted + ? const TextStyle(fontWeight: FontWeight.bold) + : null, + ), + trailing: IconButton( + icon: const Icon(Icons.delete_outline), + tooltip: 'Remove', + onPressed: () { + unawaited( + ref + .read(userPreferencesRepositoryProvider) + .removeTrustedImageSender(sender), + ); + }, + ), + ); + }, + ); + }, + ), + ); + } +} diff --git a/lib/ui/screens/user_preferences_screen.dart b/lib/ui/screens/user_preferences_screen.dart index 2d960e4..b42aee2 100644 --- a/lib/ui/screens/user_preferences_screen.dart +++ b/lib/ui/screens/user_preferences_screen.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; import 'package:sharedinbox/core/models/user_preferences.dart'; import 'package:sharedinbox/core/sync/background_sync.dart'; @@ -14,6 +15,7 @@ class UserPreferencesScreen extends ConsumerWidget { Widget build(BuildContext context, WidgetRef ref) { final prefsAsync = ref.watch(userPreferencesProvider); final trustedSendersAsync = ref.watch(trustedImageSendersProvider); + final trustedCount = trustedSendersAsync.value?.length ?? 0; return Scaffold( appBar: AppBar(title: const Text('Preferences')), @@ -213,41 +215,16 @@ class UserPreferencesScreen extends ConsumerWidget { const Divider(), ListTile( title: Text( - 'Trusted image senders', + 'Allowed addresses for images', style: Theme.of(context).textTheme.titleSmall, ), - subtitle: const Text( - 'Remote images are loaded automatically for these senders.', + subtitle: Text( + trustedCount == 0 + ? 'No addresses added yet.' + : '$trustedCount address${trustedCount == 1 ? '' : 'es'}', ), - ), - ...trustedSendersAsync.when( - loading: () => const [], - error: (_, __) => const [], - data: (senders) => senders.isEmpty - ? [ - const Padding( - padding: - EdgeInsets.symmetric(horizontal: 16, vertical: 8), - child: Text('No trusted senders yet.'), - ), - ] - : [ - for (final sender in senders) - ListTile( - title: Text(sender), - trailing: IconButton( - icon: const Icon(Icons.delete_outline), - tooltip: 'Remove', - onPressed: () { - unawaited( - ref - .read(userPreferencesRepositoryProvider) - .removeTrustedImageSender(sender), - ); - }, - ), - ), - ], + trailing: const Icon(Icons.chevron_right), + onTap: () => context.push('/accounts/trusted-senders'), ), ], ), diff --git a/scripts/check_coverage.dart b/scripts/check_coverage.dart index c1a76de..924a0a0 100644 --- a/scripts/check_coverage.dart +++ b/scripts/check_coverage.dart @@ -81,6 +81,7 @@ const _excluded = { 'lib/data/repositories/user_preferences_repository_impl.dart', 'lib/ui/screens/user_preferences_screen.dart', 'lib/core/services/update_service.dart', + 'lib/ui/screens/trusted_image_senders_screen.dart', }; void main() {