fix(ipad): ship iPad P0 layout/globe fixes, edit-last-input, drop clipboard commands

Adapt typing/voice surfaces for iPad width and height, add the system globe
key and last-input editing flow, harden host-only Rime deployment, and remove
clipboard voice commands. Bump build to 61.
This commit is contained in:
Rocky
2026-08-10 13:50:50 +08:00
parent 53abad2050
commit 2bc8c1b87d
85 changed files with 5703 additions and 3997 deletions
@@ -0,0 +1,77 @@
// EditUsageMetricsStore.swift
// OSGKeyboard · Shared
//
// Separate counters so editing never inflates ordinary dictation characters.
import Foundation
public struct EditUsageMetrics: Codable, Equatable, Sendable {
public var enteredCount = 0
public var replacedCount = 0
public var appendedCount = 0
public var cancelledCount = 0
public var failedCount = 0
public var instructionDurationSeconds: TimeInterval = 0
public var updatedAt = Date()
}
public enum EditUsageMetricsStore {
public enum Outcome: Sendable {
case entered
case replaced
case appended
case cancelled
case failed
}
private static let key = "editLastInput.usageMetrics.v1"
public static func record(
_ outcome: Outcome,
instructionDuration: TimeInterval = 0,
defaults: UserDefaults? = nil
) {
guard let store = defaults ?? AppGroup.defaultsIfAvailable else { return }
var metrics = load(defaults: store)
switch outcome {
case .entered: metrics.enteredCount += 1
case .replaced: metrics.replacedCount += 1
case .appended: metrics.appendedCount += 1
case .cancelled: metrics.cancelledCount += 1
case .failed: metrics.failedCount += 1
}
metrics.instructionDurationSeconds += max(0, instructionDuration)
metrics.updatedAt = Date()
if let data = try? JSONEncoder().encode(metrics) {
store.set(data, forKey: key)
store.synchronize()
}
}
public static func load(defaults: UserDefaults? = nil) -> EditUsageMetrics {
guard let store = defaults ?? AppGroup.defaultsIfAvailable,
let data = store.data(forKey: key),
let metrics = try? JSONDecoder().decode(EditUsageMetrics.self, from: data)
else {
return EditUsageMetrics()
}
return metrics
}
public static func recordInstructionDuration(
_ duration: TimeInterval,
defaults: UserDefaults? = nil
) {
guard duration > 0,
let store = defaults ?? AppGroup.defaultsIfAvailable else {
return
}
var metrics = load(defaults: store)
metrics.instructionDurationSeconds += duration
metrics.updatedAt = Date()
if let data = try? JSONEncoder().encode(metrics) {
store.set(data, forKey: key)
store.synchronize()
}
}
}