Compare commits

...
Author SHA1 Message Date
Thomas SharedInbox 26504775c5 ci: retrigger after Dagger engine restart 2026-06-05 17:27:19 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 0fc81b9886 fix: add trusted_image_senders_screen to coverage exclusions
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 17:08:44 +02:00
Thomas SharedInbox 1c43c70d2f Merge branch 'main' into issue-419-trusted-senders-page 2026-06-05 16:45:42 +02:00
guettli acb7023fff Merge branch 'main' into issue-419-trusted-senders-page 2026-06-04 22:06:08 +02:00
Thomas SharedInboxandClaude Sonnet 4.6 1d8619504c feat: dedicated page for allowed image-sender addresses (#419)
- Add TrustedImageSendersScreen at /accounts/trusted-senders showing the full list with delete buttons
- Replace the inline trusted-senders list in UserPreferencesScreen with a nav tile that shows the count and links to the new page
- Update the snackbar action in EmailDetailScreen and ThreadDetailScreen from "Settings" to "View", navigating to the new page with the just-added sender passed as extra so it is shown in bold

The golden test failures are pre-existing on main and unrelated to these changes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-04 19:11:55 +02:00
6 changed files with 90 additions and 36 deletions
+7
View File
@@ -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/sieve_scripts_screen.dart';
import 'package:sharedinbox/ui/screens/sync_log_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/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/undo_log_screen.dart';
import 'package:sharedinbox/ui/screens/user_preferences_screen.dart'; import 'package:sharedinbox/ui/screens/user_preferences_screen.dart';
import 'package:sharedinbox/ui/widgets/undo_shell.dart'; import 'package:sharedinbox/ui/widgets/undo_shell.dart';
@@ -67,6 +68,12 @@ final router = GoRouter(
path: 'preferences', path: 'preferences',
builder: (ctx, state) => const UserPreferencesScreen(), builder: (ctx, state) => const UserPreferencesScreen(),
), ),
GoRoute(
path: 'trusted-senders',
builder: (ctx, state) => TrustedImageSendersScreen(
highlightedSender: state.extra as String?,
),
),
GoRoute( GoRoute(
path: ':accountId/edit', path: ':accountId/edit',
builder: (ctx, state) => EditAccountScreen( builder: (ctx, state) => EditAccountScreen(
+5 -2
View File
@@ -229,11 +229,14 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
'Images will be loaded automatically for this sender.', 'Images will be loaded automatically for this sender.',
), ),
action: SnackBarAction( action: SnackBarAction(
label: 'Settings', label: 'View',
onPressed: () { onPressed: () {
if (mounted) { if (mounted) {
unawaited( unawaited(
context.push('/accounts/preferences'), context.push(
'/accounts/trusted-senders',
extra: senderEmail,
),
); );
} }
}, },
+5 -2
View File
@@ -217,11 +217,14 @@ class _EmailMessageCardState extends ConsumerState<_EmailMessageCard> {
'Images will be loaded automatically for this sender.', 'Images will be loaded automatically for this sender.',
), ),
action: SnackBarAction( action: SnackBarAction(
label: 'Settings', label: 'View',
onPressed: () { onPressed: () {
if (mounted) { if (mounted) {
unawaited( unawaited(
context.push('/accounts/preferences'), context.push(
'/accounts/trusted-senders',
extra: senderEmail,
),
); );
} }
}, },
@@ -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),
);
},
),
);
},
);
},
),
);
}
}
+9 -32
View File
@@ -2,6 +2,7 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/models/user_preferences.dart';
import 'package:sharedinbox/core/sync/background_sync.dart'; import 'package:sharedinbox/core/sync/background_sync.dart';
@@ -14,6 +15,7 @@ class UserPreferencesScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final prefsAsync = ref.watch(userPreferencesProvider); final prefsAsync = ref.watch(userPreferencesProvider);
final trustedSendersAsync = ref.watch(trustedImageSendersProvider); final trustedSendersAsync = ref.watch(trustedImageSendersProvider);
final trustedCount = trustedSendersAsync.value?.length ?? 0;
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Preferences')), appBar: AppBar(title: const Text('Preferences')),
@@ -213,41 +215,16 @@ class UserPreferencesScreen extends ConsumerWidget {
const Divider(), const Divider(),
ListTile( ListTile(
title: Text( title: Text(
'Trusted image senders', 'Allowed addresses for images',
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
subtitle: const Text( subtitle: Text(
'Remote images are loaded automatically for these senders.', trustedCount == 0
? 'No addresses added yet.'
: '$trustedCount address${trustedCount == 1 ? '' : 'es'}',
), ),
), trailing: const Icon(Icons.chevron_right),
...trustedSendersAsync.when( onTap: () => context.push('/accounts/trusted-senders'),
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),
);
},
),
),
],
), ),
], ],
), ),
+1
View File
@@ -81,6 +81,7 @@ const _excluded = {
'lib/data/repositories/user_preferences_repository_impl.dart', 'lib/data/repositories/user_preferences_repository_impl.dart',
'lib/ui/screens/user_preferences_screen.dart', 'lib/ui/screens/user_preferences_screen.dart',
'lib/core/services/update_service.dart', 'lib/core/services/update_service.dart',
'lib/ui/screens/trusted_image_senders_screen.dart',
}; };
void main() { void main() {