// // ComposerView.swift // Poche // // Author: Simon-Pierre Boucher // Contact: contact@spboucher.ai // import SwiftUI struct ComposerView: View { @Binding var text: String let isBusy: Bool let onSend: () -> Void @FocusState private var isFocused: Bool @State private var dictation = DictationController() private var canSend: Bool { !isBusy && !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } var body: some View { HStack(alignment: .bottom, spacing: Theme.spacingSmall + 2) { if dictation.isAvailable { micButton } TextField("Écris à Poche…", text: $text, axis: .vertical) .lineLimit(1...5) .textFieldStyle(.plain) .focused($isFocused) .padding(.horizontal, Theme.spacingMedium + 4) .padding(.vertical, Theme.spacingMedium - 1) .background(Theme.surface) .clipShape(RoundedRectangle(cornerRadius: Theme.bubbleCornerRadius)) .overlay { RoundedRectangle(cornerRadius: Theme.bubbleCornerRadius) .strokeBorder( isFocused ? Theme.accentA.opacity(0.45) : Color.primary.opacity(0.07), lineWidth: 1 ) } .onSubmit { if canSend { onSend() } } Button(action: onSend) { Image(systemName: "arrow.up") .font(.system(size: 17, weight: .bold)) .foregroundStyle(.white) .frame(width: 40, height: 40) .background { if canSend { Circle().fill(Theme.accentGradient) } else { Circle().fill(Color.secondary.opacity(0.35)) } } .shadow( color: canSend ? Theme.accentA.opacity(0.35) : .clear, radius: 6, y: 3 ) } .disabled(!canSend) .animation(.easeInOut(duration: 0.15), value: canSend) .accessibilityLabel("Envoyer") } .padding(.horizontal, Theme.spacingMedium) .padding(.top, Theme.spacingSmall) .padding(.bottom, Theme.spacingSmall + 2) .onChange(of: dictation.transcript) { _, transcript in if !transcript.isEmpty { text = transcript } } } private var micButton: some View { Button { Task { await dictation.toggle() } } label: { Image(systemName: dictation.state == .recording ? "waveform.circle.fill" : "mic") .font(.system(size: dictation.state == .recording ? 30 : 19, weight: .medium)) .foregroundStyle( dictation.state == .recording ? AnyShapeStyle(Theme.accentGradient) : AnyShapeStyle(Color.secondary) ) .frame(width: 40, height: 40) .symbolEffect(.pulse, isActive: dictation.state == .recording) } .disabled(isBusy) .accessibilityLabel(dictation.state == .recording ? "Arrêter la dictée" : "Dicter") } }