Files
sharedinbox/lib/ui/screens/address_emails_screen.dart
T
Claude CodeandClaude Opus 4.7 ee14b88bc4 refactor(ui): unify email-list code across folder, combined inbox, search
Closes #533.

Pull selection, swipe, pagination and batch actions out of three
near-duplicate screens (EmailListScreen, CombinedInboxScreen,
AddressEmailsScreen) into a single shared widget. Folder view, combined
inbox, in-folder search results and by-address lists now share one tile
renderer, one selection controller and one batch-action bottom bar.

- New EmailThreadList widget + EmailThreadListController own the
  list rendering, selection set, optional swipe-to-archive/delete and
  optional pagination. Hosts listen to the controller to swap between
  their normal AppBar/drawer/FAB and the shared selection AppBar /
  BottomAppBar (buildSelectionAppBar, buildSelectionBottomBar).
- Batch actions (batchArchive, batchDelete, batchMarkSpam,
  batchMove, batchSnooze) and swipeDismissThread move to
  email_action_helpers.dart and group threads by account so multi-
  account selections produce correctly scoped repository calls and undo
  actions. The combined inbox now supports the full action set (was
  archive + delete only).
- The duplicate EmailThreadTile widget is removed; ThreadTile is the
  single tile used everywhere. Search results now render with the same
  unread/flag icons as the inbox list.
- AddressEmailsScreen adopts the shared list, gaining selection +
  batch actions for free.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-06-10 12:59:08 +00:00

96 lines
2.4 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:sharedinbox/core/models/email.dart';
import 'package:sharedinbox/di.dart';
import 'package:sharedinbox/ui/widgets/email_thread_list.dart';
class AddressEmailsScreen extends ConsumerStatefulWidget {
const AddressEmailsScreen({
super.key,
required this.accountId,
required this.address,
});
final String accountId;
final String address;
@override
ConsumerState<AddressEmailsScreen> createState() =>
_AddressEmailsScreenState();
}
class _AddressEmailsScreenState extends ConsumerState<AddressEmailsScreen> {
List<Email>? _emails;
bool _loading = true;
late final EmailThreadListController _selection;
@override
void initState() {
super.initState();
_selection = EmailThreadListController()..addListener(_onSelectionChange);
unawaited(_load());
}
@override
void dispose() {
_selection
..removeListener(_onSelectionChange)
..dispose();
super.dispose();
}
void _onSelectionChange() {
if (mounted) setState(() {});
}
Future<void> _load() async {
final emails = await ref
.read(emailRepositoryProvider)
.getEmailsByAddress(widget.accountId, widget.address);
if (mounted) {
setState(() {
_emails = emails;
_loading = false;
});
}
}
@override
Widget build(BuildContext context) {
final selecting = _selection.isSelecting;
return Scaffold(
appBar: selecting
? buildSelectionAppBar(_selection)
: AppBar(title: Text(widget.address)),
bottomNavigationBar: selecting
? buildSelectionBottomBar(
context,
ref,
_selection,
onAfterAction: _onAfterBatchAction,
)
: null,
body: _loading
? const Center(child: CircularProgressIndicator())
: EmailThreadList(
controller: _selection,
items: _emails!.map(EmailThread.fromEmail).toList(),
enableSwipe: false,
showLocationLabel: true,
),
);
}
void _onAfterBatchAction(List<String> actedThreadIds) {
if (_emails == null || !mounted) return;
final actedSet = actedThreadIds.toSet();
final remaining =
_emails!.where((e) => !actedSet.contains(e.threadId ?? e.id)).toList();
setState(() => _emails = remaining);
}
}