filter thread list by selected mailbox, fix sidebar selection
- threadSummariesFromDB accepts optional mailboxId filter, uses EXISTS subquery to show only threads containing messages in the selected mailbox - add selectMailbox() and selectPerspective() to MailViewModel for clean state transitions between folder view and GTD perspective view - sidebar highlights selected mailbox/perspective, clears state on switch - default to INBOX mailbox after first sync Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -168,12 +168,12 @@ final class MailViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
func startObservingThreads(accountId: String) {
|
||||
func startObservingThreads(accountId: String, mailboxId: String? = nil) {
|
||||
guard let store else { return }
|
||||
threadObservation?.cancel()
|
||||
threadObservation = Task {
|
||||
do {
|
||||
for try await summaries in store.observeThreadSummaries(accountId: accountId) {
|
||||
for try await summaries in store.observeThreadSummaries(accountId: accountId, mailboxId: mailboxId) {
|
||||
self.threads = summaries
|
||||
}
|
||||
} catch {
|
||||
@@ -184,6 +184,22 @@ final class MailViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
func selectMailbox(_ mailbox: MailboxInfo) {
|
||||
selectedMailbox = mailbox
|
||||
selectedItem = nil
|
||||
items = []
|
||||
guard let accountConfig else { return }
|
||||
startObservingThreads(accountId: accountConfig.id, mailboxId: mailbox.id)
|
||||
}
|
||||
|
||||
func selectPerspective(_ perspective: Perspective) {
|
||||
selectedMailbox = nil
|
||||
selectedThread = nil
|
||||
messages = []
|
||||
loadPerspective(perspective)
|
||||
loadAllPerspectiveCounts()
|
||||
}
|
||||
|
||||
func selectThread(_ thread: ThreadSummary) {
|
||||
selectedThread = thread
|
||||
messageObservation?.cancel()
|
||||
@@ -231,6 +247,10 @@ final class MailViewModel {
|
||||
// Reload mailboxes after sync (they may have been created on first sync)
|
||||
if let accountConfig {
|
||||
await loadMailboxes(accountId: accountConfig.id)
|
||||
// On first sync, default to INBOX view
|
||||
if selectedMailbox == nil, let inbox = mailboxes.first(where: { $0.name.lowercased() == "inbox" }) {
|
||||
selectMailbox(inbox)
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
let desc = "\(error)"
|
||||
|
||||
@@ -9,26 +9,28 @@ struct SidebarView: View {
|
||||
Section("Perspectives") {
|
||||
ForEach(Perspective.allCases) { perspective in
|
||||
Button {
|
||||
viewModel.loadPerspective(perspective)
|
||||
viewModel.selectPerspective(perspective)
|
||||
} label: {
|
||||
Label(perspective.label, systemImage: perspective.systemImage)
|
||||
.badge(viewModel.perspectiveCounts[perspective] ?? 0)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.listItemTint(viewModel.selectedPerspective == perspective ? .accentColor : nil)
|
||||
.fontWeight(viewModel.selectedPerspective == perspective ? .semibold : .regular)
|
||||
.listItemTint(viewModel.selectedPerspective == perspective && viewModel.selectedMailbox == nil ? .accentColor : nil)
|
||||
.fontWeight(viewModel.selectedPerspective == perspective && viewModel.selectedMailbox == nil ? .semibold : .regular)
|
||||
}
|
||||
}
|
||||
|
||||
Section("Mailboxes") {
|
||||
ForEach(viewModel.mailboxes) { mailbox in
|
||||
Button {
|
||||
viewModel.selectedMailbox = mailbox
|
||||
viewModel.selectMailbox(mailbox)
|
||||
} label: {
|
||||
Label(mailbox.name, systemImage: mailbox.systemImage)
|
||||
.badge(mailbox.unreadCount)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.listItemTint(viewModel.selectedMailbox?.id == mailbox.id ? .accentColor : nil)
|
||||
.fontWeight(viewModel.selectedMailbox?.id == mailbox.id ? .semibold : .regular)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,16 @@ extension MailStore {
|
||||
}
|
||||
|
||||
/// Thread summaries with unread count and latest sender info, ordered by lastDate DESC
|
||||
public func threadSummaries(accountId: String) throws -> [ThreadSummary] {
|
||||
public func threadSummaries(accountId: String, mailboxId: String? = nil) throws -> [ThreadSummary] {
|
||||
try dbWriter.read { db in
|
||||
try Self.threadSummariesFromDB(db, accountId: accountId)
|
||||
try Self.threadSummariesFromDB(db, accountId: accountId, mailboxId: mailboxId)
|
||||
}
|
||||
}
|
||||
|
||||
/// Observe thread summaries reactively — UI updates automatically on DB change.
|
||||
public func observeThreadSummaries(accountId: String) -> AsyncValueObservation<[ThreadSummary]> {
|
||||
public func observeThreadSummaries(accountId: String, mailboxId: String? = nil) -> AsyncValueObservation<[ThreadSummary]> {
|
||||
ValueObservation.tracking { db -> [ThreadSummary] in
|
||||
try Self.threadSummariesFromDB(db, accountId: accountId)
|
||||
try Self.threadSummariesFromDB(db, accountId: accountId, mailboxId: mailboxId)
|
||||
}.values(in: dbWriter)
|
||||
}
|
||||
|
||||
@@ -44,7 +44,19 @@ extension MailStore {
|
||||
|
||||
// MARK: - Internal helpers
|
||||
|
||||
static func threadSummariesFromDB(_ db: Database, accountId: String) throws -> [ThreadSummary] {
|
||||
static func threadSummariesFromDB(_ db: Database, accountId: String, mailboxId: String? = nil) throws -> [ThreadSummary] {
|
||||
let mailboxFilter: String
|
||||
let arguments: StatementArguments
|
||||
|
||||
if let mailboxId {
|
||||
// Filter threads to only those containing messages in the specified mailbox
|
||||
mailboxFilter = "AND EXISTS (SELECT 1 FROM threadMessage tm2 JOIN message m2 ON m2.id = tm2.messageId WHERE tm2.threadId = t.id AND m2.mailboxId = ?)"
|
||||
arguments = [accountId, mailboxId]
|
||||
} else {
|
||||
mailboxFilter = ""
|
||||
arguments = [accountId]
|
||||
}
|
||||
|
||||
let sql = """
|
||||
SELECT
|
||||
t.id, t.accountId, t.subject, t.lastDate, t.messageCount,
|
||||
@@ -53,10 +65,10 @@ extension MailStore {
|
||||
(SELECT m.snippet FROM threadMessage tm JOIN message m ON m.id = tm.messageId WHERE tm.threadId = t.id ORDER BY m.date DESC LIMIT 1) as snippet,
|
||||
(SELECT COUNT(*) FROM attachment a JOIN threadMessage tm ON tm.messageId = a.messageId WHERE tm.threadId = t.id) as attachmentCount
|
||||
FROM thread t
|
||||
WHERE t.accountId = ?
|
||||
WHERE t.accountId = ? \(mailboxFilter)
|
||||
ORDER BY t.lastDate DESC
|
||||
"""
|
||||
let rows = try Row.fetchAll(db, sql: sql, arguments: [accountId])
|
||||
let rows = try Row.fetchAll(db, sql: sql, arguments: arguments)
|
||||
return rows.map { row in
|
||||
ThreadSummary(
|
||||
id: row["id"],
|
||||
|
||||
Reference in New Issue
Block a user