feat: add streaming cloud ASR, polish routing, and settings card layout

Unify Bailian/Volcengine/OpenAI realtime streaming, ABE polish routing with
fun styles, and a shared card-page Settings hierarchy; bump to 1.1 (build 32).
This commit is contained in:
Rocky
2026-07-28 20:25:17 +08:00
parent 956331a2af
commit d656bac8c3
58 changed files with 4153 additions and 1396 deletions
+19 -44
View File
@@ -7,34 +7,37 @@
import SwiftUI
import OSGKeyboardShared
struct EnginePickerSection: View {
struct EnginePickerSection<ConfigurationRows: View>: View {
@Environment(\.themePalette) private var palette: ThemePalette
@ObservedObject var config: ProviderConfig
private let configurationRows: ConfigurationRows
init(
config: ProviderConfig,
@ViewBuilder configurationRows: () -> ConfigurationRows
) {
self.config = config
self.configurationRows = configurationRows()
}
var body: some View {
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) {
sectionHeader("settings.engine.title")
CardSection("settings.engine.title") {
VStack(spacing: 0) {
engineOptionRow(
id: "local",
systemIcon: "iphone.badge.checkmark",
title: AppL10n.string("settings.engine.local.title"),
subtitle: localSubtitle
)
Divider().background(palette.divider)
engineOptionRow(
id: "cloud",
systemIcon: "wand.and.stars",
title: AppL10n.string("settings.engine.cloud.title"),
subtitle: AppL10n.string("settings.engine.cloud.subtitle")
)
configurationRows
}
.background(palette.surface, in: RoundedRectangle(cornerRadius: Radius.large, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: Radius.large, style: .continuous)
.stroke(palette.divider, lineWidth: 0.5)
)
.surfaceCard()
}
}
@@ -44,8 +47,6 @@ struct EnginePickerSection: View {
private func engineOptionRow(
id: String,
assetName: String? = nil,
systemIcon: String? = nil,
title: String,
subtitle: String
) -> some View {
@@ -55,11 +56,6 @@ struct EnginePickerSection: View {
selectEngine(id)
} label: {
HStack(spacing: Spacing.sm) {
engineMark(
assetName: assetName,
systemIcon: systemIcon,
isSelected: isSelected
)
VStack(alignment: .leading, spacing: 2) {
Text(title)
.font(TypeStyle.body)
@@ -90,33 +86,12 @@ struct EnginePickerSection: View {
}
}
@ViewBuilder
private func engineMark(assetName: String?, systemIcon: String?, isSelected: Bool) -> some View {
ZStack {
Circle()
.fill(isSelected ? palette.accentMuted : palette.surfaceElevated)
.frame(width: 32, height: 32)
if let assetName {
Image(assetName)
.resizable()
.scaledToFit()
.frame(width: 18, height: 18)
.foregroundStyle(isSelected ? palette.accent : palette.textPrimary)
} else if let systemIcon {
Image(systemName: systemIcon)
.font(.system(size: 16, weight: .medium))
.foregroundStyle(isSelected ? palette.accent : palette.textSecondary)
}
}
.frame(width: 32, height: 32)
}
}
@ViewBuilder
private func sectionHeader(_ title: LocalizedStringKey) -> some View {
Text(title)
.font(TypeStyle.caption2)
.foregroundStyle(palette.textSecondary)
.textCase(.uppercase)
.frame(maxWidth: .infinity, alignment: .leading)
extension EnginePickerSection where ConfigurationRows == EmptyView {
init(config: ProviderConfig) {
self.init(config: config) {
EmptyView()
}
}
}