## Summary - Tapping a row in the Undo Log list opens a new `UndoLogDetailScreen` - Detail screen shows: account ID, action type (with icon/colour), timestamp, source folder, destination folder (move only), and a list of all emails in the transaction (subject + sender) - Navigation uses go_router nested route `/accounts/undo-log/:actionId` with `state.extra` to pass the `UndoAction` object - AppBar has an **Undo** button that calls the existing undo service and pops back ## Also fixed - `flake.nix`: replaced the broken dagger/nix 0.20.8 Nix wrapper (infinite self-exec loop) with a direct 0.21.4 `fetchurl` derivation; wired `DAGGER_HOST` so the pre-commit `dart-check` hook can reach the running engine - `pubspec.lock`: bumped `meta` 1.17→1.18 and `test` 1.30→1.31 to match what the CI resolver picks up (eliminates spurious generated-files drift in CI) ## Verification - `task test` — all 492 unit/widget tests pass - `dart analyze --fatal-infos` — clean (no warnings or infos) - Pre-commit hooks (including `dart-check` via Dagger) — all passed on commit Closes #450 Co-authored-by: Thomas SharedInbox <sharedinbox@thomas-guettler.de> Reviewed-on: https://codeberg.org/guettli/sharedinbox/pulls/461
140 lines
4.4 KiB
Dart
140 lines
4.4 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:intl/intl.dart';
|
|
import 'package:sharedinbox/core/models/email.dart';
|
|
import 'package:sharedinbox/core/models/undo_action.dart';
|
|
import 'package:sharedinbox/di.dart';
|
|
|
|
final _dateTimeFmt = DateFormat('yyyy-MM-dd HH:mm:ss');
|
|
|
|
class UndoLogDetailScreen extends ConsumerWidget {
|
|
const UndoLogDetailScreen({super.key, required this.action});
|
|
|
|
final UndoAction action;
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final theme = Theme.of(context);
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text('Undo Log Detail'),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () async {
|
|
await ref
|
|
.read(undoServiceProvider.notifier)
|
|
.undo(actionId: action.id);
|
|
if (context.mounted) {
|
|
Navigator.of(context).pop();
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(
|
|
duration: Duration(seconds: 5),
|
|
content: Text('Action undone.'),
|
|
),
|
|
);
|
|
}
|
|
},
|
|
child: const Text('Undo'),
|
|
),
|
|
],
|
|
),
|
|
body: ListView(
|
|
children: [
|
|
_SectionHeader(text: 'Transaction', theme: theme),
|
|
ListTile(
|
|
leading: const Icon(Icons.account_circle),
|
|
title: const Text('Account'),
|
|
subtitle: Text(action.accountId),
|
|
),
|
|
ListTile(
|
|
leading: Icon(
|
|
action.type == UndoType.delete
|
|
? Icons.delete_outline
|
|
: (action.type == UndoType.snooze
|
|
? Icons.access_time
|
|
: Icons.move_to_inbox),
|
|
color: action.type == UndoType.delete
|
|
? Colors.redAccent
|
|
: (action.type == UndoType.snooze
|
|
? Colors.orangeAccent
|
|
: Colors.blueAccent),
|
|
),
|
|
title: const Text('Action'),
|
|
subtitle: Text(action.type.name.toUpperCase()),
|
|
),
|
|
ListTile(
|
|
leading: const Icon(Icons.schedule),
|
|
title: const Text('Timestamp'),
|
|
subtitle: Text(_dateTimeFmt.format(action.timestamp.toLocal())),
|
|
),
|
|
_SectionHeader(text: 'Folders', theme: theme),
|
|
ListTile(
|
|
leading: const Icon(Icons.folder_open),
|
|
title: const Text('Source'),
|
|
subtitle: Text(action.sourceMailboxPath),
|
|
),
|
|
if (action.type == UndoType.move &&
|
|
action.destinationMailboxPath != null)
|
|
ListTile(
|
|
leading: const Icon(Icons.drive_file_move),
|
|
title: const Text('Destination'),
|
|
subtitle: Text(action.destinationMailboxPath!),
|
|
),
|
|
_SectionHeader(
|
|
text: 'Emails (${action.emailIds.length})',
|
|
theme: theme,
|
|
),
|
|
if (action.originalEmails.isEmpty)
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
child: Text(
|
|
'${action.emailIds.length} email(s) — details not available',
|
|
style: theme.textTheme.bodySmall,
|
|
),
|
|
),
|
|
...action.originalEmails.map((email) => _EmailTile(email: email)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SectionHeader extends StatelessWidget {
|
|
const _SectionHeader({required this.text, required this.theme});
|
|
|
|
final String text;
|
|
final ThemeData theme;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
|
|
child: Text(
|
|
text,
|
|
style: theme.textTheme.labelLarge?.copyWith(
|
|
color: theme.colorScheme.primary,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _EmailTile extends StatelessWidget {
|
|
const _EmailTile({required this.email});
|
|
|
|
final Email email;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final sender = email.from.isNotEmpty
|
|
? (email.from.first.name ?? email.from.first.email)
|
|
: '(Unknown Sender)';
|
|
return ListTile(
|
|
leading: const Icon(Icons.email_outlined),
|
|
title: Text(email.subject ?? '(No Subject)'),
|
|
subtitle: Text(sender, maxLines: 1, overflow: TextOverflow.ellipsis),
|
|
);
|
|
}
|
|
}
|