89 lines
3.2 KiB
Swift
89 lines
3.2 KiB
Swift
// SPDX-FileCopyrightText: 2026 Kaede Akino
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
import SwiftUI
|
|
|
|
/// Trailing controls inside an answer field. Both recording actions stay on
|
|
/// the right; neither the draft nor a waveform takes their place.
|
|
public struct UserInputSpeechControls: View {
|
|
private let isActive: Bool
|
|
private let isRecording: Bool
|
|
private let canStart: Bool
|
|
private let start: () -> Void
|
|
private let cancel: () -> Void
|
|
private let stop: () -> Void
|
|
|
|
public init(
|
|
isActive: Bool, isRecording: Bool, canStart: Bool,
|
|
start: @escaping () -> Void, cancel: @escaping () -> Void, stop: @escaping () -> Void
|
|
) {
|
|
self.isActive = isActive
|
|
self.isRecording = isRecording
|
|
self.canStart = canStart
|
|
self.start = start
|
|
self.cancel = cancel
|
|
self.stop = stop
|
|
}
|
|
|
|
private var controlSize: CGFloat {
|
|
#if os(iOS)
|
|
44
|
|
#else
|
|
32
|
|
#endif
|
|
}
|
|
|
|
public var body: some View {
|
|
HStack(spacing: 2) {
|
|
if isActive {
|
|
control(AppStrings.Assistant.cancelTranscription, symbol: "xmark", action: cancel)
|
|
.accessibilityIdentifier("UserInputSpeech.Cancel")
|
|
if isRecording {
|
|
control(AppStrings.Assistant.stopRecording, symbol: "stop.fill", action: stop)
|
|
.accessibilityIdentifier("UserInputSpeech.Stop")
|
|
} else {
|
|
ProgressView()
|
|
.controlSize(.small)
|
|
.frame(width: controlSize, height: controlSize)
|
|
.accessibilityLabel(Text(AppStrings.Assistant.transcribing))
|
|
}
|
|
} else {
|
|
Button(action: start) {
|
|
Image(systemName: "mic")
|
|
.frame(width: controlSize, height: controlSize)
|
|
.contentShape(.rect)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.disabled(!canStart)
|
|
.accessibilityLabel(Text(AppStrings.Assistant.transcribe))
|
|
.help(Text(AppStrings.Assistant.transcribeWithAppleSpeech))
|
|
.accessibilityIdentifier("UserInputSpeech.Start")
|
|
}
|
|
}
|
|
.fixedSize()
|
|
}
|
|
|
|
private func control(_ title: LocalizedStringResource, symbol: String, action: @escaping () -> Void) -> some View {
|
|
Button(action: action) {
|
|
#if os(iOS)
|
|
Image(systemName: symbol)
|
|
.font(.title3.weight(.medium))
|
|
.symbolRenderingMode(.monochrome)
|
|
.foregroundStyle(Color.primary.opacity(0.75))
|
|
.frame(width: controlSize, height: controlSize)
|
|
.contentShape(.rect)
|
|
#else
|
|
Image(systemName: symbol)
|
|
.font(.system(size: 10, weight: .semibold))
|
|
.frame(width: 26, height: 26)
|
|
.background(.quaternary.opacity(0.55), in: .circle)
|
|
.frame(width: controlSize, height: controlSize)
|
|
.contentShape(.rect)
|
|
#endif
|
|
}
|
|
.buttonStyle(.plain)
|
|
.accessibilityLabel(Text(title))
|
|
.help(Text(title))
|
|
}
|
|
}
|