ios: share prompt field across chat and agent views
TestFlight / Build and upload (push) Successful in 1m44s

This commit is contained in:
2026-10-14 15:08:34 -07:00
parent 4389f805f4
commit 153aa5d2fa
4 changed files with 99 additions and 92 deletions
@@ -194,31 +194,31 @@ struct SybilAgentView: View {
} }
} }
private var canSendComposer: Bool {
!room.composer.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && room.composer.utf16.count <= 40_000
}
private var composer: some View { private var composer: some View {
HStack(alignment: .bottom, spacing: 10) { HStack(alignment: .bottom, spacing: 10) {
TextField("Message Sybil…", text: $room.composer, axis: .vertical) SybilPromptField(
.lineLimit(1...6) text: $room.composer,
.font(.sybil(.body)) focused: $composerFocused,
.focused($composerFocused) submit: submitComposer
.accessibilityIdentifier("agent-composer") )
Button { .accessibilityIdentifier("agent-composer")
let content = room.composer
if room.send(content) { room.composer = ""; atLatest = true } SybilPromptSendButton(canSend: canSendComposer, submit: submitComposer)
} label: {
Image(systemName: "arrow.up")
.font(.system(size: 17, weight: .semibold))
.foregroundStyle(.white)
.frame(width: 38, height: 38)
.background(SybilTheme.primaryGradient, in: Circle())
}
.disabled(room.composer.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || room.composer.utf16.count > 40_000)
.accessibilityLabel("Send message")
.accessibilityIdentifier("agent-send") .accessibilityIdentifier("agent-send")
} }
.padding(12) .padding(.horizontal, 14)
.background(SybilTheme.surfaceStrong, in: RoundedRectangle(cornerRadius: 22)) .padding(.bottom, 12)
.overlay(RoundedRectangle(cornerRadius: 22).stroke(SybilTheme.border)) .padding(.top, 6)
.padding(.horizontal, 12).padding(.bottom, 10).padding(.top, 6) }
private func submitComposer() {
guard canSendComposer, room.send(room.composer) else { return }
room.composer = ""
atLatest = true
} }
} }
@@ -0,0 +1,62 @@
import SwiftUI
struct SybilPromptField: View {
var placeholder: String = "Enter Prompt"
@Binding var text: String
@FocusState.Binding var focused: Bool
var selection: Binding<TextSelection?>? = nil
var axis: Axis = .vertical
var submit: () -> Void
var body: some View {
Group {
if let selection {
TextField(placeholder, text: $text, selection: selection, axis: axis)
} else {
TextField(placeholder, text: $text, axis: axis)
}
}
.focused($focused)
.font(.sybil(.body))
.textInputAutocapitalization(.sentences)
.autocorrectionDisabled(false)
.lineLimit(1 ... 6)
.submitLabel(.send)
.onSubmit(submit)
.submitOnMacReturn(submit)
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(SybilTheme.composerGradient)
.opacity(0.98)
)
.foregroundStyle(SybilTheme.text)
}
}
struct SybilPromptSendButton: View {
var systemImage: String = "arrow.up"
var canSend: Bool
var submit: () -> Void
var body: some View {
Button(action: submit) {
Image(systemName: systemImage)
.font(.system(size: 17, weight: .semibold))
.frame(width: 40, height: 40)
.background(
Circle()
.fill(
canSend
? AnyShapeStyle(SybilTheme.primaryGradient)
: AnyShapeStyle(SybilTheme.surface)
)
)
.foregroundStyle(canSend ? SybilTheme.text : SybilTheme.textMuted)
}
.buttonStyle(.plain)
.disabled(!canSend)
.accessibilityLabel("Send message")
}
}
@@ -95,46 +95,19 @@ struct SybilQuickQuestionView: View {
private var composer: some View { private var composer: some View {
VStack(alignment: .leading, spacing: 10) { VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .bottom, spacing: 10) { HStack(alignment: .bottom, spacing: 10) {
TextField( SybilPromptField(
"Ask anything...", placeholder: "Ask anything...",
text: Binding( text: Binding(
get: { viewModel.quickQuestionPrompt }, get: { viewModel.quickQuestionPrompt },
set: { viewModel.updateQuickQuestionPrompt($0) } set: { viewModel.updateQuickQuestionPrompt($0) }
), ),
focused: $promptFocused,
selection: $promptSelection, selection: $promptSelection,
axis: .horizontal axis: .horizontal,
submit: submitQuestion
) )
.focused($promptFocused)
.font(.body)
.textInputAutocapitalization(.sentences)
.autocorrectionDisabled(false)
.submitLabel(.send)
.onSubmit(submitQuestion)
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(SybilTheme.composerGradient)
.opacity(0.98)
)
.foregroundStyle(SybilTheme.text)
Button(action: submitQuestion) { SybilPromptSendButton(canSend: viewModel.canSendQuickQuestion, submit: submitQuestion)
Image(systemName: "arrow.up")
.font(.body.weight(.semibold))
.frame(width: 40, height: 40)
.background(
Circle()
.fill(
viewModel.canSendQuickQuestion
? AnyShapeStyle(SybilTheme.primaryGradient)
: AnyShapeStyle(SybilTheme.surfaceStrong.opacity(0.92))
)
)
.foregroundStyle(viewModel.canSendQuickQuestion ? SybilTheme.text : SybilTheme.textMuted)
}
.buttonStyle(.plain)
.disabled(!viewModel.canSendQuickQuestion)
.accessibilityLabel("Ask quick question") .accessibilityLabel("Ask quick question")
} }
@@ -613,47 +613,19 @@ struct SybilWorkspaceView: View {
.accessibilityLabel("Attach file") .accessibilityLabel("Attach file")
} }
TextField( SybilPromptField(
viewModel.isSearchMode ? "Search the web" : "Enter Prompt", placeholder: viewModel.isSearchMode ? "Search the web" : "Enter Prompt",
text: $viewModel.composer, text: $viewModel.composer,
axis: .vertical focused: $composerFocused,
submit: submitComposer
) )
.focused($composerFocused)
.textInputAutocapitalization(.sentences)
.autocorrectionDisabled(false)
.lineLimit(1 ... 6)
.submitLabel(.send)
.onSubmit {
submitComposer()
}
.submitOnMacReturn(submitComposer)
.padding(.horizontal, 12)
.padding(.vertical, 10)
.background(
RoundedRectangle(cornerRadius: 12)
.fill(SybilTheme.composerGradient)
.opacity(0.98)
)
.foregroundStyle(SybilTheme.text)
Button { SybilPromptSendButton(
submitComposer() systemImage: viewModel.isSearchMode ? "magnifyingglass" : "arrow.up",
} label: { canSend: viewModel.canSendComposer,
Image(systemName: viewModel.isSearchMode ? "magnifyingglass" : "arrow.up") submit: submitComposer
.font(.system(size: 17, weight: .semibold)) )
.frame(width: 40, height: 40) .accessibilityLabel(viewModel.isSearchMode ? "Search" : "Send message")
.background(
Circle()
.fill(
viewModel.canSendComposer
? AnyShapeStyle(SybilTheme.primaryGradient)
: AnyShapeStyle(SybilTheme.surface)
)
)
.foregroundStyle(viewModel.canSendComposer ? SybilTheme.text : SybilTheme.textMuted)
}
.buttonStyle(.plain)
.disabled(!viewModel.canSendComposer)
} }
} }
.padding(.horizontal, 14) .padding(.horizontal, 14)