Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions apps/iOS/App/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@
system bar no longer applies. -->
<key>UIDesignRequiresCompatibility</key>
<true/>

<!-- The palette in ApproachNoteTheme is a single hardcoded light scheme, so
the app's own screens render light whatever the device is set to. Without
this key the system surfaces we don't draw — alerts, menus, the share
sheet, the keyboard — follow the device into dark mode and stop matching,
and brand-tinted alert buttons become navy on near-black. Declaring the
app light-only keeps system chrome in step with the palette. The Mac app
does the same with .preferredColorScheme(.light). -->
<key>UIUserInterfaceStyle</key>
<string>Light</string>

<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
Expand Down
36 changes: 16 additions & 20 deletions apps/iOS/Views/AuthorityRecommendationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -151,41 +151,37 @@ struct AuthorityRecommendationsView: View {
showingAddSheet = false
}
}
.confirmationDialog(
// Alerts rather than confirmationDialogs: on iPad a dialog is presented
// as a popover, and on iPadOS 26 (with the app's
// UIDesignRequiresCompatibility opt-out) that popover renders with no
// readable content. Alerts are unaffected.
.alert(
"Delete Authority Reference",
isPresented: $showingDeleteConfirmation,
titleVisibility: .visible
) {
presenting: deleteTarget
) { authority in
Button("Delete", role: .destructive) {
if let authority = deleteTarget {
Task { await deleteAuthority(authority) }
}
Task { await deleteAuthority(authority) }
}
Button("Cancel", role: .cancel) {
deleteTarget = nil
}
} message: {
if let authority = deleteTarget {
Text("Remove this \(authority.sourceDisplayName) reference from the recording?")
}
} message: { authority in
Text("Remove this \(authority.sourceDisplayName) reference from the recording?")
}
.confirmationDialog(
.alert(
"Link to This Recording",
isPresented: $showingLinkConfirmation,
titleVisibility: .visible
) {
presenting: linkTarget
) { authority in
Button("Link to \(albumTitle)") {
if let authority = linkTarget {
Task { await linkAuthority(authority) }
}
Task { await linkAuthority(authority) }
}
Button("Cancel", role: .cancel) {
linkTarget = nil
}
} message: {
if let authority = linkTarget {
Text("Link \"\(authority.artistName ?? "Unknown") - \(authority.albumTitle ?? "Unknown")\" to this recording?")
}
} message: { authority in
Text("Link \"\(authority.artistName ?? "Unknown") - \(authority.albumTitle ?? "Unknown")\" to this recording?")
}
.overlay {
if linkingInProgress {
Expand Down
53 changes: 31 additions & 22 deletions apps/iOS/Views/MusicBrainzSearchSheet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ struct MusicBrainzSearchSheet: View {
let onSongImported: () -> Void

@Environment(\.dismiss) private var dismiss
@Environment(\.openURL) private var openURL
@EnvironmentObject var authManager: AuthenticationManager
@StateObject private var musicBrainzService = MusicBrainzService()

Expand Down Expand Up @@ -73,31 +74,39 @@ struct MusicBrainzSearchSheet: View {
} message: {
Text(resultMessage)
}
.confirmationDialog(
"Request Song",
isPresented: .constant(selectedWork != nil && !isSubmitting),
titleVisibility: .visible
) {
if let work = selectedWork {
Button("Request \"\(work.title)\"") {
Task {
await requestSong(work)
}
}

if let url = URL(string: work.musicbrainzUrl) {
Link("View on MusicBrainz", destination: url)
}

Button("Cancel", role: .cancel) {
selectedWork = nil
}
}
// An alert rather than a confirmationDialog: on iPad a dialog is presented
// as a popover, and on iPadOS 26 (with the app's
// UIDesignRequiresCompatibility opt-out) that popover renders with no
// readable content. Alerts are unaffected, but SwiftUI only honours one
// alert per view, so this one hangs off the NavigationStack while the
// result alert above stays on its content. Alerts also only take buttons,
// so the MusicBrainz link opens through the environment.
.alert(
"Request Song",
isPresented: Binding(
get: { selectedWork != nil && !isSubmitting },
set: { if !$0 { selectedWork = nil } }
),
presenting: selectedWork
) { work in
Button("Request \"\(work.title)\"") {
Task {
await requestSong(work)
}
} message: {
if let work = selectedWork {
Text("Request that \"\(work.title)\" by \(work.composerDisplay) be added? We'll review it and add the song if it's a good fit.")
}

if let url = URL(string: work.musicbrainzUrl) {
Button("View on MusicBrainz") {
openURL(url)
}
}

Button("Cancel", role: .cancel) {
selectedWork = nil
}
} message: { work in
Text("Request that \"\(work.title)\" by \(work.composerDisplay) be added? We'll review it and add the song if it's a good fit.")
}
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
Expand Down
10 changes: 5 additions & 5 deletions apps/iOS/Views/RecordingContributionEditView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,11 @@ struct RecordingContributionEditView: View {
} message: {
Text(errorMessage)
}
.confirmationDialog(
"Delete Contribution",
isPresented: $showDeleteConfirmation,
titleVisibility: .visible
) {
// An alert rather than a confirmationDialog: on iPad a dialog is
// presented as a popover, and on iPadOS 26 (with the app's
// UIDesignRequiresCompatibility opt-out) that popover renders with no
// readable content. Alerts are unaffected.
.alert("Delete Contribution", isPresented: $showDeleteConfirmation) {
Button("Delete", role: .destructive) {
deleteContribution()
}
Expand Down
44 changes: 23 additions & 21 deletions apps/iOS/Views/SongDetailView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,6 @@ struct SongDetailView: View {
@State private var alertMessage = ""
@State private var isAddingToRepertoire = false

// Song refresh management
@State private var showRefreshConfirmation = false

// NEW: Toast notification
@State private var toast: ToastItem?

Expand Down Expand Up @@ -140,9 +137,29 @@ struct SongDetailView: View {
.font(ApproachNoteTheme.largeTitle(weight: .regular))
.foregroundColor(ApproachNoteTheme.textSecondary)
)
.onLongPressGesture {
if canQueueForRefresh {
showRefreshConfirmation = true
// Long-press the title to re-research the song. This used to be a
// confirmationDialog, but on iPad that presents as a popover, and
// on iPadOS 26 (with the app's UIDesignRequiresCompatibility
// opt-out) the popover renders as an empty panel over the tab bar
// with no readable content. A context menu draws its own chrome,
// anchors to the title, and is the natural match for a long press.
.contextMenu {
Section("Refresh Song Data") {
if canQueueForRefresh {
Button {
refreshSongData(forceRefresh: false)
} label: {
Label("Quick Refresh", systemImage: "arrow.clockwise")
}
Button {
refreshSongData(forceRefresh: true)
} label: {
Label("Full Refresh", systemImage: "arrow.triangle.2.circlepath")
}
Text("Quick uses cached data. Full re-fetches everything.")
} else {
Text("This song is already queued for research.")
}
}
}

Expand Down Expand Up @@ -346,21 +363,6 @@ struct SongDetailView: View {
} message: {
Text(alertMessage)
}
.confirmationDialog(
"Refresh Song Data",
isPresented: $showRefreshConfirmation,
titleVisibility: .visible
) {
Button("Quick Refresh") {
refreshSongData(forceRefresh: false)
}
Button("Full Refresh") {
refreshSongData(forceRefresh: true)
}
Button("Cancel", role: .cancel) { }
} message: {
Text("Quick refresh uses cached data for faster results. Full refresh re-fetches everything from external sources.")
}
.toast($toast)
.onDisappear {
viewModel.stopResearchStatusPolling()
Expand Down
10 changes: 5 additions & 5 deletions apps/iOS/Views/YouTubeImportView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -94,11 +94,11 @@ struct YouTubeImportView: View {
)
}
}
.confirmationDialog(
"Link to Recording?",
isPresented: $showingRecordingChoice,
titleVisibility: .visible
) {
// An alert rather than a confirmationDialog: on iPad a dialog is presented
// as a popover, and on iPadOS 26 (with the app's
// UIDesignRequiresCompatibility opt-out) that popover renders with no
// readable content. Alerts are unaffected.
.alert("Link to Recording?", isPresented: $showingRecordingChoice) {
Button("Pick a Recording") {
showingRecordingPicker = true
}
Expand Down
Loading