Files
OSGKeyboard/OSGKeyboardShared/DesignSystem/UsageStatCard.swift
Rocky cc8dd1070a feat: macOS architecture, cloud ASR/LLM providers, and 6-step iOS onboarding
- Add macOS menu-bar dictation app with local ASR models (SenseVoice/Qwen3),
  global Option hotkey, and bottom overlay
- Add cloud ASR/LLM providers (Anthropic, Volcengine, Bailian, and more) with
  provider logos, model listing, and connection checks
- Add shared 7-day usage stats UI (UsageStatsCluster / SevenDayUsageChart)
- Add iOS onboarding step 6 for polish LLM setup; hide custom-language-model
  diagnostic toggle behind DEBUG
- Unify iOS onboarding tagline with the macOS brand line ("开口即文字。")
- Rewrite README (Chinese-first, product-oriented) and refresh GitHub Pages
2026-07-11 19:10:20 +08:00

109 lines
3.7 KiB
Swift

// UsageStatCard.swift
// OSGKeyboard · Shared
//
// Single cumulative metric tile. Compact = title / value / caption stack;
// prominent = horizontal hero bar for the primary word-count metric.
import SwiftUI
public struct UsageStatCard: View {
@Environment(\.themePalette) private var palette
public let title: String
public let value: String
public let caption: String
public var systemImage: String?
public var accent: Bool
/// Hero metric: wide horizontal layout for the primary word count.
public var prominent: Bool
public init(
title: String,
value: String,
caption: String,
systemImage: String? = nil,
accent: Bool = false,
prominent: Bool = false
) {
self.title = title
self.value = value
self.caption = caption
self.systemImage = systemImage
self.accent = accent
self.prominent = prominent
}
public var body: some View {
UsageSurfaceCard(padding: Spacing.md) {
if prominent {
prominentBody
} else {
compactBody
}
}
}
private var compactBody: some View {
VStack(alignment: .leading, spacing: Spacing.xs) {
HStack {
Text(title.uppercased())
.font(TypeStyle.caption2)
.tracking(0.6)
.foregroundStyle(palette.textTertiary)
Spacer()
if let systemImage {
Image(systemName: systemImage)
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(accent ? palette.accent : palette.textTertiary)
.symbolRenderingMode(.hierarchical)
}
}
Text(value)
.font(TypeStyle.title2)
.foregroundStyle(accent ? palette.accent : palette.textPrimary)
.lineLimit(1)
.minimumScaleFactor(0.7)
.contentTransition(.numericText())
.animation(Motion.soft, value: value)
Text(caption)
.font(TypeStyle.caption)
.foregroundStyle(palette.textSecondary)
}
.frame(maxWidth: .infinity, alignment: .leading)
}
/// Wide "hero bar": icon badge + title/caption left, big number right.
private var prominentBody: some View {
HStack(spacing: Spacing.md) {
if let systemImage {
ZStack {
Circle()
.fill(palette.accentMuted)
.frame(width: 44, height: 44)
Image(systemName: systemImage)
.font(.system(size: 17, weight: .semibold))
.foregroundStyle(palette.accent)
.symbolRenderingMode(.hierarchical)
}
}
VStack(alignment: .leading, spacing: 2) {
Text(title.uppercased())
.font(TypeStyle.caption2)
.tracking(0.6)
.foregroundStyle(palette.textTertiary)
Text(caption)
.font(TypeStyle.caption)
.foregroundStyle(palette.textSecondary)
}
Spacer(minLength: Spacing.md)
Text(value)
.font(.system(size: 34, weight: .bold))
.foregroundStyle(accent ? palette.accent : palette.textPrimary)
.lineLimit(1)
.minimumScaleFactor(0.6)
.contentTransition(.numericText())
.animation(Motion.soft, value: value)
}
}
}