Add dictation-locale transcription (incl. Russian), rework list capture UI

- TranscriptionSettings/Service route per-locale to SpeechTranscriber or
  DictationTranscriber (union of both supported sets, speech preferred);
  neutral-confidence ranking fallback keeps mixed-engine auto-pick working
- NotesListView: text + voice capture buttons above the list (glassProminent),
  search moved to the iOS 26 toolbar search accessory (.minimize)
- Changelog entries for languages, capture buttons, search placement
This commit is contained in:
2026-07-15 15:31:03 -04:00
parent 3a9c36d843
commit 8bdda913a7
6 changed files with 313 additions and 76 deletions
+73 -48
View File
@@ -16,57 +16,20 @@ struct NotesListView: View {
var body: some View {
NavigationStack {
List {
if let error = syncEngine.lastSyncError {
Section {
Label(error, systemImage: "exclamationmark.icloud")
.foregroundStyle(.red)
.font(.footnote)
}
}
if searchText.isEmpty, !relevantNotes.isEmpty {
Section("Right here, right now") {
ForEach(relevantNotes) { note in
row(note)
}
}
}
Section(searchText.isEmpty ? "All notes" : "Results") {
ForEach(filteredNotes) { note in
row(note)
}
}
VStack(spacing: 0) {
captureButtons
list
}
.navigationTitle("Notes")
// iOS 26: attached at the NavigationStack root inside a TabView, this
// renders as the Liquid-Glass toolbar search accessory (bottom, in the
// tab bar). `.searchToolbarBehavior(.minimize)` collapses it to the
// magnifier capsule until tapped a modifier that only exists on iOS,
// hence the guard; macOS keeps the standard toolbar search field.
.searchable(text: $searchText, prompt: "Search notes and tags")
.overlay {
if notes.isEmpty {
ContentUnavailableView(
"No notes yet",
systemImage: "square.and.pencil",
description: Text("Jot something down — Notes remembers where and when, so it can resurface at the right moment.")
)
}
}
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button {
recording = true
} label: {
Label("Record Note", systemImage: "mic")
}
}
ToolbarItem(placement: .primaryAction) {
Button {
composing = true
} label: {
Label("New Note", systemImage: "square.and.pencil")
}
.keyboardShortcut("n", modifiers: .command)
}
}
#if os(iOS)
.searchToolbarBehavior(.minimize)
#endif
.sheet(isPresented: $composing) {
NoteEditorView(mode: .create)
}
@@ -79,6 +42,68 @@ struct NotesListView: View {
}
}
/// The two prominent capture actions, side by side above the list the
/// primary way to start a note, promoted out of the toolbar. Glass-prominent
/// capsules inherit the app's red accent; each fills half the width.
private var captureButtons: some View {
HStack(spacing: 12) {
Button {
composing = true
} label: {
Label("Text", systemImage: "square.and.pencil")
.frame(maxWidth: .infinity)
}
.keyboardShortcut("n", modifiers: .command)
Button {
recording = true
} label: {
Label("Audio", systemImage: "mic.fill")
.frame(maxWidth: .infinity)
}
}
.buttonStyle(.glassProminent)
.controlSize(.large)
.padding(.horizontal)
.padding(.top, 4)
.padding(.bottom, 8)
}
private var list: some View {
List {
if let error = syncEngine.lastSyncError {
Section {
Label(error, systemImage: "exclamationmark.icloud")
.foregroundStyle(.red)
.font(.footnote)
}
}
if searchText.isEmpty, !relevantNotes.isEmpty {
Section("Right here, right now") {
ForEach(relevantNotes) { note in
row(note)
}
}
}
Section(searchText.isEmpty ? "All notes" : "Results") {
ForEach(filteredNotes) { note in
row(note)
}
}
}
.overlay {
if notes.isEmpty {
ContentUnavailableView(
"No notes yet",
systemImage: "square.and.pencil",
description: Text("Jot something down — Notes remembers where and when, so it can resurface at the right moment.")
)
}
}
}
private func row(_ note: NoteEntity) -> some View {
Button {
editingNote = note