Combined view is different #424

Closed
opened 2026-06-04 17:34:03 +00:00 by guettli · 2 comments
guettli commented 2026-06-04 17:34:03 +00:00 (Migrated from codeberg.org)

The combined inbox view is different.

For example I can't select multiple mails in the combined view.

Is that what we want?

I think it would be great when all email list views share the same code:

  • Folder view
  • search result view
  • combined inbox view.

Create a plan if it makes sense to do it like that.

The combined inbox view is different. For example I can't select multiple mails in the combined view. Is that what we want? I think it would be great when all email list views share the same code: - Folder view - search result view - combined inbox view. Create a plan if it makes sense to do it like that.
guettlibot commented 2026-06-04 17:40:15 +00:00 (Migrated from codeberg.org)

Now I have a complete picture. Let me write the plan.


Implementation Plan for Issue #424: Unify email list views

Scope decision

The three views differ significantly in purpose:

  • EmailListScreen — full-featured: threads, multi-select, batch ops, inline folder search, swipe gestures
  • CombinedInboxScreen — same thread-list concept, but missing multi-select/batch ops; has ~150 lines of duplicated thread-tile rendering code
  • SearchScreen — different concept: cross-entity discovery (folders + addresses + emails), one-shot queries, individual emails not threads; already reuses EmailTile

Conclusion: Fully unify EmailListScreen and CombinedInboxScreen by extracting a shared EmailThreadTile widget and adding multi-select + batch actions to CombinedInboxScreen. Leave SearchScreen out of this change — it serves a different UX purpose and already reuses EmailTile.


Step 1 — Extract EmailThreadTile widget

Create lib/ui/widgets/email_thread_tile.dart.

Parameters:

