Watch: keep Recent past 10 behind an N more row with a full list, and settle all in one call
This commit is contained in:
1 parent
2a127327bf
commit
a5222e14e8
13 files changed
+384
-52
No files matched your search
@@ -1,6 +1,7 @@
|
||||
// Mock B's attention feed: the count waiting on you, then cards for every host. Needs you first
|
||||
// (with Allow and Deny on the card), then working (with what the agent is doing), then recent.
|
||||
// A Recent row swipes left to settle, and the Recent header settles every completed thread.
|
||||
// A Recent row swipes left to settle, and the Recent header settles every completed thread. Past
|
||||
// Recent's first 10, an "N more" row opens the full Recent list.
|
||||
|
||||
import SwiftUI
|
||||
|
||||
@@ -15,28 +16,25 @@ struct FeedView: View {
|
||||
.feedRow()
|
||||
|
||||
if let feed = model.feed {
|
||||
let recent = feed.recent.filter { !model.settling.contains($0.id) }
|
||||
let recent = model.visibleRecent(feed.recent)
|
||||
let more = model.settlingAll ? 0 : feed.moreRecent
|
||||
ForEach(feed.needsYou) { item in
|
||||
NeedsYouCard(item: item).feedRow()
|
||||
}
|
||||
ForEach(feed.working) { item in
|
||||
StatusCard(item: item).feedRow()
|
||||
}
|
||||
if !recent.isEmpty {
|
||||
RecentHeader(recent: recent).feedRow()
|
||||
if !recent.isEmpty || more > 0 {
|
||||
RecentHeader(recent: recent, more: more).feedRow()
|
||||
}
|
||||
ForEach(recent) { item in
|
||||
StatusCard(item: item)
|
||||
.feedRow()
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button { Task { await model.settle([item]) } } label: {
|
||||
Label("Settle", systemImage: "checkmark")
|
||||
}
|
||||
.tint(Theme.work)
|
||||
}
|
||||
RecentRow(item: item).feedRow()
|
||||
}
|
||||
if feed.needsYou.isEmpty && feed.working.isEmpty && recent.isEmpty {
|
||||
Text("No threads in the last day.")
|
||||
if more > 0 {
|
||||
MoreRecentRow(count: more).feedRow()
|
||||
}
|
||||
if feed.needsYou.isEmpty && feed.working.isEmpty && recent.isEmpty && more == 0 {
|
||||
Text("No threads right now.")
|
||||
.font(.system(size: 13))
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
.feedRow()
|
||||
@@ -79,7 +77,8 @@ struct FeedView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private extension View {
|
||||
extension View {
|
||||
/// A clear List row with the feed's card insets.
|
||||
func feedRow() -> some View {
|
||||
listRowBackground(Color.clear)
|
||||
.listRowInsets(EdgeInsets(top: 3, leading: 4, bottom: 3, trailing: 4))
|
||||
@@ -130,30 +129,84 @@ private struct FeedHeader: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// "Recent" over the finished and failed threads, with Settle all when any completed. Settle all
|
||||
/// A title over the finished and failed threads, with Settle all when any completed or when
|
||||
/// threads past `recent` may have. Settle all is one call for every completed thread in Recent. It
|
||||
/// leaves failed threads, which settle one at a time by swipe.
|
||||
struct RecentHeader: View {
|
||||
@Environment(AppModel.self) private var model
|
||||
var title = "Recent"
|
||||
var recent: [FeedItem]
|
||||
/// Threads in Recent that `recent` leaves out.
|
||||
var more = 0
|
||||
|
||||
var body: some View {
|
||||
let completed = recent.filter { $0.phase == .completed }
|
||||
HStack {
|
||||
Text("Recent")
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
Spacer()
|
||||
if !completed.isEmpty {
|
||||
Button("Settle all") { Task { await model.settle(completed) } }
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(Theme.work)
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
HStack {
|
||||
Text(title)
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
Spacer()
|
||||
if more > 0 || recent.contains(where: { $0.phase == .completed }) {
|
||||
Button("Settle all") { Task { await model.settleAllRecent() } }
|
||||
.buttonStyle(.plain)
|
||||
.foregroundStyle(Theme.work)
|
||||
.disabled(model.settlingAll)
|
||||
}
|
||||
}
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
if let error = model.settleAllError {
|
||||
Text(error)
|
||||
.font(.system(size: 11))
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
}
|
||||
}
|
||||
.font(.system(size: 12, weight: .semibold))
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.top, 4)
|
||||
}
|
||||
}
|
||||
|
||||
/// A Recent thread: its card, which opens the thread, and a left swipe to settle it.
|
||||
struct RecentRow: View {
|
||||
@Environment(AppModel.self) private var model
|
||||
var item: FeedItem
|
||||
|
||||
var body: some View {
|
||||
StatusCard(item: item)
|
||||
.swipeActions(edge: .trailing) {
|
||||
Button { Task { await model.settle([item]) } } label: {
|
||||
Label("Settle", systemImage: "checkmark")
|
||||
}
|
||||
.tint(Theme.work)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The last Recent row when the feed leaves threads out: "N more", dimmer than a card, opening the
|
||||
/// full Recent list.
|
||||
struct MoreRecentRow: View {
|
||||
@Environment(AppModel.self) private var model
|
||||
var count: Int
|
||||
|
||||
var body: some View {
|
||||
Button { model.path.append(.recent) } label: {
|
||||
Card(edge: Theme.done.opacity(0.5)) {
|
||||
HStack {
|
||||
Text("\(count) more")
|
||||
.font(.system(size: 13, weight: .semibold))
|
||||
.foregroundStyle(Theme.done)
|
||||
Spacer(minLength: 4)
|
||||
Image(systemName: "chevron.right")
|
||||
.font(.system(size: 11, weight: .bold))
|
||||
.foregroundStyle(Theme.done)
|
||||
}
|
||||
}
|
||||
.opacity(0.8)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityLabel("\(count) more recent threads")
|
||||
}
|
||||
}
|
||||
|
||||
/// An amber card for a thread waiting on an approval or an answer, with Allow and Deny inline.
|
||||
/// When a subagent asked, one line names it, and the answer goes to the subagent.
|
||||
struct NeedsYouCard: View {
|
||||
|
||||
Reference in new issue
Block a user