diff --git a/ios/Packages/Sybil/Sources/Sybil/SybilAgentView.swift b/ios/Packages/Sybil/Sources/Sybil/SybilAgentView.swift index fcf04f9..023b7bd 100644 --- a/ios/Packages/Sybil/Sources/Sybil/SybilAgentView.swift +++ b/ios/Packages/Sybil/Sources/Sybil/SybilAgentView.swift @@ -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 { HStack(alignment: .bottom, spacing: 10) { - TextField("Message Sybil…", text: $room.composer, axis: .vertical) - .lineLimit(1...6) - .font(.sybil(.body)) - .focused($composerFocused) - .accessibilityIdentifier("agent-composer") - Button { - let content = room.composer - if room.send(content) { room.composer = ""; atLatest = true } - } 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") + SybilPromptField( + text: $room.composer, + focused: $composerFocused, + submit: submitComposer + ) + .accessibilityIdentifier("agent-composer") + + SybilPromptSendButton(canSend: canSendComposer, submit: submitComposer) .accessibilityIdentifier("agent-send") } - .padding(12) - .background(SybilTheme.surfaceStrong, in: RoundedRectangle(cornerRadius: 22)) - .overlay(RoundedRectangle(cornerRadius: 22).stroke(SybilTheme.border)) - .padding(.horizontal, 12).padding(.bottom, 10).padding(.top, 6) + .padding(.horizontal, 14) + .padding(.bottom, 12) + .padding(.top, 6) + } + + private func submitComposer() { + guard canSendComposer, room.send(room.composer) else { return } + room.composer = "" + atLatest = true } } diff --git a/ios/Packages/Sybil/Sources/Sybil/SybilPromptField.swift b/ios/Packages/Sybil/Sources/Sybil/SybilPromptField.swift new file mode 100644 index 0000000..7c0b94a --- /dev/null +++ b/ios/Packages/Sybil/Sources/Sybil/SybilPromptField.swift @@ -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? = 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") + } +} diff --git a/ios/Packages/Sybil/Sources/Sybil/SybilQuickQuestionView.swift b/ios/Packages/Sybil/Sources/Sybil/SybilQuickQuestionView.swift index ceb7fc4..1952c6d 100644 --- a/ios/Packages/Sybil/Sources/Sybil/SybilQuickQuestionView.swift +++ b/ios/Packages/Sybil/Sources/Sybil/SybilQuickQuestionView.swift @@ -95,46 +95,19 @@ struct SybilQuickQuestionView: View { private var composer: some View { VStack(alignment: .leading, spacing: 10) { HStack(alignment: .bottom, spacing: 10) { - TextField( - "Ask anything...", + SybilPromptField( + placeholder: "Ask anything...", text: Binding( get: { viewModel.quickQuestionPrompt }, set: { viewModel.updateQuickQuestionPrompt($0) } ), + focused: $promptFocused, 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) { - 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) + SybilPromptSendButton(canSend: viewModel.canSendQuickQuestion, submit: submitQuestion) .accessibilityLabel("Ask quick question") } diff --git a/ios/Packages/Sybil/Sources/Sybil/SybilWorkspaceView.swift b/ios/Packages/Sybil/Sources/Sybil/SybilWorkspaceView.swift index f018f01..909d1b7 100644 --- a/ios/Packages/Sybil/Sources/Sybil/SybilWorkspaceView.swift +++ b/ios/Packages/Sybil/Sources/Sybil/SybilWorkspaceView.swift @@ -613,47 +613,19 @@ struct SybilWorkspaceView: View { .accessibilityLabel("Attach file") } - TextField( - viewModel.isSearchMode ? "Search the web" : "Enter Prompt", + SybilPromptField( + placeholder: viewModel.isSearchMode ? "Search the web" : "Enter Prompt", 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 { - submitComposer() - } label: { - Image(systemName: viewModel.isSearchMode ? "magnifyingglass" : "arrow.up") - .font(.system(size: 17, weight: .semibold)) - .frame(width: 40, height: 40) - .background( - Circle() - .fill( - viewModel.canSendComposer - ? AnyShapeStyle(SybilTheme.primaryGradient) - : AnyShapeStyle(SybilTheme.surface) - ) - ) - .foregroundStyle(viewModel.canSendComposer ? SybilTheme.text : SybilTheme.textMuted) - } - .buttonStyle(.plain) - .disabled(!viewModel.canSendComposer) + SybilPromptSendButton( + systemImage: viewModel.isSearchMode ? "magnifyingglass" : "arrow.up", + canSend: viewModel.canSendComposer, + submit: submitComposer + ) + .accessibilityLabel(viewModel.isSearchMode ? "Search" : "Send message") } } .padding(.horizontal, 14)