Watch: what changed on Recent cards and the thread screen, search in All threads, a switching complication with Allow and Deny in the Smart Stack, and question options on notifications
This commit is contained in:
1 parent
f8241e42bb
commit
bfcbb69550
29 files changed
+1100
-137
No files matched your search
@@ -27,9 +27,62 @@ struct SnapshotTests {
|
||||
hostId: target.hostId, threadId: target.threadId,
|
||||
request: .decision(requestId: item.request!.id, decision: .decline))
|
||||
}
|
||||
let idle = WaitingEntry(date: .now, content: .counts(try await gateway.summary(), stale: false))
|
||||
let working = WaitingEntry(date: .now, content: .counts(try await gateway.summary(), stale: false))
|
||||
|
||||
try write(ComplicationGallery(waiting: waiting, waitingCount: feed.needsYou.count, idle: idle), "complication.png")
|
||||
try write(ComplicationGallery(sections: [("\(feed.needsYou.count) waiting", waiting), ("Nothing waiting", working)]), "complication.png")
|
||||
try write(ComplicationGallery(sections: [("\(feed.needsYou.count) waiting", waiting)]), "complication-waiting.png")
|
||||
try write(ComplicationGallery(sections: [("Nothing waiting", working)]), "complication-working.png")
|
||||
}
|
||||
|
||||
@Test func smartStack() async throws {
|
||||
// Answering the release question leaves the homelab card's subagent approval waiting longest.
|
||||
let gateway = DemoGateway()
|
||||
let release = try await gateway.thread(hostId: "mac-mini", threadId: "t-release")
|
||||
try await gateway.respond(
|
||||
hostId: "mac-mini", threadId: "t-release",
|
||||
request: .answers(requestId: release.item.request!.id, answers: ["checks": .values(["unit"])]))
|
||||
let summary = try await gateway.summary()
|
||||
let approval = try #require(await WaitingEntry.Approval.load(for: summary, api: gateway))
|
||||
let waiting = WaitingEntry(date: .now, content: .counts(summary, stale: false), approval: approval)
|
||||
|
||||
// Denying everything on a fresh gateway leaves the scaffold thread working most recently.
|
||||
let idle = DemoGateway()
|
||||
for item in try await idle.feed().needsYou {
|
||||
let target = item.requestThread
|
||||
try await idle.respond(
|
||||
hostId: target.hostId, threadId: target.threadId,
|
||||
request: .decision(requestId: item.request!.id, decision: .decline))
|
||||
}
|
||||
let working = WaitingEntry(date: .now, content: .counts(try await idle.summary(), stale: false))
|
||||
try write(SmartStackGallery(entries: [waiting, working]), "smart-stack.png")
|
||||
}
|
||||
|
||||
@Test func changes() async throws {
|
||||
let gateway = DemoGateway()
|
||||
let feed = try await gateway.feed()
|
||||
try write(feedColumn {
|
||||
RecentHeader(recent: feed.recent, more: feed.moreRecent)
|
||||
ForEach(feed.recent.prefix(4)) { StatusCard(item: $0) }
|
||||
}, "feed-changes.png")
|
||||
let detail = try await gateway.thread(hostId: "littleman", threadId: "t-next")
|
||||
try write(titled("Thread", ThreadView(hostId: "littleman", threadId: "t-next", detail: detail, scrolls: false)), "thread-changes.png")
|
||||
}
|
||||
|
||||
@Test func search() async throws {
|
||||
let gateway = DemoGateway()
|
||||
let hits = try await gateway.search("healthcheck")
|
||||
try write(titled("\u{201C}healthcheck\u{201D}", SearchView(query: "healthcheck", response: hits, scrolls: false)), "search.png")
|
||||
let none = try await gateway.search("terraform")
|
||||
try write(titled("\u{201C}terraform\u{201D}", SearchView(query: "terraform", response: none, scrolls: false)), "search-empty.png")
|
||||
}
|
||||
|
||||
@Test func questionNotification() throws {
|
||||
let question = PushQuestion(
|
||||
id: "base", options: [.init(label: "bun:1-alpine", value: "alpine"), .init(label: "bun:1-debian", value: "debian")],
|
||||
allowCustomAnswer: true)
|
||||
try write(NotificationPreview(
|
||||
view: InputNotificationView(title: "Gateway Containerfile", question: "Which base image?"),
|
||||
actions: NotificationHandler.inputActions(question).map(\.title)), "notification-question.png")
|
||||
}
|
||||
|
||||
@Test func multiSelectQuestion() async throws {
|
||||
@@ -169,16 +222,15 @@ struct SnapshotTests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Every family at its 46mm size, once with threads waiting and once with nothing waiting.
|
||||
/// Every family at its 46mm size, once per titled entry.
|
||||
private struct ComplicationGallery: View {
|
||||
var waiting: WaitingEntry
|
||||
var waitingCount: Int
|
||||
var idle: WaitingEntry
|
||||
var sections: [(title: String, entry: WaitingEntry)]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
section("\(waitingCount) waiting", waiting)
|
||||
section("Nothing waiting", idle)
|
||||
ForEach(sections.indices, id: \.self) { index in
|
||||
section(sections[index].title, sections[index].entry)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.top, 8)
|
||||
@@ -193,7 +245,7 @@ private struct ComplicationGallery: View {
|
||||
WaitingView(entry: entry, familyOverride: .accessoryInline)
|
||||
.font(.system(size: 13, weight: .semibold, design: .rounded))
|
||||
.frame(height: 18)
|
||||
WaitingView(entry: entry, familyOverride: .accessoryRectangular)
|
||||
WaitingView(entry: entry, familyOverride: .accessoryRectangular, inStackOverride: false)
|
||||
.frame(width: 184, height: 72)
|
||||
HStack(spacing: 16) {
|
||||
// Outside a face AccessoryWidgetBackground fills a square; a face clips it round.
|
||||
@@ -206,3 +258,51 @@ private struct ComplicationGallery: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The rectangular widget as the Smart Stack shows it, on its card and with Allow and Deny when the
|
||||
/// waiting thread wants an approval.
|
||||
private struct SmartStackGallery: View {
|
||||
var entries: [WaitingEntry]
|
||||
|
||||
var body: some View {
|
||||
VStack(spacing: 10) {
|
||||
ForEach(entries.indices, id: \.self) { index in
|
||||
WaitingView(entry: entries[index], familyOverride: .accessoryRectangular, inStackOverride: true)
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.vertical, 8)
|
||||
.frame(width: 196, height: 96, alignment: .topLeading)
|
||||
.background(Color(white: 0.11), in: RoundedRectangle(cornerRadius: 18, style: .continuous))
|
||||
}
|
||||
}
|
||||
.padding(.top, 8)
|
||||
.foregroundStyle(.white)
|
||||
.fontDesign(.rounded)
|
||||
}
|
||||
}
|
||||
|
||||
/// The question notification's long look over its actions. The system draws the actions; these
|
||||
/// stand in for them with the same titles.
|
||||
private struct NotificationPreview: View {
|
||||
var view: InputNotificationView
|
||||
var actions: [String]
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 6) {
|
||||
Text("WRISTCODE")
|
||||
.font(.system(size: 11, weight: .semibold, design: .rounded))
|
||||
.foregroundStyle(Theme.secondaryText)
|
||||
view
|
||||
.padding(.bottom, 4)
|
||||
ForEach(actions, id: \.self) { title in
|
||||
Text(title)
|
||||
.font(.system(size: 15, weight: .semibold, design: .rounded))
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 9)
|
||||
.background(Theme.control, in: RoundedRectangle(cornerRadius: 14, style: .continuous))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 8)
|
||||
.padding(.top, 10)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user