Widget glassmorphism redesign
This commit is contained in:
parent
8607bce56a
commit
0a1a46df47
6 changed files with 225 additions and 15 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@
|
|||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
172
iOS/Views/Library/LicensesView.swift
Normal file
172
iOS/Views/Library/LicensesView.swift
Normal file
|
|
@ -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
|
||||
Loading…
Reference in a new issue