cc8dd1070a
- 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
117 lines
4.4 KiB
Swift
117 lines
4.4 KiB
Swift
// OpenSourceLicensesView.swift
|
|
// OSGKeyboard · Main App
|
|
//
|
|
// Standard acknowledgements list: one tappable row per dependency,
|
|
// detail screen with the verbatim license text. Reached from
|
|
// Settings → About → "Third-Party Licenses".
|
|
|
|
import SwiftUI
|
|
import OSGKeyboardShared
|
|
|
|
struct OpenSourceLicensesView: View {
|
|
@Environment(\.themePalette) private var palette: ThemePalette
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: SettingsListMetrics.sectionLabelSpacing) {
|
|
Text("settings.licenses.footer")
|
|
.font(TypeStyle.caption2)
|
|
.foregroundStyle(palette.textTertiary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
.padding(.horizontal, Spacing.xs)
|
|
|
|
VStack(spacing: 0) {
|
|
ForEach(Array(OpenSourceLicenseCatalog.entries.enumerated()), id: \.element.id) { index, entry in
|
|
NavigationLink {
|
|
OpenSourceLicenseDetailView(entry: entry)
|
|
} label: {
|
|
licenseRow(entry)
|
|
}
|
|
.buttonStyle(.plain)
|
|
|
|
if index < OpenSourceLicenseCatalog.entries.count - 1 {
|
|
Divider().background(palette.divider)
|
|
}
|
|
}
|
|
}
|
|
.background(palette.surface, in: RoundedRectangle(cornerRadius: Radius.large, style: .continuous))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: Radius.large, style: .continuous)
|
|
.stroke(palette.divider, lineWidth: 0.5)
|
|
)
|
|
}
|
|
.padding(.horizontal, Spacing.lg)
|
|
.padding(.vertical, Spacing.md)
|
|
}
|
|
.background(palette.background.ignoresSafeArea())
|
|
.navigationTitle("settings.licenses.title")
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.hidesTabBarWhenPushed()
|
|
}
|
|
|
|
private func licenseRow(_ entry: OpenSourceLicenseCatalog.Entry) -> some View {
|
|
HStack(spacing: Spacing.xs) {
|
|
Text(entry.name)
|
|
.font(TypeStyle.caption2)
|
|
.foregroundStyle(palette.textPrimary)
|
|
.lineLimit(1)
|
|
.truncationMode(.middle)
|
|
Spacer(minLength: Spacing.xs)
|
|
Text(entry.licenseName)
|
|
.font(TypeStyle.caption2)
|
|
.foregroundStyle(palette.textTertiary)
|
|
Image(systemName: "chevron.right")
|
|
.font(.system(size: 11, weight: .semibold))
|
|
.foregroundStyle(palette.textTertiary)
|
|
}
|
|
.settingsListRow()
|
|
.contentShape(Rectangle())
|
|
}
|
|
}
|
|
|
|
// MARK: - Detail
|
|
|
|
private struct OpenSourceLicenseDetailView: View {
|
|
@Environment(\.themePalette) private var palette: ThemePalette
|
|
|
|
let entry: OpenSourceLicenseCatalog.Entry
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(alignment: .leading, spacing: Spacing.sm) {
|
|
if let url = entry.url {
|
|
Link(destination: url) {
|
|
HStack(spacing: Spacing.xs) {
|
|
Text(url.absoluteString)
|
|
.font(TypeStyle.caption2)
|
|
.foregroundStyle(palette.accent)
|
|
.lineLimit(2)
|
|
.multilineTextAlignment(.leading)
|
|
Spacer(minLength: 0)
|
|
MaterialIcon(name: .openInNew, size: 14)
|
|
.foregroundStyle(palette.textTertiary)
|
|
}
|
|
}
|
|
}
|
|
|
|
Text(entry.purpose)
|
|
.font(TypeStyle.caption2)
|
|
.foregroundStyle(palette.textSecondary)
|
|
.fixedSize(horizontal: false, vertical: true)
|
|
|
|
Text(entry.licenseText)
|
|
.font(TypeStyle.monoSmall)
|
|
.foregroundStyle(palette.textPrimary)
|
|
.textSelection(.enabled)
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
.padding(.horizontal, Spacing.lg)
|
|
.padding(.vertical, Spacing.md)
|
|
}
|
|
.background(palette.background.ignoresSafeArea())
|
|
.navigationTitle(entry.name)
|
|
.navigationBarTitleDisplayMode(.inline)
|
|
.hidesTabBarWhenPushed()
|
|
}
|
|
}
|