Align Mail UI with social.io design handoff
Rewrite the Mail feature to match the Apple-native look from the handoff spec: lane-split inbox, AI summary card, clean ThreadRow, Cc/From + format toolbar in Compose. Drop the gradient hero surfaces and blurred canvas backgrounds the spec calls out as anti-patterns, and introduce a token-backed design layer so the lane palette and SIO tint live in the asset catalog. - Add Assets.xcassets with SIOTint, LaneFeed, LanePaper, LanePeople (light + dark variants). - Add Sources/Core/Design/SIODesign.swift: SIO tokens, Lane enum, LaneChip, AvatarView, AISummaryCard, KeyboardHint, button styles, and a glass-chrome helper with iOS 26 / material fallback. - Extend MailThread with lane + summary; custom Codable keeps old payloads decodable. Seed mock threads with sensible lanes and hand-write summaries on launch-copy, investor-update, roadmap-notes. - Add lane filtering to AppViewModel (selectedLane, selectLane, laneUnreadCount, laneThreadCount). - Rewrite MailRootView end to end: sidebar with Inbox/lane rows, lane filter strip, Apple-native ThreadRow (avatar, unread dot, lane chip, summary chip), ThreadReadingView with AI summary + floating reply pill, ComposeView with To/Cc/From/Subject and a format toolbar. - Wire Assets.xcassets + SIODesign.swift into project.pbxproj. Accessibility identifiers preserved byte-identical; new ones (mailbox.lane.*, lane.chip.*) added only where new. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import Observation
|
||||
@Observable
|
||||
final class AppViewModel {
|
||||
var selectedMailbox: Mailbox = .inbox
|
||||
var selectedLane: Lane?
|
||||
var selectedThreadID: MailThread.ID?
|
||||
var focusedMessageRouteID: String?
|
||||
var searchText = ""
|
||||
@@ -44,6 +45,10 @@ final class AppViewModel {
|
||||
.filter { thread in
|
||||
selectedMailbox == .starred ? thread.isStarred : thread.mailbox == selectedMailbox
|
||||
}
|
||||
.filter { thread in
|
||||
guard let lane = selectedLane else { return true }
|
||||
return thread.lane == lane
|
||||
}
|
||||
.filter { thread in
|
||||
!showUnreadOnly || thread.isUnread
|
||||
}
|
||||
@@ -51,6 +56,27 @@ final class AppViewModel {
|
||||
.sorted { $0.lastUpdated > $1.lastUpdated }
|
||||
}
|
||||
|
||||
func laneUnreadCount(_ lane: Lane) -> Int {
|
||||
threads.filter { thread in
|
||||
let inCurrentMailbox = selectedMailbox == .starred
|
||||
? thread.isStarred
|
||||
: thread.mailbox == selectedMailbox
|
||||
return inCurrentMailbox && thread.lane == lane && thread.isUnread
|
||||
}.count
|
||||
}
|
||||
|
||||
func laneThreadCount(_ lane: Lane) -> Int {
|
||||
threads.filter { thread in
|
||||
thread.mailbox == .inbox && thread.lane == lane
|
||||
}.count
|
||||
}
|
||||
|
||||
func selectLane(_ lane: Lane?) {
|
||||
selectedLane = lane
|
||||
clearThreadSelection()
|
||||
mailboxNavigationToken = UUID()
|
||||
}
|
||||
|
||||
var totalUnreadCount: Int {
|
||||
threads.filter(\.isUnread).count
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ struct SocialIOApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
MailRootView(model: model)
|
||||
.tint(MailTheme.accent)
|
||||
.tint(SIO.tint)
|
||||
.onOpenURL { url in
|
||||
model.apply(url: url)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user