Refactor recorder components
This commit is contained in:
parent
077fe10ab2
commit
d72ef7c1d3
@ -28,37 +28,16 @@ struct MiniRecorderView: View {
|
||||
}
|
||||
|
||||
private var statusView: some View {
|
||||
Group {
|
||||
let currentState = whisperState.recordingState
|
||||
|
||||
if currentState == .enhancing {
|
||||
Text("Enhancing")
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 10, weight: .medium, design: .default))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
} else if currentState == .transcribing {
|
||||
Text("Transcribing")
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 10, weight: .medium, design: .default))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
} else if currentState == .recording {
|
||||
AudioVisualizer(
|
||||
audioMeter: recorder.audioMeter,
|
||||
color: .white,
|
||||
isActive: currentState == .recording
|
||||
)
|
||||
} else {
|
||||
StaticVisualizer(color: .white)
|
||||
}
|
||||
}
|
||||
RecorderStatusDisplay(
|
||||
currentState: whisperState.recordingState,
|
||||
audioMeter: recorder.audioMeter
|
||||
)
|
||||
}
|
||||
|
||||
private var rightButton: some View {
|
||||
Group {
|
||||
if powerModeManager.isPowerModeEnabled {
|
||||
NotchToggleButton(
|
||||
RecorderToggleButton(
|
||||
isEnabled: powerModeManager.isPowerModeEnabled,
|
||||
icon: powerModeManager.currentActiveConfiguration.emoji,
|
||||
color: .orange,
|
||||
@ -72,7 +51,7 @@ struct MiniRecorderView: View {
|
||||
PowerModePopover()
|
||||
}
|
||||
} else {
|
||||
NotchToggleButton(
|
||||
RecorderToggleButton(
|
||||
isEnabled: enhancementService.isEnhancementEnabled,
|
||||
icon: enhancementService.activePrompt?.icon.rawValue ?? "brain",
|
||||
color: .blue,
|
||||
@ -109,7 +88,7 @@ struct MiniRecorderView: View {
|
||||
let isRecording = whisperState.recordingState == .recording
|
||||
let isProcessing = whisperState.recordingState == .transcribing || whisperState.recordingState == .enhancing
|
||||
|
||||
NotchRecordButton(
|
||||
RecorderRecordButton(
|
||||
isRecording: isRecording,
|
||||
isProcessing: isProcessing
|
||||
) {
|
||||
|
||||
@ -36,7 +36,7 @@ struct NotchRecorderView: View {
|
||||
let isRecording = whisperState.recordingState == .recording
|
||||
let isProcessing = whisperState.recordingState == .transcribing || whisperState.recordingState == .enhancing
|
||||
|
||||
NotchRecordButton(
|
||||
RecorderRecordButton(
|
||||
isRecording: isRecording,
|
||||
isProcessing: isProcessing
|
||||
) {
|
||||
@ -55,7 +55,7 @@ struct NotchRecorderView: View {
|
||||
private var rightToggleButton: some View {
|
||||
Group {
|
||||
if powerModeManager.isPowerModeEnabled {
|
||||
NotchToggleButton(
|
||||
RecorderToggleButton(
|
||||
isEnabled: powerModeManager.isPowerModeEnabled,
|
||||
icon: powerModeManager.currentActiveConfiguration.emoji,
|
||||
color: .orange,
|
||||
@ -68,7 +68,7 @@ struct NotchRecorderView: View {
|
||||
PowerModePopover()
|
||||
}
|
||||
} else {
|
||||
NotchToggleButton(
|
||||
RecorderToggleButton(
|
||||
isEnabled: enhancementService.isEnhancementEnabled,
|
||||
icon: enhancementService.activePrompt?.icon.rawValue ?? "brain",
|
||||
color: .blue,
|
||||
@ -106,33 +106,11 @@ struct NotchRecorderView: View {
|
||||
}
|
||||
|
||||
private var statusDisplay: some View {
|
||||
Group {
|
||||
let currentState = whisperState.recordingState
|
||||
|
||||
if currentState == .enhancing {
|
||||
Text("Enhancing")
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 10, weight: .medium, design: .default))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
} else if currentState == .transcribing {
|
||||
Text("Transcribing")
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 10, weight: .medium, design: .default))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
} else if currentState == .recording {
|
||||
AudioVisualizer(
|
||||
audioMeter: recorder.audioMeter,
|
||||
color: .white,
|
||||
isActive: currentState == .recording
|
||||
)
|
||||
.scaleEffect(y: min(1.0, (menuBarHeight - 8) / 25), anchor: .center)
|
||||
} else {
|
||||
StaticVisualizer(color: .white)
|
||||
.scaleEffect(y: min(1.0, (menuBarHeight - 8) / 25), anchor: .center)
|
||||
}
|
||||
}
|
||||
RecorderStatusDisplay(
|
||||
currentState: whisperState.recordingState,
|
||||
audioMeter: recorder.audioMeter,
|
||||
menuBarHeight: menuBarHeight
|
||||
)
|
||||
.frame(width: 70)
|
||||
.padding(.trailing, 8)
|
||||
}
|
||||
@ -163,101 +141,4 @@ struct NotchRecorderView: View {
|
||||
|
||||
|
||||
|
||||
struct NotchToggleButton: View {
|
||||
let isEnabled: Bool
|
||||
let icon: String
|
||||
let color: Color
|
||||
let disabled: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(isEnabled: Bool, icon: String, color: Color, disabled: Bool = false, action: @escaping () -> Void) {
|
||||
self.isEnabled = isEnabled
|
||||
self.icon = icon
|
||||
self.color = color
|
||||
self.disabled = disabled
|
||||
self.action = action
|
||||
}
|
||||
|
||||
private var isEmoji: Bool {
|
||||
return !icon.contains(".") && !icon.contains("-") && icon.unicodeScalars.contains { !$0.isASCII }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
Group {
|
||||
if isEmoji {
|
||||
Text(icon)
|
||||
.font(.system(size: 12))
|
||||
} else {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 11))
|
||||
}
|
||||
}
|
||||
.foregroundColor(disabled ? .white.opacity(0.3) : (isEnabled ? .white : .white.opacity(0.6)))
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.disabled(disabled)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Notch-specific button styles
|
||||
struct NotchRecordButton: View {
|
||||
let isRecording: Bool
|
||||
let isProcessing: Bool
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(buttonColor)
|
||||
.frame(width: 22, height: 22)
|
||||
|
||||
if isProcessing {
|
||||
ProcessingIndicator(color: .white)
|
||||
.frame(width: 14, height: 14)
|
||||
} else if isRecording {
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.white)
|
||||
.frame(width: 8, height: 8)
|
||||
} else {
|
||||
Circle()
|
||||
.fill(Color.white)
|
||||
.frame(width: 8, height: 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.disabled(isProcessing)
|
||||
}
|
||||
|
||||
private var buttonColor: Color {
|
||||
if isProcessing {
|
||||
return Color(red: 0.4, green: 0.4, blue: 0.45)
|
||||
} else if isRecording {
|
||||
return .red
|
||||
} else {
|
||||
return Color(red: 0.3, green: 0.3, blue: 0.35)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ProcessingIndicator: View {
|
||||
@State private var rotation: Double = 0
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
Circle()
|
||||
.trim(from: 0.1, to: 0.9)
|
||||
.stroke(color, lineWidth: 1.5)
|
||||
.frame(width: 12, height: 12)
|
||||
.rotationEffect(.degrees(rotation))
|
||||
.onAppear {
|
||||
withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
|
||||
rotation = 360
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
141
VoiceInk/Views/Recorder/RecorderComponents.swift
Normal file
141
VoiceInk/Views/Recorder/RecorderComponents.swift
Normal file
@ -0,0 +1,141 @@
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Generic Toggle Button Component
|
||||
struct RecorderToggleButton: View {
|
||||
let isEnabled: Bool
|
||||
let icon: String
|
||||
let color: Color
|
||||
let disabled: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(isEnabled: Bool, icon: String, color: Color, disabled: Bool = false, action: @escaping () -> Void) {
|
||||
self.isEnabled = isEnabled
|
||||
self.icon = icon
|
||||
self.color = color
|
||||
self.disabled = disabled
|
||||
self.action = action
|
||||
}
|
||||
|
||||
private var isEmoji: Bool {
|
||||
return !icon.contains(".") && !icon.contains("-") && icon.unicodeScalars.contains { !$0.isASCII }
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
Group {
|
||||
if isEmoji {
|
||||
Text(icon)
|
||||
.font(.system(size: 12))
|
||||
} else {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 11))
|
||||
}
|
||||
}
|
||||
.foregroundColor(disabled ? .white.opacity(0.3) : (isEnabled ? .white : .white.opacity(0.6)))
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.disabled(disabled)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Generic Record Button Component
|
||||
struct RecorderRecordButton: View {
|
||||
let isRecording: Bool
|
||||
let isProcessing: Bool
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
ZStack {
|
||||
Circle()
|
||||
.fill(buttonColor)
|
||||
.frame(width: 22, height: 22)
|
||||
|
||||
if isProcessing {
|
||||
ProcessingIndicator(color: .white)
|
||||
.frame(width: 14, height: 14)
|
||||
} else if isRecording {
|
||||
RoundedRectangle(cornerRadius: 3)
|
||||
.fill(Color.white)
|
||||
.frame(width: 8, height: 8)
|
||||
} else {
|
||||
Circle()
|
||||
.fill(Color.white)
|
||||
.frame(width: 8, height: 8)
|
||||
}
|
||||
}
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.disabled(isProcessing)
|
||||
}
|
||||
|
||||
private var buttonColor: Color {
|
||||
if isProcessing {
|
||||
return Color(red: 0.4, green: 0.4, blue: 0.45)
|
||||
} else if isRecording {
|
||||
return .red
|
||||
} else {
|
||||
return Color(red: 0.3, green: 0.3, blue: 0.35)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Processing Indicator Component
|
||||
struct ProcessingIndicator: View {
|
||||
@State private var rotation: Double = 0
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
Circle()
|
||||
.trim(from: 0.1, to: 0.9)
|
||||
.stroke(color, lineWidth: 1.5)
|
||||
.frame(width: 12, height: 12)
|
||||
.rotationEffect(.degrees(rotation))
|
||||
.onAppear {
|
||||
withAnimation(.linear(duration: 1).repeatForever(autoreverses: false)) {
|
||||
rotation = 360
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Status Display Component
|
||||
struct RecorderStatusDisplay: View {
|
||||
let currentState: RecordingState
|
||||
let audioMeter: AudioMeter
|
||||
let menuBarHeight: CGFloat?
|
||||
|
||||
init(currentState: RecordingState, audioMeter: AudioMeter, menuBarHeight: CGFloat? = nil) {
|
||||
self.currentState = currentState
|
||||
self.audioMeter = audioMeter
|
||||
self.menuBarHeight = menuBarHeight
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if currentState == .enhancing {
|
||||
Text("Enhancing")
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 10, weight: .medium, design: .default))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
} else if currentState == .transcribing {
|
||||
Text("Transcribing")
|
||||
.foregroundColor(.white)
|
||||
.font(.system(size: 10, weight: .medium, design: .default))
|
||||
.lineLimit(1)
|
||||
.minimumScaleFactor(0.5)
|
||||
} else if currentState == .recording {
|
||||
AudioVisualizer(
|
||||
audioMeter: audioMeter,
|
||||
color: .white,
|
||||
isActive: currentState == .recording
|
||||
)
|
||||
.scaleEffect(y: menuBarHeight != nil ? min(1.0, (menuBarHeight! - 8) / 25) : 1.0, anchor: .center)
|
||||
} else {
|
||||
StaticVisualizer(color: .white)
|
||||
.scaleEffect(y: menuBarHeight != nil ? min(1.0, (menuBarHeight! - 8) / 25) : 1.0, anchor: .center)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user