chore(release): bump version to 1.0.1 (build 27)

Release the current PiP reliability, polish style, macOS dictionary, and UI updates.
This commit is contained in:
Rocky
2026-07-27 00:09:30 +08:00
parent 5b1283c3ed
commit 956331a2af
41 changed files with 1241 additions and 295 deletions
+21
View File
@@ -14,6 +14,8 @@ import SwiftUI
/// Fixed metrics that keep every desktop surface on the same grid.
enum MacMetrics {
/// Shared height for search fields and primary actions in page headers.
static let pageHeaderControlHeight: CGFloat = 28
/// Shared height for credential inputs and icon buttons matches the iOS
/// settings controls (38).
static let settingsControlHeight: CGFloat = 38
@@ -443,6 +445,25 @@ struct MacSettingRow<Content: View>: View {
// MARK: - Page header
/// Capsule-shaped primary action aligned with page-header search controls.
struct MacHeaderActionButtonStyle: ButtonStyle {
@Environment(\.themePalette) private var palette
@Environment(\.isEnabled) private var isEnabled
func makeBody(configuration: Configuration) -> some View {
configuration.label
.padding(.horizontal, Spacing.md)
.frame(height: MacMetrics.pageHeaderControlHeight)
.foregroundStyle(.white)
.background(
palette.accent.opacity(configuration.isPressed ? 0.82 : 1),
in: Capsule()
)
.contentShape(Capsule())
.opacity(isEnabled ? 1 : 0.45)
}
}
/// Page title for History / Dictionary / Settings. Applies the shared
/// `pageHorizontalInset` so its left edge matches inset card content below.
/// Type size matches Home's brand line (`TypeStyle.pageTitle`).
+111 -4
View File
@@ -11,6 +11,10 @@ struct MacDictionaryView: View {
@Environment(\.themePalette) private var palette
@State private var query = ""
@State private var entryPendingDeletion: PersonalDictionary.Entry?
@State private var showEntryEditor = false
@State private var generatingAliasEntryIDs: Set<UUID> = []
private let aliasGenerator = DictionaryAliasGenerator()
private var lang: AppUILanguage { viewModel.config.uiLanguage }
@@ -49,8 +53,19 @@ struct MacDictionaryView: View {
title: MacL10n.string("mac.section.dictionary", language: lang),
subtitle: MacL10n.string("mac.page.dictionary.subtitle", language: lang)
) {
if !entries.isEmpty {
searchField
HStack(spacing: Spacing.sm) {
if !entries.isEmpty {
searchField
}
Button {
showEntryEditor = true
} label: {
Label(
MacL10n.string("mac.dict.add", language: lang),
systemImage: "plus"
)
}
.buttonStyle(MacHeaderActionButtonStyle())
}
}
@@ -67,6 +82,11 @@ struct MacDictionaryView: View {
}
.background(palette.background)
.animation(Motion.soft, value: entries.isEmpty)
.sheet(isPresented: $showEntryEditor) {
MacDictionaryEntryEditor(language: lang) { term in
saveManualEntry(term: term)
}
}
.task {
await MacICloudSyncBootstrap.dictionarySync.pullAndMergeIfEnabled()
viewModel.refreshDictionaryFromCloud()
@@ -154,8 +174,7 @@ struct MacDictionaryView: View {
.font(TypeStyle.footnote)
}
.padding(.horizontal, Spacing.sm)
.padding(.vertical, 6)
.frame(width: 220)
.frame(width: 220, height: MacMetrics.pageHeaderControlHeight)
.background(palette.surface, in: Capsule())
.overlay(Capsule().stroke(palette.divider, lineWidth: 0.5))
}
@@ -178,6 +197,8 @@ struct MacDictionaryView: View {
}
if !entry.aliases.isEmpty {
parts.append(entry.aliases.joined(separator: " / "))
} else if generatingAliasEntryIDs.contains(entry.id) {
parts.append(MacL10n.string("mac.dict.aliasesGenerating", language: lang))
}
return parts.isEmpty ? nil : parts.joined(separator: " · ")
}
@@ -205,11 +226,97 @@ struct MacDictionaryView: View {
private func delete(_ entry: PersonalDictionary.Entry) {
let store = AppGroupStore(defaults: viewModel.defaults)
store.deletePersonalDictionaryEntry(id: entry.id)
generatingAliasEntryIDs.remove(entry.id)
viewModel.refreshDictionaryFromCloud()
Task {
try? await MacICloudSyncBootstrap.dictionarySync.pushLocalIfEnabled(store.personalDictionary)
}
}
private func saveManualEntry(term: String) {
let store = AppGroupStore(defaults: viewModel.defaults)
var dictionary = store.personalDictionary
guard let saved = dictionary.upsertManual(term: term) else { return }
dictionary.version += 1
store.setPersonalDictionary(dictionary)
viewModel.refreshDictionaryFromCloud()
generatingAliasEntryIDs.insert(saved.id)
Task {
try? await MacICloudSyncBootstrap.dictionarySync.pushLocalIfEnabled(dictionary)
let aliases = await aliasGenerator.generateAliases(for: saved.term)
generatingAliasEntryIDs.remove(saved.id)
guard !aliases.isEmpty else { return }
var latest = store.personalDictionary
guard latest.entries.contains(where: {
$0.id == saved.id && $0.term == saved.term
}) else { return }
latest.updateAliases(for: saved.id, aliases: aliases)
latest.version += 1
store.setPersonalDictionary(latest)
viewModel.refreshDictionaryFromCloud()
try? await MacICloudSyncBootstrap.dictionarySync.pushLocalIfEnabled(latest)
}
}
}
private struct MacDictionaryEntryEditor: View {
let language: AppUILanguage
let onSave: (String) -> Void
@Environment(\.dismiss) private var dismiss
@Environment(\.themePalette) private var palette
@State private var term = ""
@FocusState private var termFocused: Bool
private var trimmedTerm: String {
term.trimmingCharacters(in: .whitespacesAndNewlines)
}
var body: some View {
VStack(alignment: .leading, spacing: Spacing.md) {
Text(MacL10n.string("mac.dict.add", language: language))
.font(TypeStyle.title2)
TextField(
MacL10n.string("mac.dict.addField", language: language),
text: $term
)
.textFieldStyle(.roundedBorder)
.focused($termFocused)
.onSubmit(save)
Text(MacL10n.string("mac.dict.addFooter", language: language))
.font(TypeStyle.caption2)
.foregroundStyle(palette.textTertiary)
.fixedSize(horizontal: false, vertical: true)
HStack {
Spacer()
Button(MacL10n.string("mac.cancel", language: language)) {
dismiss()
}
Button(MacL10n.string("mac.save", language: language), action: save)
.buttonStyle(.borderedProminent)
.tint(palette.accent)
.disabled(trimmedTerm.isEmpty)
}
}
.padding(Spacing.xl)
.frame(width: 440)
.background(palette.background)
.onAppear {
termFocused = true
}
}
private func save() {
guard !trimmedTerm.isEmpty else { return }
onSave(trimmedTerm)
dismiss()
}
}
private struct MacDictionaryRow: View {
@@ -25,7 +25,7 @@ actor MacMLXStreamingASRProvider {
locale: Locale
) async throws -> MacMLXStreamingSession {
let qwen = try await loadModel(model)
var config = StreamingConfig(
let config = StreamingConfig(
decodeIntervalSeconds: 0.5,
boundaryDecodeIntervalSeconds: 0.2,
boundaryBoostSeconds: 1.0,
+162 -53
View File
@@ -24,6 +24,15 @@ struct MacPolishStylesView: View {
_ = viewModel.polishStylesRevision
return store.activePolishStyleId
}
private var columns: [GridItem] {
[
GridItem(
.adaptive(minimum: 240, maximum: 360),
spacing: Spacing.md,
alignment: .top
),
]
}
var body: some View {
VStack(spacing: 0) {
@@ -40,7 +49,7 @@ struct MacPolishStylesView: View {
systemImage: "plus"
)
}
.buttonStyle(.borderedProminent)
.buttonStyle(MacHeaderActionButtonStyle())
.disabled(catalog.entries.count >= PolishStyleLimits.maximumUserPacks)
}
@@ -96,72 +105,52 @@ struct MacPolishStylesView: View {
.font(MacSettingsType.sectionTitle)
.foregroundStyle(palette.textSecondary)
.textCase(.uppercase)
MacCard(padding: 0) {
VStack(spacing: 0) {
ForEach(packs) { pack in
styleRow(pack)
if pack.id != packs.last?.id {
Divider().background(palette.divider)
}
}
LazyVGrid(columns: columns, alignment: .leading, spacing: Spacing.md) {
ForEach(packs) { pack in
styleCard(pack)
}
}
}
}
private func styleRow(_ pack: PolishStylePack) -> some View {
HStack(spacing: Spacing.md) {
Button {
private func styleCard(_ pack: PolishStylePack) -> some View {
MacPolishStyleCard(
name: pack.displayName(language: lang),
subtitle: subtitle(for: pack),
iconName: iconName(for: pack),
isSelected: pack.id == activeID,
isUserStyle: pack.kind == .user,
language: lang,
activate: {
activate(pack)
} label: {
HStack(spacing: Spacing.md) {
Image(systemName: pack.id == activeID ? "checkmark.circle.fill" : "circle")
.foregroundStyle(pack.id == activeID ? palette.accent : palette.textTertiary)
VStack(alignment: .leading, spacing: 2) {
Text(pack.displayName(language: lang))
.font(TypeStyle.body)
.foregroundStyle(palette.textPrimary)
Text(subtitle(for: pack))
.font(TypeStyle.caption2)
.foregroundStyle(palette.textTertiary)
.lineLimit(1)
}
Spacer()
}
.contentShape(Rectangle())
}
.buttonStyle(.plain)
Button {
},
duplicate: {
editingPack = PolishStylePack(
name: "\(pack.displayName(language: lang)) \(MacL10n.string("mac.styles.copy", language: lang))",
prompt: pack.prompt
)
showEditor = true
} label: {
Image(systemName: "plus.square.on.square")
},
edit: {
editingPack = pack
showEditor = true
},
delete: {
delete(pack)
}
.buttonStyle(.borderless)
)
}
if pack.kind == .user {
Button {
editingPack = pack
showEditor = true
} label: {
Image(systemName: "pencil")
}
.buttonStyle(.borderless)
Button(role: .destructive) {
delete(pack)
} label: {
Image(systemName: "trash")
}
.buttonStyle(.borderless)
}
private func iconName(for pack: PolishStylePack) -> String {
switch pack.id {
case "builtin.structured": return "list.bullet.rectangle"
case "builtin.formal": return "briefcase"
case "builtin.dating": return "heart.text.square"
case "builtin.chat": return "bubble.left.and.bubble.right"
case "builtin.light": return "wand.and.sparkles"
default: return "text.badge.star"
}
.padding(.horizontal, Spacing.md)
.padding(.vertical, Spacing.sm)
}
private func subtitle(for pack: PolishStylePack) -> String {
@@ -210,6 +199,126 @@ struct MacPolishStylesView: View {
}
}
private struct MacPolishStyleCard: View {
let name: String
let subtitle: String
let iconName: String
let isSelected: Bool
let isUserStyle: Bool
let language: AppUILanguage
let activate: () -> Void
let duplicate: () -> Void
let edit: () -> Void
let delete: () -> Void
@Environment(\.themePalette) private var palette
@State private var isHovering = false
private let shape = RoundedRectangle(cornerRadius: Radius.xl, style: .continuous)
var body: some View {
ZStack(alignment: .topTrailing) {
Button(action: activate) {
VStack(alignment: .leading, spacing: Spacing.sm) {
Image(systemName: iconName)
.font(.system(size: 24, weight: .medium))
.foregroundStyle(isSelected ? palette.accent : palette.textSecondary)
Spacer(minLength: Spacing.xs)
Text(name)
.font(TypeStyle.bodyEmph)
.foregroundStyle(palette.textPrimary)
.lineLimit(1)
Text(subtitle)
.font(TypeStyle.caption2)
.foregroundStyle(palette.textTertiary)
.lineLimit(2)
.fixedSize(horizontal: false, vertical: true)
}
.frame(maxWidth: .infinity, minHeight: 132, alignment: .leading)
.padding(Spacing.md)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
actionButtons
.padding(Spacing.sm)
if isSelected {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 21, weight: .semibold))
.foregroundStyle(palette.accent)
.background(palette.surface, in: Circle())
.padding(Spacing.sm)
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottomTrailing)
.allowsHitTesting(false)
}
}
.background(
isSelected ? palette.accentMuted : palette.surface,
in: shape
)
.overlay(
shape.stroke(
isSelected ? palette.accent : hoverBorder,
lineWidth: isSelected ? 1.5 : 0.5
)
)
.clipShape(shape)
.scaleEffect(isHovering ? 1.01 : 1)
.animation(Motion.quick, value: isHovering)
.animation(Motion.quick, value: isSelected)
.onHover { isHovering = $0 }
}
private var actionButtons: some View {
HStack(spacing: Spacing.xxs) {
cardActionButton(
systemImage: "plus.square.on.square",
accessibilityLabel: MacL10n.string("mac.styles.copy", language: language),
action: duplicate
)
if isUserStyle {
cardActionButton(
systemImage: "pencil",
accessibilityLabel: MacL10n.string("mac.styles.edit", language: language),
action: edit
)
cardActionButton(
systemImage: "trash",
accessibilityLabel: MacL10n.string("mac.delete", language: language),
foreground: palette.danger,
action: delete
)
}
}
}
private func cardActionButton(
systemImage: String,
accessibilityLabel: String,
foreground: Color? = nil,
action: @escaping () -> Void
) -> some View {
Button(action: action) {
Image(systemName: systemImage)
.font(.system(size: 12, weight: .semibold))
.foregroundStyle(foreground ?? palette.textSecondary)
.frame(width: 28, height: 28)
.background(palette.background.opacity(isHovering ? 0.9 : 0.72), in: Circle())
}
.buttonStyle(.plain)
.accessibilityLabel(accessibilityLabel)
}
private var hoverBorder: Color {
isHovering ? palette.dividerStrong : palette.divider
}
}
private struct MacPolishStyleEditor: View {
let pack: PolishStylePack?
let language: AppUILanguage