EmailThread thread
bool isSelected
bool isSelecting
bool showAccount         // extra account-name subtitle line (combined inbox only)
String? accountName
VoidCallback onTap
VoidCallback onLongPress
Future<void> Function(DismissDirection) onDismissed
  • Move the ListTile + Dismissible rendering from CombinedInboxScreen._buildThreadTile into this widget (it already has the showAccount subtitle line, so it's the more general version).
  • Move _swipeBackground here as a private helper.
  • The Dismissible direction becomes DismissDirection.none when isSelecting is true (same guard EmailListScreen already uses).
  • The _dateFmt / _formattedDates / _dayKey / _fmtDate date-caching helpers (currently duplicated in both screen files) move here and are removed from the screen files.

Step 2 — Refactor EmailListScreen to use EmailThreadTile

In lib/ui/screens/email_list_screen.dart, replace the inline ListTile+Dismissible block inside _buildThreadList with:

EmailThreadTile(
  thread: t,
  isSelected: _selectedThreadIds.contains(t.threadId),
  isSelecting: _selecting,
  showAccount: false,
  onTap: ...,
  onLongPress: () => _toggleThreadSelection(t),
  onDismissed: (dir) => _onSwipeDismissed(t, dir),
)

Extract the swipe archive/delete logic from the Dismissible.onDismissed callback into a private _onSwipeDismissed(EmailThread t, DismissDirection dir) method (mirrors the pattern already in CombinedInboxScreen).

Step 3 — Add multi-select and batch ops to CombinedInboxScreen

In lib/ui/screens/combined_inbox_screen.dart:

State additions (same pattern as EmailListScreen):

final Set<String> _selectedThreadIds = {};
List<EmailThread> _currentThreads = [];
bool get _selecting => _selectedThreadIds.isNotEmpty;

Methods to add:

  • _toggleThreadSelection(EmailThread t)
  • _clearSelection()
  • _selectAll() — uses _currentThreads
  • _selectedEmailIds getter — expands selected threads to email ID lists

AppBar changes: When _selecting, show close button + "N selected" title + select-all action (identical pattern to EmailListScreen).

Batch action bar: _selectionBottomBar() with Archive and Delete. Skip Move-to-folder and Mark-as-spam for now — those require per-account folder resolution for cross-account selections and can be added later.

_buildThreadList update: Store emitted threads into _currentThreads, then render with EmailThreadTile(isSelecting: _selecting, onLongPress: ..., ...).

Files to change

File Action
lib/ui/widgets/email_thread_tile.dart Create — shared tile widget
lib/ui/screens/email_list_screen.dart Modify — use EmailThreadTile, extract swipe handler
lib/ui/screens/combined_inbox_screen.dart Modify — use EmailThreadTile, add multi-select + batch archive/delete

No schema changes; no build_runner run needed.

Out of scope

  • SearchScreen — no changes
  • Move-to-folder and Mark-as-spam in combined inbox — cross-account folder resolution adds complexity; defer to a follow-up issue
  • AddressEmailsScreen — read-only list, no batch ops make sense there
Now I have a complete picture. Let me write the plan. --- ## Implementation Plan for Issue #424: Unify email list views ### Scope decision The three views differ significantly in purpose: - **`EmailListScreen`** — full-featured: threads, multi-select, batch ops, inline folder search, swipe gestures - **`CombinedInboxScreen`** — same thread-list concept, but missing multi-select/batch ops; has ~150 lines of duplicated thread-tile rendering code - **`SearchScreen`** — different concept: cross-entity discovery (folders + addresses + emails), one-shot queries, individual emails not threads; already reuses `EmailTile` **Conclusion:** Fully unify `EmailListScreen` and `CombinedInboxScreen` by extracting a shared `EmailThreadTile` widget and adding multi-select + batch actions to `CombinedInboxScreen`. Leave `SearchScreen` out of this change — it serves a different UX purpose and already reuses `EmailTile`. --- ### Step 1 — Extract `EmailThreadTile` widget Create `lib/ui/widgets/email_thread_tile.dart`. Parameters: ```dart EmailThread thread bool isSelected bool isSelecting bool showAccount // extra account-name subtitle line (combined inbox only) String? accountName VoidCallback onTap VoidCallback onLongPress Future<void> Function(DismissDirection) onDismissed ``` - Move the `ListTile` + `Dismissible` rendering from `CombinedInboxScreen._buildThreadTile` into this widget (it already has the `showAccount` subtitle line, so it's the more general version). - Move `_swipeBackground` here as a private helper. - The `Dismissible` direction becomes `DismissDirection.none` when `isSelecting` is true (same guard `EmailListScreen` already uses). - The `_dateFmt` / `_formattedDates` / `_dayKey` / `_fmtDate` date-caching helpers (currently duplicated in both screen files) move here and are removed from the screen files. ### Step 2 — Refactor `EmailListScreen` to use `EmailThreadTile` In `lib/ui/screens/email_list_screen.dart`, replace the inline `ListTile`+`Dismissible` block inside `_buildThreadList` with: ```dart EmailThreadTile( thread: t, isSelected: _selectedThreadIds.contains(t.threadId), isSelecting: _selecting, showAccount: false, onTap: ..., onLongPress: () => _toggleThreadSelection(t), onDismissed: (dir) => _onSwipeDismissed(t, dir), ) ``` Extract the swipe archive/delete logic from the `Dismissible.onDismissed` callback into a private `_onSwipeDismissed(EmailThread t, DismissDirection dir)` method (mirrors the pattern already in `CombinedInboxScreen`). ### Step 3 — Add multi-select and batch ops to `CombinedInboxScreen` In `lib/ui/screens/combined_inbox_screen.dart`: **State additions** (same pattern as `EmailListScreen`): ```dart final Set<String> _selectedThreadIds = {}; List<EmailThread> _currentThreads = []; bool get _selecting => _selectedThreadIds.isNotEmpty; ``` **Methods to add:** - `_toggleThreadSelection(EmailThread t)` - `_clearSelection()` - `_selectAll()` — uses `_currentThreads` - `_selectedEmailIds` getter — expands selected threads to email ID lists **AppBar changes:** When `_selecting`, show close button + `"N selected"` title + select-all action (identical pattern to `EmailListScreen`). **Batch action bar:** `_selectionBottomBar()` with Archive and Delete. Skip Move-to-folder and Mark-as-spam for now — those require per-account folder resolution for cross-account selections and can be added later. **`_buildThreadList` update:** Store emitted threads into `_currentThreads`, then render with `EmailThreadTile(isSelecting: _selecting, onLongPress: ..., ...)`. ### Files to change | File | Action | |---|---| | `lib/ui/widgets/email_thread_tile.dart` | **Create** — shared tile widget | | `lib/ui/screens/email_list_screen.dart` | **Modify** — use `EmailThreadTile`, extract swipe handler | | `lib/ui/screens/combined_inbox_screen.dart` | **Modify** — use `EmailThreadTile`, add multi-select + batch archive/delete | No schema changes; no `build_runner` run needed. ### Out of scope - `SearchScreen` — no changes - Move-to-folder and Mark-as-spam in combined inbox — cross-account folder resolution adds complexity; defer to a follow-up issue - `AddressEmailsScreen` — read-only list, no batch ops make sense there
guettlibot commented 2026-06-04 20:40:02 +00:00 (Migrated from codeberg.org)

agentloop: agent exceeded its 30m0s runtime budget and was stopped.

agent stderr tail:

[acpx] session agentloop-sharedinbox-issue-424 (75359acd-fd4d-40a3-aa59-3838fd701572) · /home/si/agentloop/loop-data/sharedinbox/issues/424 · agent needs reconnect
agentloop: agent exceeded its 30m0s runtime budget and was stopped. agent stderr tail: ``` [acpx] session agentloop-sharedinbox-issue-424 (75359acd-fd4d-40a3-aa59-3838fd701572) · /home/si/agentloop/loop-data/sharedinbox/issues/424 · agent needs reconnect ```
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: guettli/sharedinbox#424