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
+34 -19
View File
@@ -8,6 +8,7 @@ import OSGKeyboardShared
struct KeyboardSurfaceRoot: View {
@Environment(\.colorScheme) private var colorScheme
@Namespace private var keyboardTabSelectionNamespace
@ObservedObject var state: KeyboardState
@ObservedObject var typing: TypingSessionController
@@ -68,6 +69,16 @@ struct KeyboardSurfaceRoot: View {
// Overlays are siblings of the surfaces, so the palette has to be
// injected here or they fall back to the environment's dark default.
.environment(\.themePalette, palette)
// The input surfaces are replaced when switching tabs. Keep one
// namespace above that switch so the selected glass pill can morph
// between the outgoing and incoming top-control instances.
.environment(\.keyboardTabSelectionNamespace, keyboardTabSelectionNamespace)
// Bottom-anchored on purpose: UIKit hands the input view a container up
// to the full screen height while the keyboard slides in, and centering
// a fixed-height surface in it parks the whole keyboard above the
// visible slot which reads as a blank keyboard whenever that frame
// lingers (a slow Universal Clipboard read, a system alert).
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)
.animation(.easeInOut(duration: 0.15), value: state.surface)
.onChange(of: state.surface) { _, newSurface in
if newSurface != .typing {
@@ -90,26 +101,30 @@ struct KeyboardSurfaceRoot: View {
@ViewBuilder
private var clipboardOverlayLayer: some View {
switch state.clipboardOverlay {
case .none:
if !state.canShowClipboardEntry {
EmptyView()
case .enableGuide:
ClipboardEnableGuideView(
onClose: state.dismissClipboardOverlay,
onOpenSettings: {
state.dismissClipboardOverlay()
state.openClipboardSettings()
}
)
case .historyPanel:
ClipboardHistoryPanelView(
history: ClipboardHistoryStore.shared,
onClose: state.dismissClipboardOverlay,
onClear: state.clearClipboardHistory,
onInsert: { state.insertClipboardText($0) },
onDelete: { state.deleteClipboardHistoryEntry($0) },
pastePermissionHint: nil
)
} else {
switch state.clipboardOverlay {
case .none:
EmptyView()
case .enableGuide:
ClipboardEnableGuideView(
onClose: state.dismissClipboardOverlay,
onOpenSettings: {
state.dismissClipboardOverlay()
state.openClipboardSettings()
}
)
case .historyPanel:
ClipboardHistoryPanelView(
history: ClipboardHistoryStore.shared,
onClose: state.dismissClipboardOverlay,
onClear: state.clearClipboardHistory,
onInsert: { state.insertClipboardText($0) },
onDelete: { state.deleteClipboardHistoryEntry($0) },
pastePermissionHint: nil
)
}
}
}
}
+4 -6
View File
@@ -122,7 +122,9 @@ struct TypingRootView: View {
if hasCandidateContent {
// Composing Chinese/English candidates hide the clipboard strip.
candidateBar
} else if let suggestion = state.clipboardSuggestionText, !suggestion.isEmpty {
} else if state.canShowClipboardEntry,
let suggestion = state.clipboardSuggestionText,
!suggestion.isEmpty {
// Same slot as logo + capsule tabs hide chrome until dismissed.
ClipboardSuggestionBar(
text: suggestion,
@@ -548,6 +550,7 @@ struct TypingRootView: View {
)
.fill(visualKeyFill(for: key, pressed: showPressed))
)
// NativeKeyboardKeySurface +
.overlay(
RoundedRectangle(
cornerRadius: TypingLayoutMetrics.keyCornerRadius,
@@ -555,11 +558,6 @@ struct TypingRootView: View {
)
.stroke(visualKeyBorder(for: key), lineWidth: 0.5)
)
.shadow(
color: Color.black.opacity(showPressed ? 0.04 : 0.13),
radius: showPressed ? 0.5 : 1,
y: showPressed ? 0 : 1
)
.scaleEffect(pressed ? 0.98 : 1)
.animation(.easeOut(duration: 0.08), value: pressed)
.accessibilityElement()