feat(keyboard): ship AI hint carousel, home library cards, and clipboard polish

Rotate AI idle suggestions with optional remote packs, move history/dictionary onto self-sizing Home preview cards, harden clipboard capture/prompting, and simplify keyboard chrome by dropping most liquid-glass shadows.
This commit is contained in:
Rocky
2026-08-13 01:00:51 +08:00
parent fd6e0d3e7e
commit 9f308fadd2
202 changed files with 10897 additions and 5962 deletions
@@ -1,5 +1,5 @@
// SevenDayUsageChart.swift
// OSGKeyboard · Shared
// OSGKeyboard · HostSupport
//
// 7-day dictation bar chart. Platform shells wrap this in their own page
// layout; the chart itself only needs points + UI language.
@@ -1,5 +1,5 @@
// SupportDeveloperSection.swift
// OSGKeyboard · Shared
// OSGKeyboard · HostSupport
//
// Optional voluntary tip block for Settings. Does not gate features.
@@ -1,29 +1,32 @@
// UsageStatsCluster.swift
// OSGKeyboard · Shared
// OSGKeyboard · HostSupport
//
// Cross-platform home / dashboard stats: 7-day chart + cumulative metrics.
// Callers observe their store and pass plain values Shared stays unbound
// from platform singletons.
//
// Optional `header` sits above the 7-day chart inside the same surface card
// (iOS Home glass preview field). Mac / plain call sites keep `EmptyView`.
import SwiftUI
#if canImport(OSGKeyboardShared)
import OSGKeyboardShared
#endif
public struct UsageStatsCluster: View {
@Environment(\.themePalette) private var palette
public enum Layout: Sendable, Equatable {
/// Chart left, 2×2 `UsageStatCard` grid right (Mac / iPad).
case split
/// Chart above a compact single-card 2×2 grid (iPhone).
case stacked
}
public enum UsageStatsClusterLayout: Sendable, Equatable {
/// Chart left, 2×2 `UsageStatCard` grid right (Mac / iPad).
case split
/// Chart above a compact single-card 2×2 grid (iPhone).
case stacked
/// 2×2 沿 HomeStatsCard
static let compactGridHeight: CGFloat = 166
public static let compactGridHeight: CGFloat = 166
}
public let layout: Layout
public struct UsageStatsCluster<Header: View>: View {
@Environment(\.themePalette) private var palette
public let layout: UsageStatsClusterLayout
public let language: AppUILanguage
public let points: [UsageStatisticsStore.DailyUsagePoint]
public let dictationCharacterCount: Int
@@ -32,16 +35,18 @@ public struct UsageStatsCluster: View {
public let dictionaryTermCount: Int
/// iPhone SE stacked
public let compact: Bool
private let header: Header
public init(
layout: Layout,
layout: UsageStatsClusterLayout,
language: AppUILanguage,
points: [UsageStatisticsStore.DailyUsagePoint],
dictationCharacterCount: Int,
dictationDurationSeconds: TimeInterval,
translationCharacterCount: Int,
dictionaryTermCount: Int,
compact: Bool = false
compact: Bool = false,
@ViewBuilder header: () -> Header
) {
self.layout = layout
self.language = language
@@ -51,6 +56,7 @@ public struct UsageStatsCluster: View {
self.translationCharacterCount = translationCharacterCount
self.dictionaryTermCount = dictionaryTermCount
self.compact = compact
self.header = header()
}
public var body: some View {
@@ -66,7 +72,7 @@ public struct UsageStatsCluster: View {
private var splitBody: some View {
HStack(alignment: .top, spacing: Spacing.md) {
SevenDayUsageChart(points: points, language: language)
chartCard
.frame(maxWidth: .infinity, maxHeight: .infinity)
splitStatGrid
.frame(maxWidth: .infinity)
@@ -112,13 +118,34 @@ public struct UsageStatsCluster: View {
private var stackedBody: some View {
VStack(spacing: compact ? Spacing.sm : Spacing.md) {
chartCard
compactStatGrid
}
}
/// Chart surface; when `header` is present it sits above the bars in the same card.
@ViewBuilder
private var chartCard: some View {
if Header.self == EmptyView.self {
SevenDayUsageChart(
points: points,
language: language,
chartMinHeight: compact ? 72 : 96,
expands: false
expands: layout == .split
)
compactStatGrid
} else {
UsageSurfaceCard(padding: Spacing.md) {
VStack(alignment: .leading, spacing: Spacing.sm) {
header
SevenDayUsageChart(
points: points,
language: language,
chartMinHeight: compact ? 72 : 96,
embedsInCard: false,
expands: layout == .split
)
}
}
}
}
@@ -156,7 +183,7 @@ public struct UsageStatsCluster: View {
}
}
// HomeStatsCard 166pt
.frame(height: UsageStatsCluster.compactGridHeight)
.frame(height: UsageStatsClusterLayout.compactGridHeight)
.background(palette.surface)
.clipShape(RoundedRectangle(cornerRadius: Radius.xl, style: .continuous))
.overlay(
@@ -197,3 +224,28 @@ public struct UsageStatsCluster: View {
.padding(Spacing.md)
}
}
extension UsageStatsCluster where Header == EmptyView {
public init(
layout: UsageStatsClusterLayout,
language: AppUILanguage,
points: [UsageStatisticsStore.DailyUsagePoint],
dictationCharacterCount: Int,
dictationDurationSeconds: TimeInterval,
translationCharacterCount: Int,
dictionaryTermCount: Int,
compact: Bool = false
) {
self.init(
layout: layout,
language: language,
points: points,
dictationCharacterCount: dictationCharacterCount,
dictationDurationSeconds: dictationDurationSeconds,
translationCharacterCount: translationCharacterCount,
dictionaryTermCount: dictionaryTermCount,
compact: compact,
header: { EmptyView() }
)
}
}