feat(detail): drop AppBar subject, surface Mark as spam icon (#531)
## Summary - Drop the truncated subject preview from the single-mail AppBar title; the full subject is already shown in the body header. - Replace the popup-menu entry for **Mark as spam** with a direct `IconButton` (`Icons.report_outlined`) in the AppBar actions so the action is reachable without opening the `⋯` menu. - Update affected widget tests for the new layout (subject is only in the body header; spam action is now a standalone button rather than a popup item). Closes #528 ## Test plan - [x] `dart format --output=none --set-exit-if-changed lib test` — 0 changed - [x] `dart analyze --fatal-infos lib test` — no issues - [x] `flutter test test/widget/email_detail_screen_test.dart test/widget/email_list_screen_test.dart` — 42/42 passing - [x] Full widget suite (`flutter test test/widget/`) — 172/172 passing Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/531
This commit was merged in pull request #531.
This commit is contained in:
committed by
guettli
co-authored by
guettli
parent
38f7ada8b5
commit
41c8196a97
@@ -74,10 +74,6 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
automaticallyImplyLeading: !isMobile,
|
automaticallyImplyLeading: !isMobile,
|
||||||
title: Text(
|
|
||||||
header?.subject ?? '(loading…)',
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
),
|
|
||||||
actions: [
|
actions: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.reply),
|
icon: const Icon(Icons.reply),
|
||||||
@@ -133,12 +129,20 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
if (mounted) setState(() => _isFlagged = next);
|
if (mounted) setState(() => _isFlagged = next);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.report_outlined),
|
||||||
|
tooltip: 'Mark as spam',
|
||||||
|
onPressed: header == null
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
unawaited(_markAsSpam(context, header));
|
||||||
|
},
|
||||||
|
),
|
||||||
PopupMenuButton<String>(
|
PopupMenuButton<String>(
|
||||||
itemBuilder: (ctx) => [
|
itemBuilder: (ctx) => [
|
||||||
const PopupMenuItem(value: 'forward', child: Text('Forward')),
|
const PopupMenuItem(value: 'forward', child: Text('Forward')),
|
||||||
const PopupMenuItem(value: 'move', child: Text('Move to folder')),
|
const PopupMenuItem(value: 'move', child: Text('Move to folder')),
|
||||||
const PopupMenuItem(value: 'snooze', child: Text('Snooze')),
|
const PopupMenuItem(value: 'snooze', child: Text('Snooze')),
|
||||||
const PopupMenuItem(value: 'spam', child: Text('Mark as spam')),
|
|
||||||
const PopupMenuItem(
|
const PopupMenuItem(
|
||||||
value: 'mark_unread',
|
value: 'mark_unread',
|
||||||
child: Text('Mark as unread'),
|
child: Text('Mark as unread'),
|
||||||
@@ -166,8 +170,6 @@ class _EmailDetailScreenState extends ConsumerState<EmailDetailScreen> {
|
|||||||
unawaited(_moveTo(context, header));
|
unawaited(_moveTo(context, header));
|
||||||
} else if (value == 'snooze' && header != null) {
|
} else if (value == 'snooze' && header != null) {
|
||||||
unawaited(_snooze(context, header));
|
unawaited(_snooze(context, header));
|
||||||
} else if (value == 'spam' && header != null) {
|
|
||||||
unawaited(_markAsSpam(context, header));
|
|
||||||
} else if (value == 'mark_unread') {
|
} else if (value == 'mark_unread') {
|
||||||
final nextEmailId = await _getNextEmailIdIfNeeded(header);
|
final nextEmailId = await _getNextEmailIdIfNeeded(header);
|
||||||
await repo.setFlag(widget.emailId, seen: false);
|
await repo.setFlag(widget.emailId, seen: false);
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ void main() {
|
|||||||
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
expect(find.byType(CircularProgressIndicator), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('shows subject in app bar after data loads', (tester) async {
|
testWidgets('shows subject in email header section', (tester) async {
|
||||||
final email = testEmail(subject: 'Project update');
|
final email = testEmail(subject: 'Project update');
|
||||||
const body = EmailBody(
|
const body = EmailBody(
|
||||||
emailId: 'acc-1:42',
|
emailId: 'acc-1:42',
|
||||||
@@ -106,8 +106,8 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Subject appears in both the app bar and the email header section.
|
// Subject appears only in the email header section, not in the app bar.
|
||||||
expect(find.text('Project update'), findsAtLeastNWidgets(1));
|
expect(find.text('Project update'), findsOneWidget);
|
||||||
expect(find.text('See attached slides.'), findsOneWidget);
|
expect(find.text('See attached slides.'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -266,7 +266,7 @@ void main() {
|
|||||||
expect(find.textContaining('carol@example.com'), findsAtLeastNWidgets(1));
|
expect(find.textContaining('carol@example.com'), findsAtLeastNWidgets(1));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Mark as spam is in popup menu, not a standalone button', (
|
testWidgets('Mark as spam is a standalone button, not in popup menu', (
|
||||||
tester,
|
tester,
|
||||||
) async {
|
) async {
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@@ -279,19 +279,19 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// No standalone icon button for mark as spam.
|
// Standalone icon button for mark as spam is in the app bar.
|
||||||
expect(
|
expect(
|
||||||
find.byWidgetPredicate(
|
find.byWidgetPredicate(
|
||||||
(w) => w is Tooltip && w.message == 'Mark as spam',
|
(w) => w is Tooltip && w.message == 'Mark as spam',
|
||||||
),
|
),
|
||||||
findsNothing,
|
findsOneWidget,
|
||||||
);
|
);
|
||||||
|
|
||||||
// It appears in the popup menu.
|
// It does NOT appear in the popup menu.
|
||||||
await tester.tap(find.byType(PopupMenuButton<String>));
|
await tester.tap(find.byType(PopupMenuButton<String>));
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('Mark as spam'), findsOneWidget);
|
expect(find.text('Mark as spam'), findsNothing);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Mark as spam shows dialog when no junk folder', (
|
testWidgets('Mark as spam shows dialog when no junk folder', (
|
||||||
@@ -309,11 +309,11 @@ void main() {
|
|||||||
);
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
// Open the popup menu first, then tap Mark as spam.
|
await tester.tap(
|
||||||
await tester.tap(find.byType(PopupMenuButton<String>));
|
find.byWidgetPredicate(
|
||||||
await tester.pumpAndSettle();
|
(w) => w is Tooltip && w.message == 'Mark as spam',
|
||||||
|
),
|
||||||
await tester.tap(find.text('Mark as spam'));
|
);
|
||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.text('No spam folder found'), findsOneWidget);
|
expect(find.text('No spam folder found'), findsOneWidget);
|
||||||
|
|||||||
@@ -446,10 +446,10 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
expect(find.byType(EmailDetailScreen), findsOneWidget);
|
expect(find.byType(EmailDetailScreen), findsOneWidget);
|
||||||
// The detail AppBar title shows the first email's subject.
|
// The detail body header shows the first email's subject.
|
||||||
expect(
|
expect(
|
||||||
find.descendant(
|
find.descendant(
|
||||||
of: find.byType(AppBar),
|
of: find.byType(EmailDetailScreen),
|
||||||
matching: find.text('Alpha Match'),
|
matching: find.text('Alpha Match'),
|
||||||
),
|
),
|
||||||
findsOneWidget,
|
findsOneWidget,
|
||||||
|
|||||||
Reference in New Issue
Block a user