AppBar switches to "N selected" + close + select-all
Batch Archive and Delete via bottom action bar
Cross-account batch archive groups threads by accountId to resolve the correct archive folder per account
Compose FAB and drawer are hidden while selecting
EmailListScreen — refactored to use EmailThreadTile
Swipe handler extracted into _onSwipeDismissed() method (was an inline closure)
Duplicate date-formatting helpers removed (now live in email_thread_tile.dart)
All existing behaviour preserved
SearchScreen — no changes
Serves a different purpose (cross-entity discovery across folders, addresses, and emails). Its email section already reuses the shared EmailTile widget.
Verification
flutter analyze — no issues
flutter test test/unit/ test/widget/ — 492 tests pass
Golden files regenerated to match the refactored widget output
Closes #424
## What changed
### New widget: `lib/ui/widgets/email_thread_tile.dart`
A shared `EmailThreadTile` that handles:
- `Dismissible` swipe gestures (archive left, delete right)
- Selection-mode checkbox (when `isSelecting` is true, swipe is disabled)
- Optional account-name subtitle line (`showAccount` / `accountName`) for combined-inbox use
This eliminates ~150 lines of duplicated thread-tile rendering code that existed separately in both `EmailListScreen` and `CombinedInboxScreen`.
### `CombinedInboxScreen` — added multi-select + batch ops
- Long-press a thread to enter selection mode
- AppBar switches to "N selected" + close + select-all
- Batch Archive and Delete via bottom action bar
- Cross-account batch archive groups threads by `accountId` to resolve the correct archive folder per account
- Compose FAB and drawer are hidden while selecting
### `EmailListScreen` — refactored to use `EmailThreadTile`
- Swipe handler extracted into `_onSwipeDismissed()` method (was an inline closure)
- Duplicate date-formatting helpers removed (now live in `email_thread_tile.dart`)
- All existing behaviour preserved
### `SearchScreen` — no changes
Serves a different purpose (cross-entity discovery across folders, addresses, and emails). Its email section already reuses the shared `EmailTile` widget.
## Verification
- `flutter analyze` — no issues
- `flutter test test/unit/ test/widget/` — 492 tests pass
- Golden files regenerated to match the refactored widget output
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #424
What changed
New widget:
lib/ui/widgets/email_thread_tile.dartA shared
EmailThreadTilethat handles:Dismissibleswipe gestures (archive left, delete right)isSelectingis true, swipe is disabled)showAccount/accountName) for combined-inbox useThis eliminates ~150 lines of duplicated thread-tile rendering code that existed separately in both
EmailListScreenandCombinedInboxScreen.CombinedInboxScreen— added multi-select + batch opsaccountIdto resolve the correct archive folder per accountEmailListScreen— refactored to useEmailThreadTile_onSwipeDismissed()method (was an inline closure)email_thread_tile.dart)SearchScreen— no changesServes a different purpose (cross-entity discovery across folders, addresses, and emails). Its email section already reuses the shared
EmailTilewidget.Verification
flutter analyze— no issuesflutter test test/unit/ test/widget/— 492 tests pass