improve: enhance dashboard styling and metrics - Add Words/Minute and Words/Session metrics, improve card designs, update TimeEfficiencyView, ensure consistent styling
This commit is contained in:
parent
49aacc8cd6
commit
09b73a350c
@ -3,15 +3,31 @@ import SwiftUI
|
||||
struct MetricCard: View {
|
||||
let title: String
|
||||
let value: String
|
||||
let icon: String
|
||||
let color: Color
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
Text(value)
|
||||
.font(.title)
|
||||
.fontWeight(.bold)
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
HStack(spacing: 12) {
|
||||
// Icon
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 24))
|
||||
.foregroundColor(color)
|
||||
.frame(width: 32, height: 32)
|
||||
.background(
|
||||
Circle()
|
||||
.fill(color.opacity(0.1))
|
||||
)
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(title)
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
Text(value)
|
||||
.font(.system(size: 24, weight: .bold, design: .rounded))
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.padding()
|
||||
|
||||
@ -38,8 +38,30 @@ struct MetricsContent: View {
|
||||
|
||||
private var metricsGrid: some View {
|
||||
LazyVGrid(columns: [GridItem(.flexible()), GridItem(.flexible())], spacing: 20) {
|
||||
MetricCard(title: "Words Captured", value: "\(totalWordsTranscribed)")
|
||||
MetricCard(title: "Voice-to-Text Sessions", value: "\(transcriptions.count)")
|
||||
MetricCard(
|
||||
title: "Words Captured",
|
||||
value: "\(totalWordsTranscribed)",
|
||||
icon: "text.word.spacing",
|
||||
color: .blue
|
||||
)
|
||||
MetricCard(
|
||||
title: "Voice-to-Text Sessions",
|
||||
value: "\(transcriptions.count)",
|
||||
icon: "mic.circle.fill",
|
||||
color: .green
|
||||
)
|
||||
MetricCard(
|
||||
title: "Average Words/Minute",
|
||||
value: String(format: "%.1f", averageWordsPerMinute),
|
||||
icon: "speedometer",
|
||||
color: .orange
|
||||
)
|
||||
MetricCard(
|
||||
title: "Words/Session",
|
||||
value: String(format: "%.1f", averageWordsPerSession),
|
||||
icon: "chart.bar.fill",
|
||||
color: .purple
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -117,4 +139,15 @@ struct MetricsContent: View {
|
||||
|
||||
return dailyData.reversed()
|
||||
}
|
||||
|
||||
// Add computed properties for new metrics
|
||||
private var averageWordsPerMinute: Double {
|
||||
guard totalRecordedTime > 0 else { return 0 }
|
||||
return Double(totalWordsTranscribed) / (totalRecordedTime / 60.0)
|
||||
}
|
||||
|
||||
private var averageWordsPerSession: Double {
|
||||
guard !transcriptions.isEmpty else { return 0 }
|
||||
return Double(totalWordsTranscribed) / Double(transcriptions.count)
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,8 +45,9 @@ struct TimeEfficiencyView: View {
|
||||
bottomSection
|
||||
}
|
||||
.padding(.vertical, 24)
|
||||
.background(backgroundDesign)
|
||||
.overlay(borderOverlay)
|
||||
.background(Color(.controlBackgroundColor))
|
||||
.cornerRadius(10)
|
||||
.shadow(radius: 2)
|
||||
}
|
||||
|
||||
// MARK: - Subviews
|
||||
@ -154,34 +155,6 @@ struct TimeEfficiencyView: View {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Extension to allow hex color initialization
|
||||
|
||||
// MARK: - Styling Views
|
||||
|
||||
private var backgroundDesign: some View {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(nsColor: .controlBackgroundColor))
|
||||
}
|
||||
|
||||
private var borderOverlay: some View {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(
|
||||
.linearGradient(
|
||||
colors: [
|
||||
Color(nsColor: .controlAccentColor).opacity(0.2),
|
||||
Color.clear,
|
||||
Color.clear,
|
||||
Color(nsColor: .controlAccentColor).opacity(0.1)
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
private var efficiencyGradient: LinearGradient {
|
||||
LinearGradient(
|
||||
|
||||
@ -43,7 +43,7 @@ struct MetricsView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.background(Color(.windowBackgroundColor))
|
||||
.background(Color(.controlBackgroundColor))
|
||||
.task {
|
||||
// Ensure the model context is ready
|
||||
hasLoadedData = true
|
||||
|
||||
@ -661,22 +661,31 @@ class WhisperState: NSObject, ObservableObject, AVAudioRecorderDelegate {
|
||||
await toggleRecord()
|
||||
}
|
||||
} else {
|
||||
// Start a parallel task for both UI and recording
|
||||
// Serialize audio operations to prevent deadlocks
|
||||
Task {
|
||||
// Play start sound first
|
||||
SoundManager.shared.playStartSound()
|
||||
|
||||
// Start audio engine immediately - this can happen in parallel
|
||||
audioEngine.startAudioEngine()
|
||||
|
||||
// Show UI (this is quick now that we removed animations)
|
||||
await MainActor.run {
|
||||
showRecorderPanel() // Modified version that doesn't start audio engine
|
||||
isMiniRecorderVisible = true
|
||||
do {
|
||||
// First start the audio engine
|
||||
await MainActor.run {
|
||||
audioEngine.startAudioEngine()
|
||||
}
|
||||
|
||||
// Small delay to ensure audio system is ready
|
||||
try await Task.sleep(nanoseconds: 50_000_000) // 50ms
|
||||
|
||||
// Now play the sound
|
||||
SoundManager.shared.playStartSound()
|
||||
|
||||
// Show UI
|
||||
await MainActor.run {
|
||||
showRecorderPanel()
|
||||
isMiniRecorderVisible = true
|
||||
}
|
||||
|
||||
// Finally start recording
|
||||
await toggleRecord()
|
||||
} catch {
|
||||
logger.error("Error during recorder initialization: \(error)")
|
||||
}
|
||||
|
||||
// Start recording (this will happen in parallel with UI showing)
|
||||
await toggleRecord()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user