| 1 | import LChatCore |
| 2 | import SwiftUI |
| 3 | |
| 4 | // One chat row's content — extracted so the UIKit list (ThreadListCollection) can |
| 5 | // render it inside a UICollectionViewListCell via UIHostingConfiguration. Pure |
| 6 | // presentation; the cell owns the highlight/selection chrome. |
| 7 | struct ThreadRow: View { |
| 8 | let thread: ChatThread |
| 9 | |
| 10 | var body: some View { |
| 11 | HStack(spacing: 12) { |
| 12 | // The main list finally shows app identity: Eleven's own threads |
| 13 | // wear the brand tile, apps their mark (docs/icons.md), and the |
| 14 | // emoji glyph stays the fallback for anything unmapped. |
| 15 | Avatar(name: thread.title, size: 44, source: thread.avatar, |
| 16 | photo: thread.isEleven ? ElevenBrand.icon : nil, |
| 17 | glyph: thread.glyph, |
| 18 | symbol: AppIdentity.symbol(for: thread.appId), |
| 19 | mark: AppIdentity.mark(for: thread.appId)) |
| 20 | VStack(alignment: .leading, spacing: 2) { |
| 21 | HStack { |
| 22 | Text(thread.title).font(.headline).lineLimit(1) |
| 23 | if let badge = thread.badge { |
| 24 | AgentBadge(badge) |
| 25 | } |
| 26 | Spacer() |
| 27 | HStack(spacing: 4) { |
| 28 | if thread.pinned { |
| 29 | Image(systemName: "pin.fill") |
| 30 | .font(.caption2) |
| 31 | .foregroundStyle(.secondary) |
| 32 | .accessibilityLabel("Pinned") |
| 33 | } |
| 34 | if thread.lastAt > 0 { |
| 35 | Text(relTime(thread.lastAt)).font(.caption).foregroundStyle(.secondary) |
| 36 | } |
| 37 | } |
| 38 | } |
| 39 | // Always rendered, preview or not: two reserved lines keep |
| 40 | // every row the same height (à la Mail/Messages — a list of |
| 41 | // mixed heights read as jumbled next to app rows with no |
| 42 | // recent message). The web keeps its 2-line clamp. |
| 43 | HStack(spacing: 6) { |
| 44 | Text(thread.preview) |
| 45 | .font(.subheadline).foregroundStyle(.secondary) |
| 46 | .lineLimit(2, reservesSpace: true) |
| 47 | Spacer() |
| 48 | // A muted chat never escalates: the slashed bell replaces |
| 49 | // the unread dot — the state stays visible (never absence |
| 50 | // alone), it just doesn't shout. Web-list parity. |
| 51 | if thread.isMuted { |
| 52 | Image(systemName: "bell.slash.fill") |
| 53 | .font(.caption) |
| 54 | .foregroundStyle(.secondary) |
| 55 | .accessibilityLabel("Muted") |
| 56 | } else if thread.unread { |
| 57 | Circle().fill(Color.accentColor).frame(width: 9, height: 9) |
| 58 | } |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | // relTime moved to LChatCore (Format) — the Mac thread list uses it too. |
| 66 | |