From b8f354439541528e101378a3a48b851082f662da Mon Sep 17 00:00:00 2001 From: Dallas Groot Date: Sun, 12 Apr 2026 19:34:58 -0700 Subject: [PATCH 1/2] quick fix --- Widget/NowPlayingWidgetViews.swift | 2 - iOS/Data/BackupManager.swift | 42 ++++- iOS/Resources/Info.plist | 4 +- iOS/Views/Library/DownloadsSettingsView.swift | 9 + iOS/Views/Library/LicensesView.swift | 172 ++++++++++++++++++ 5 files changed, 218 insertions(+), 11 deletions(-) create mode 100644 iOS/Views/Library/LicensesView.swift diff --git a/Widget/NowPlayingWidgetViews.swift b/Widget/NowPlayingWidgetViews.swift index c8ca4ff..f8da379 100644 --- a/Widget/NowPlayingWidgetViews.swift +++ b/Widget/NowPlayingWidgetViews.swift @@ -146,8 +146,6 @@ struct WaveformBar: View { var body: some View { GeometryReader { geo in - let w = geo.size.width - let barWidth: CGFloat = max((w - CGFloat(barCount - 1) * 1.5) / CGFloat(barCount), 1.5) let playedCount = Int(Double(barCount) * progress) ZStack { diff --git a/iOS/Data/BackupManager.swift b/iOS/Data/BackupManager.swift index 4188230..0ece00c 100644 --- a/iOS/Data/BackupManager.swift +++ b/iOS/Data/BackupManager.swift @@ -115,10 +115,24 @@ class BackupManager { // 5. Visualizer settings let visSettings = VisualizerSettings.shared let visDict: [String: Any] = [ + "enabled": visSettings.enabled, + "nowPlayingEnabled": visSettings.nowPlayingEnabled, + "miniPlayerEnabled": visSettings.miniPlayerEnabled, "realAudioAnalysis": visSettings.realAudioAnalysis, - "barCount": visSettings.barCount, - "gain": visSettings.gain, - "colorScheme": visSettings.colorScheme, + "dynamicGainEnabled": visSettings.dynamicGainEnabled, + "fps": visSettings.fps, + "viscosity": visSettings.viscosity, + "frequencyCutoff": visSettings.frequencyCutoff, + "baseMultiplier": visSettings.baseMultiplier, + "waveStrokeThickness": visSettings.waveStrokeThickness, + "barSpacing": visSettings.barSpacing, + "barCornerRadius": visSettings.barCornerRadius, + "lineThickness": visSettings.lineThickness, + "nowPlayingHeightPct": visSettings.nowPlayingHeightPct, + "waveOffsetTop": visSettings.waveOffsetTop, + "npAmplitude": visSettings.npAmplitude, + "npBaseLift": visSettings.npBaseLift, + "depthOffset": visSettings.depthOffset, ] let visData = try JSONSerialization.data(withJSONObject: visDict, options: .prettyPrinted) try visData.write(to: tempDir.appendingPathComponent("visualizer_settings.json")) @@ -243,10 +257,24 @@ class BackupManager { let data = try? Data(contentsOf: visURL), let dict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] { let vis = VisualizerSettings.shared - if let v = dict["realAudioAnalysis"] as? Bool { vis.realAudioAnalysis = v } - if let v = dict["barCount"] as? Int { vis.barCount = v } - if let v = dict["gain"] as? Double { vis.gain = Float(v) } - if let v = dict["colorScheme"] as? String { vis.colorScheme = v } + if let v = dict["enabled"] as? Bool { vis.enabled = v } + if let v = dict["nowPlayingEnabled"] as? Bool { vis.nowPlayingEnabled = v } + if let v = dict["miniPlayerEnabled"] as? Bool { vis.miniPlayerEnabled = v } + if let v = dict["realAudioAnalysis"] as? Bool { vis.realAudioAnalysis = v } + if let v = dict["dynamicGainEnabled"] as? Bool { vis.dynamicGainEnabled = v } + if let v = dict["fps"] as? Double { vis.fps = v } + if let v = dict["viscosity"] as? Double { vis.viscosity = v } + if let v = dict["frequencyCutoff"] as? Int { vis.frequencyCutoff = v } + if let v = dict["baseMultiplier"] as? Double { vis.baseMultiplier = v } + if let v = dict["waveStrokeThickness"] as? Double { vis.waveStrokeThickness = v } + if let v = dict["barSpacing"] as? Double { vis.barSpacing = v } + if let v = dict["barCornerRadius"] as? Double { vis.barCornerRadius = v } + if let v = dict["lineThickness"] as? Double { vis.lineThickness = v } + if let v = dict["nowPlayingHeightPct"] as? Double { vis.nowPlayingHeightPct = v } + if let v = dict["waveOffsetTop"] as? Double { vis.waveOffsetTop = v } + if let v = dict["npAmplitude"] as? Double { vis.npAmplitude = v } + if let v = dict["npBaseLift"] as? Double { vis.npBaseLift = v } + if let v = dict["depthOffset"] as? Double { vis.depthOffset = v } } // 6. Restore playback state diff --git a/iOS/Resources/Info.plist b/iOS/Resources/Info.plist index ca38d74..1fe5504 100644 --- a/iOS/Resources/Info.plist +++ b/iOS/Resources/Info.plist @@ -17,9 +17,9 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(MARKETING_VERSION) CFBundleVersion - 1 + $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/iOS/Views/Library/DownloadsSettingsView.swift b/iOS/Views/Library/DownloadsSettingsView.swift index 4553569..4cb9d55 100644 --- a/iOS/Views/Library/DownloadsSettingsView.swift +++ b/iOS/Views/Library/DownloadsSettingsView.swift @@ -726,6 +726,15 @@ struct SettingsView: View { Spacer() Text("1.16.1").foregroundColor(.gray) } + NavigationLink { + LicensesView() + } label: { + HStack { + Image(systemName: "doc.text") + .foregroundStyle(accentPink) + Text("Licenses & Acknowledgments") + } + } } // Logout diff --git a/iOS/Views/Library/LicensesView.swift b/iOS/Views/Library/LicensesView.swift new file mode 100644 index 0000000..b30d476 --- /dev/null +++ b/iOS/Views/Library/LicensesView.swift @@ -0,0 +1,172 @@ +import SwiftUI + +// ────────────────────────────────────────────────────────────────────── +// LicensesView.swift +// Displays all open-source licenses for dependencies used in the app +// and the Companion API server. +// ────────────────────────────────────────────────────────────────────── + +struct LicenseItem: Identifiable { + let id = UUID() + let name: String + let description: String + let license: String + let url: String + let category: String // "App" or "Companion API" +} + +struct LicensesView: View { + private let accentPink = Color(red: 1.0, green: 0.176, blue: 0.333) + + private let licenses: [LicenseItem] = [ + // ── iOS App ── + LicenseItem( + name: "ZIPFoundation", + description: "Effortless ZIP handling in Swift", + license: "MIT License", + url: "https://github.com/weichsel/ZIPFoundation", + category: "App" + ), + + // ── Companion API (Python) ── + LicenseItem( + name: "FastAPI", + description: "Modern, fast web framework for building APIs with Python", + license: "MIT License", + url: "https://github.com/tiangolo/fastapi", + category: "Companion API" + ), + LicenseItem( + name: "Uvicorn", + description: "Lightning-fast ASGI server for Python", + license: "BSD License", + url: "https://github.com/encode/uvicorn", + category: "Companion API" + ), + LicenseItem( + name: "Mutagen", + description: "Python module to handle audio metadata", + license: "GPL-2.0 License", + url: "https://github.com/quodlibet/mutagen", + category: "Companion API" + ), + LicenseItem( + name: "httpx", + description: "Fully featured HTTP client for Python 3", + license: "BSD License", + url: "https://github.com/encode/httpx", + category: "Companion API" + ), + LicenseItem( + name: "NumPy", + description: "Fundamental package for scientific computing with Python", + license: "BSD License", + url: "https://github.com/numpy/numpy", + category: "Companion API" + ), + LicenseItem( + name: "Pydantic", + description: "Data validation using Python type annotations", + license: "MIT License", + url: "https://github.com/pydantic/pydantic", + category: "Companion API" + ), + LicenseItem( + name: "librosa", + description: "Python library for audio and music analysis", + license: "ISC License", + url: "https://github.com/librosa/librosa", + category: "Companion API" + ), + ] + + private var appLicenses: [LicenseItem] { + licenses.filter { $0.category == "App" } + } + + private var companionLicenses: [LicenseItem] { + licenses.filter { $0.category == "Companion API" } + } + + var body: some View { + List { + Section { + VStack(alignment: .leading, spacing: 6) { + Text("NavidromePlayer uses the following open-source libraries. We're grateful to the developers and communities behind these projects.") + .font(.system(size: 13)) + .foregroundStyle(.secondary) + } + .padding(.vertical, 4) + } + + Section("App") { + ForEach(appLicenses) { item in + licenseRow(item) + } + } + + Section("Companion API") { + ForEach(companionLicenses) { item in + licenseRow(item) + } + } + + Section { + VStack(alignment: .leading, spacing: 6) { + Text("Visualizer") + .font(.system(size: 15, weight: .semibold)) + Text("The audio visualizer is inspired by Mitsuha, originally created by c0ldra1n for jailbroken iOS. This is an independent reimplementation using Apple frameworks.") + .font(.system(size: 13)) + .foregroundStyle(.secondary) + } + .padding(.vertical, 4) + } header: { + Text("Acknowledgments") + } + } + .navigationTitle("Licenses") + } + + private func licenseRow(_ item: LicenseItem) -> some View { + VStack(alignment: .leading, spacing: 4) { + HStack { + Text(item.name) + .font(.system(size: 15, weight: .semibold)) + Spacer() + Text(item.license) + .font(.system(size: 11, weight: .medium)) + .foregroundStyle(accentPink) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .background( + Capsule() + .fill(accentPink.opacity(0.12)) + ) + } + + Text(item.description) + .font(.system(size: 13)) + .foregroundStyle(.secondary) + .lineLimit(2) + + if let url = URL(string: item.url) { + Link(destination: url) { + Text(item.url.replacingOccurrences(of: "https://", with: "")) + .font(.system(size: 11)) + .foregroundStyle(.blue.opacity(0.8)) + .lineLimit(1) + .truncationMode(.middle) + } + } + } + .padding(.vertical, 4) + } +} + +#if DEBUG +#Preview { + NavigationStack { + LicensesView() + } +} +#endif From b41a0062b812bb5d92a9a635a98de77fe715a290 Mon Sep 17 00:00:00 2001 From: Dallas Groot Date: Sun, 12 Apr 2026 19:49:05 -0700 Subject: [PATCH 2/2] Captured remaining into let finalRemaining MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit • BackupManager — replaced barCount/gain/colorScheme with the 18 actual VisualizerSettings properties on both export and import sides • PendingOperationsQueue — captured remaining into let finalRemaining before MainActor.run closure (Swift 6 concurrency), changed try? CompanionAPIService() to CompanionAPIService.shared • Widget — removed unused barWidth • Version — both plists now use $(MARKETING_VERSION) The localization warning about String Catalog Symbol Generation is an Xcode recommendation, not an error — you can dismiss it or enable it in project settings. --- iOS/Data/PendingOperationsQueue.swift | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/iOS/Data/PendingOperationsQueue.swift b/iOS/Data/PendingOperationsQueue.swift index 10c1172..ec1483e 100644 --- a/iOS/Data/PendingOperationsQueue.swift +++ b/iOS/Data/PendingOperationsQueue.swift @@ -122,16 +122,19 @@ class PendingOperationsQueue: ObservableObject { } } + // Copy to let for safe capture in MainActor.run (Swift 6 concurrency) + let finalRemaining = remaining + await MainActor.run { - self.operations = remaining + self.operations = finalRemaining self.saveToDisk() self.isProcessing = false - if remaining.isEmpty { + if finalRemaining.isEmpty { DebugLogger.shared.log("All pending ops completed", category: "PendingOps") } else { DebugLogger.shared.log( - "\(remaining.count) ops still pending after retry", + "\(finalRemaining.count) ops still pending after retry", category: "PendingOps" ) } @@ -140,7 +143,7 @@ class PendingOperationsQueue: ObservableObject { } private func retryOperation(_ op: PendingOperation) async -> Bool { - guard let api = try? CompanionAPIService() else { return false } + let api = CompanionAPIService.shared switch op.type { case .metadataEdit: