Merge branch 'dev' into canary
This commit is contained in:
@@ -9,6 +9,9 @@ struct StartChatComposer: View {
|
|||||||
/// start typing a new chat immediately. Consumed (reset to false) once handled, so it never
|
/// start typing a new chat immediately. Consumed (reset to false) once handled, so it never
|
||||||
/// steals focus on a later Home appearance. `.constant(false)` for the iPhone/compact path.
|
/// steals focus on a later Home appearance. `.constant(false)` for the iPhone/compact path.
|
||||||
var focusNewChat: Binding<Bool> = .constant(false)
|
var focusNewChat: Binding<Bool> = .constant(false)
|
||||||
|
/// Called after a chat is kicked off, so a host that presents the composer modally (e.g. the
|
||||||
|
/// Sessions tab's floating New-chat button) can dismiss itself. Home leaves it nil and stays put.
|
||||||
|
var onStart: (() -> Void)? = nil
|
||||||
@State private var projectID: ProjectID?
|
@State private var projectID: ProjectID?
|
||||||
@State private var draft = ""
|
@State private var draft = ""
|
||||||
@State private var auto = false
|
@State private var auto = false
|
||||||
@@ -93,6 +96,7 @@ struct StartChatComposer: View {
|
|||||||
baseBranch: branch.isEmpty ? nil : branch,
|
baseBranch: branch.isEmpty ? nil : branch,
|
||||||
useWorktree: useWorktree, auto: auto)
|
useWorktree: useWorktree, auto: auto)
|
||||||
draft = ""
|
draft = ""
|
||||||
|
onStart?()
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrow.up.circle.fill").font(.title)
|
Image(systemName: "arrow.up.circle.fill").font(.title)
|
||||||
|
|||||||
@@ -6,8 +6,13 @@ import NucleicProtocol
|
|||||||
struct SessionsView: View {
|
struct SessionsView: View {
|
||||||
@EnvironmentObject var store: RemoteStore
|
@EnvironmentObject var store: RemoteStore
|
||||||
@State private var showArchived = false
|
@State private var showArchived = false
|
||||||
|
@State private var showNewChat = false
|
||||||
@State private var path = NavigationPath()
|
@State private var path = NavigationPath()
|
||||||
|
|
||||||
|
/// The floating New-chat button only makes sense when this device can control a Mac and there's
|
||||||
|
/// at least one project to start a chat in; otherwise the composer it opens would be inert.
|
||||||
|
private var canStartChat: Bool { store.canControl && !store.dashboard.projects.isEmpty }
|
||||||
|
|
||||||
private var grouped: [(title: String, rows: [WireSessionSummary])] {
|
private var grouped: [(title: String, rows: [WireSessionSummary])] {
|
||||||
let pool = (showArchived ? store.sessions : store.liveSessions)
|
let pool = (showArchived ? store.sessions : store.liveSessions)
|
||||||
.sorted(by: StatusStyle.attentionThenRecency)
|
.sorted(by: StatusStyle.attentionThenRecency)
|
||||||
@@ -84,10 +89,50 @@ struct SessionsView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// A floating "new chat" affordance in the lower-right, mirroring the Mac's per-project
|
||||||
|
// "New chat" button. Picks a project + prompt in a sheet, then kicks off the chat.
|
||||||
|
.overlay(alignment: .bottomTrailing) {
|
||||||
|
if canStartChat { newChatButton }
|
||||||
|
}
|
||||||
|
.sheet(isPresented: $showNewChat) {
|
||||||
|
NavigationStack {
|
||||||
|
ScrollView {
|
||||||
|
StartChatComposer(onStart: { showNewChat = false })
|
||||||
|
.padding()
|
||||||
|
}
|
||||||
|
.navigationTitle("New chat")
|
||||||
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
|
.toolbar {
|
||||||
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
|
Button("Cancel") { showNewChat = false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.presentationDetents([.medium, .large])
|
||||||
|
}
|
||||||
.safeAreaInset(edge: .bottom) { ConnectionChip().padding(.bottom, 8) }
|
.safeAreaInset(edge: .bottom) { ConnectionChip().padding(.bottom, 8) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The circular plus button, styled to match the transcript's jump-to-bottom FAB: an accent
|
||||||
|
/// disc with a soft shadow, floated clear of the connection chip.
|
||||||
|
private var newChatButton: some View {
|
||||||
|
Button {
|
||||||
|
showNewChat = true
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "plus")
|
||||||
|
.font(.system(size: 22, weight: .semibold))
|
||||||
|
.foregroundStyle(.white)
|
||||||
|
.frame(width: 56, height: 56)
|
||||||
|
.background(Palette.accent, in: .circle)
|
||||||
|
.shadow(color: .black.opacity(0.25), radius: 6, y: 2)
|
||||||
|
}
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
.padding(.trailing, 20)
|
||||||
|
.padding(.bottom, 20)
|
||||||
|
.accessibilityLabel("New chat")
|
||||||
|
}
|
||||||
|
|
||||||
/// Push the session a notification tap asked for, then clear the request.
|
/// Push the session a notification tap asked for, then clear the request.
|
||||||
private func consumeRoute() {
|
private func consumeRoute() {
|
||||||
guard let route = store.pendingRoute else { return }
|
guard let route = store.pendingRoute else { return }
|
||||||
|
|||||||
Reference in New Issue
Block a user