268 lines
7.9 KiB
Swift
268 lines
7.9 KiB
Swift
import SwiftUI
|
|
|
|
struct TimeEfficiencyView: View {
|
|
// MARK: - Properties
|
|
|
|
private let totalRecordedTime: TimeInterval
|
|
private let estimatedTypingTime: TimeInterval
|
|
|
|
// Computed properties for efficiency metrics
|
|
private var timeSaved: TimeInterval {
|
|
estimatedTypingTime - totalRecordedTime
|
|
}
|
|
|
|
private var efficiencyMultiplier: Double {
|
|
guard totalRecordedTime > 0 else { return 0 }
|
|
let multiplier = estimatedTypingTime / totalRecordedTime
|
|
return round(multiplier * 10) / 10 // Round to 1 decimal place
|
|
}
|
|
|
|
private var efficiencyMultiplierFormatted: String {
|
|
String(format: "%.1fx", efficiencyMultiplier)
|
|
}
|
|
|
|
// MARK: - Initializer
|
|
|
|
init(totalRecordedTime: TimeInterval, estimatedTypingTime: TimeInterval) {
|
|
self.totalRecordedTime = totalRecordedTime
|
|
self.estimatedTypingTime = estimatedTypingTime
|
|
}
|
|
|
|
// MARK: - Body
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
mainContent
|
|
}
|
|
}
|
|
|
|
// MARK: - Main Content View
|
|
|
|
private var mainContent: some View {
|
|
VStack(spacing: 24) {
|
|
headerSection
|
|
timeComparisonSection
|
|
bottomSection
|
|
}
|
|
.padding(.vertical, 24)
|
|
.background(Color(.controlBackgroundColor))
|
|
.cornerRadius(10)
|
|
.shadow(radius: 2)
|
|
}
|
|
|
|
// MARK: - Subviews
|
|
|
|
private var headerSection: some View {
|
|
VStack(alignment: .center, spacing: 8) {
|
|
HStack(spacing: 8) {
|
|
Text("You are")
|
|
.font(.system(size: 32, weight: .bold))
|
|
|
|
Text("\(efficiencyMultiplierFormatted) Faster")
|
|
.font(.system(size: 32, weight: .bold))
|
|
.foregroundStyle(efficiencyGradient)
|
|
|
|
Text("with VoiceInk")
|
|
.font(.system(size: 32, weight: .bold))
|
|
}
|
|
.lineLimit(1)
|
|
.minimumScaleFactor(0.5)
|
|
}
|
|
.padding(.horizontal, 24)
|
|
}
|
|
|
|
private var timeComparisonSection: some View {
|
|
HStack(spacing: 16) {
|
|
TimeBlockView(
|
|
duration: totalRecordedTime,
|
|
label: "SPEAKING TIME",
|
|
icon: "mic.circle.fill",
|
|
color: .green
|
|
)
|
|
|
|
TimeBlockView(
|
|
duration: estimatedTypingTime,
|
|
label: "TYPING TIME",
|
|
icon: "keyboard.fill",
|
|
color: .orange
|
|
)
|
|
}
|
|
.padding(.horizontal, 24)
|
|
}
|
|
|
|
private var bottomSection: some View {
|
|
HStack {
|
|
timeSavedView
|
|
Spacer()
|
|
reportIssueButton
|
|
}
|
|
.padding(.horizontal, 24)
|
|
}
|
|
|
|
private var timeSavedView: some View {
|
|
VStack(alignment: .leading, spacing: 8) {
|
|
Text("YOU'VE SAVED ⏳")
|
|
.font(.system(size: 13, weight: .heavy))
|
|
.tracking(4)
|
|
.foregroundColor(.secondary)
|
|
|
|
Text(formatDuration(timeSaved))
|
|
.font(.system(size: 32, weight: .black, design: .rounded))
|
|
.foregroundStyle(accentGradient)
|
|
}
|
|
}
|
|
|
|
private var reportIssueButton: some View {
|
|
ZStack {
|
|
Button(action: {
|
|
EmailSupport.openSupportEmail()
|
|
}) {
|
|
HStack(alignment: .center, spacing: 12) {
|
|
// Left icon
|
|
Image(systemName: "exclamationmark.bubble.fill")
|
|
.font(.system(size: 20, weight: .medium))
|
|
.foregroundStyle(.white)
|
|
|
|
// Center text
|
|
Text("Feedback or Issues?")
|
|
.font(.system(size: 13, weight: .medium))
|
|
.foregroundStyle(.white)
|
|
|
|
Spacer(minLength: 8)
|
|
}
|
|
.padding(.vertical, 10)
|
|
.padding(.horizontal, 12)
|
|
.background(accentGradient)
|
|
.cornerRadius(10)
|
|
}
|
|
.buttonStyle(.plain)
|
|
.shadow(color: Color.accentColor.opacity(0.2), radius: 3, y: 1)
|
|
.frame(maxWidth: 280)
|
|
|
|
// Copy system info button overlaid and centered vertically
|
|
HStack {
|
|
Spacer()
|
|
CopySystemInfoButton()
|
|
.padding(.trailing, 8)
|
|
}
|
|
.frame(maxWidth: 280)
|
|
}
|
|
.frame(maxWidth: 280)
|
|
}
|
|
|
|
private var efficiencyGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color.green,
|
|
Color.green.opacity(0.7)
|
|
],
|
|
startPoint: .leading,
|
|
endPoint: .trailing
|
|
)
|
|
}
|
|
|
|
private var accentGradient: LinearGradient {
|
|
LinearGradient(
|
|
colors: [
|
|
Color(nsColor: .controlAccentColor),
|
|
Color(nsColor: .controlAccentColor).opacity(0.8)
|
|
],
|
|
startPoint: .leading,
|
|
endPoint: .trailing
|
|
)
|
|
}
|
|
|
|
// MARK: - Utility Methods
|
|
|
|
private func formatDuration(_ duration: TimeInterval) -> String {
|
|
let formatter = DateComponentsFormatter()
|
|
formatter.allowedUnits = [.hour, .minute, .second]
|
|
formatter.unitsStyle = .abbreviated
|
|
return formatter.string(from: duration) ?? ""
|
|
}
|
|
}
|
|
|
|
// MARK: - Helper Struct
|
|
|
|
struct TimeBlockView: View {
|
|
let duration: TimeInterval
|
|
let label: String
|
|
let icon: String
|
|
let color: Color
|
|
|
|
private func formatDuration(_ duration: TimeInterval) -> String {
|
|
let formatter = DateComponentsFormatter()
|
|
formatter.allowedUnits = [.hour, .minute, .second]
|
|
formatter.unitsStyle = .abbreviated
|
|
return formatter.string(from: duration) ?? ""
|
|
}
|
|
|
|
var body: some View {
|
|
HStack(spacing: 16) {
|
|
Image(systemName: icon)
|
|
.font(.system(size: 24, weight: .semibold))
|
|
.foregroundColor(color)
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(formatDuration(duration))
|
|
.font(.system(size: 24, weight: .bold, design: .rounded))
|
|
|
|
Text(label)
|
|
.font(.system(size: 12, weight: .heavy))
|
|
.tracking(2)
|
|
.foregroundColor(.secondary)
|
|
}
|
|
|
|
Spacer()
|
|
}
|
|
.padding(.horizontal, 24)
|
|
.padding(.vertical, 16)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 16)
|
|
.fill(color.opacity(0.1))
|
|
)
|
|
}
|
|
}
|
|
|
|
// MARK: - Copy System Info Button
|
|
private struct CopySystemInfoButton: View {
|
|
@State private var isCopied: Bool = false
|
|
|
|
var body: some View {
|
|
Button(action: {
|
|
copySystemInfo()
|
|
}) {
|
|
Image(systemName: isCopied ? "checkmark" : "doc.on.doc")
|
|
.font(.system(size: 14, weight: .medium))
|
|
.foregroundColor(.white.opacity(0.9))
|
|
.padding(8)
|
|
.background(
|
|
Circle()
|
|
.fill(Color.white.opacity(0.2))
|
|
.overlay(
|
|
Circle()
|
|
.stroke(Color.white.opacity(0.3), lineWidth: 1)
|
|
)
|
|
)
|
|
.rotationEffect(.degrees(isCopied ? 360 : 0))
|
|
}
|
|
.buttonStyle(.plain)
|
|
.scaleEffect(isCopied ? 1.1 : 1.0)
|
|
.animation(.spring(response: 0.3, dampingFraction: 0.7), value: isCopied)
|
|
}
|
|
|
|
private func copySystemInfo() {
|
|
SystemInfoService.shared.copySystemInfoToClipboard()
|
|
|
|
withAnimation {
|
|
isCopied = true
|
|
}
|
|
|
|
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
|
|
withAnimation {
|
|
isCopied = false
|
|
}
|
|
}
|
|
}
|
|
}
|