diff --git a/boringNotch.xcodeproj/project.pbxproj b/boringNotch.xcodeproj/project.pbxproj index 0a0b287..26b0b48 100644 --- a/boringNotch.xcodeproj/project.pbxproj +++ b/boringNotch.xcodeproj/project.pbxproj @@ -80,6 +80,8 @@ B186543C2C6F49AE000B926A /* ShortcutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B186543B2C6F49AE000B926A /* ShortcutConstants.swift */; }; B19016222CC15B3D00E3F12E /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = B19016212CC15B3D00E3F12E /* Defaults */; }; B19016242CC15B5000E3F12E /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19016232CC15B4D00E3F12E /* Constants.swift */; }; + B19424092CD0FF01003E5DC2 /* LottieAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19424082CD0FEFE003E5DC2 /* LottieAnimationView.swift */; }; + B194240B2CD10017003E5DC2 /* LottieUI in Frameworks */ = {isa = PBXBuildFile; productRef = B194240A2CD10017003E5DC2 /* LottieUI */; }; B1A78C822C8BA08100BD51B0 /* FullscreenMediaDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A78C812C8BA08100BD51B0 /* FullscreenMediaDetection.swift */; }; B1A78C842C8C65ED00BD51B0 /* MediaRemote.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B1A78C832C8C65ED00BD51B0 /* MediaRemote.framework */; }; B1B1128F2C6A56E800093D8F /* DynamicNotch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B1128E2C6A56E800093D8F /* DynamicNotch.swift */; }; @@ -191,6 +193,7 @@ B17C3C242C7DAD0C0082390A /* OSD.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OSD.framework; path = ../../../../../System/Library/PrivateFrameworks/OSD.framework; sourceTree = ""; }; B186543B2C6F49AE000B926A /* ShortcutConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutConstants.swift; sourceTree = ""; }; B19016232CC15B4D00E3F12E /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + B19424082CD0FEFE003E5DC2 /* LottieAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationView.swift; sourceTree = ""; }; B1A78C812C8BA08100BD51B0 /* FullscreenMediaDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenMediaDetection.swift; sourceTree = ""; }; B1A78C832C8C65ED00BD51B0 /* MediaRemote.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaRemote.framework; path = ../../../../../System/Library/PrivateFrameworks/MediaRemote.framework; sourceTree = ""; }; B1B1128E2C6A56E800093D8F /* DynamicNotch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DynamicNotch.swift; sourceTree = ""; }; @@ -220,6 +223,7 @@ 9A987A102C73CA8D005CA465 /* Collections in Frameworks */, 1485442F2C8F049C00943381 /* TheBoringWorkerNotifier in Frameworks */, 14D0321D2C68F3350096E6A1 /* Sparkle in Frameworks */, + B194240B2CD10017003E5DC2 /* LottieUI in Frameworks */, 14D0321A2C68F32E0096E6A1 /* LaunchAtLogin in Frameworks */, B1A78C842C8C65ED00BD51B0 /* MediaRemote.framework in Frameworks */, B18654392C6F4990000B926A /* KeyboardShortcuts in Frameworks */, @@ -482,6 +486,7 @@ B186542E2C6F453B000B926A /* Music */ = { isa = PBXGroup; children = ( + B19424082CD0FEFE003E5DC2 /* LottieAnimationView.swift */, 147163972C5D35B70068B555 /* MusicVisualizer.swift */, ); path = Music; @@ -570,6 +575,7 @@ 1485442E2C8F049C00943381 /* TheBoringWorkerNotifier */, B19016212CC15B3D00E3F12E /* Defaults */, B1628B912CC260C0003D8DF3 /* SwiftUIIntrospect */, + B194240A2CD10017003E5DC2 /* LottieUI */, ); productName = dynamicNotch; productReference = 14CEF4122C5CAED300855D72 /* boringNotch.app */; @@ -609,6 +615,7 @@ 1485442D2C8F049C00943381 /* XCRemoteSwiftPackageReference "TheBoringWorkerNotifier" */, B19016202CC15B3D00E3F12E /* XCRemoteSwiftPackageReference "Defaults" */, B1628B902CC260C0003D8DF3 /* XCRemoteSwiftPackageReference "swiftui-introspect" */, + B19424072CD0DB52003E5DC2 /* XCRemoteSwiftPackageReference "LottieUI" */, ); productRefGroup = 14CEF4132C5CAED300855D72 /* Products */; projectDirPath = ""; @@ -651,6 +658,7 @@ B141C23F2CA5F51E00AC8CC8 /* ProOnboarding.swift in Sources */, B141C2412CA5F53F00AC8CC8 /* SparkleView.swift in Sources */, 14D570B92C5E98A20011E668 /* drop.swift in Sources */, + B19424092CD0FF01003E5DC2 /* LottieAnimationView.swift in Sources */, 9A987A072C73CA66005CA465 /* Ext+NSImage.swift in Sources */, B1C974342C642B6D0000E707 /* MarqueeTextView.swift in Sources */, 9A987A052C73CA66005CA465 /* Ext+FileProvider.swift in Sources */, @@ -1023,6 +1031,14 @@ kind = branch; }; }; + B19424072CD0DB52003E5DC2 /* XCRemoteSwiftPackageReference "LottieUI" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/jasudev/LottieUI.git"; + requirement = { + branch = main; + kind = branch; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1061,6 +1077,11 @@ package = B19016202CC15B3D00E3F12E /* XCRemoteSwiftPackageReference "Defaults" */; productName = Defaults; }; + B194240A2CD10017003E5DC2 /* LottieUI */ = { + isa = XCSwiftPackageProductDependency; + package = B19424072CD0DB52003E5DC2 /* XCRemoteSwiftPackageReference "LottieUI" */; + productName = LottieUI; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 14CEF40A2C5CAED200855D72 /* Project object */; diff --git a/boringNotch.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/boringNotch.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 83601bc..331b8a4 100644 --- a/boringNotch.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/boringNotch.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "7cdc1f3bd18c54561dd174b285f645b3029f5a2147bf6d2ab4a408088e2a45e9", + "originHash" : "aae72b9079c2044604a3d1a8185d269560dd640056d837546f4c964ea61652f0", "pins" : [ { "identity" : "defaults", @@ -28,6 +28,24 @@ "revision" : "a04ec1c363be3627734f6dad757d82f5d4fa8fcc" } }, + { + "identity" : "lottie-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/airbnb/lottie-spm.git", + "state" : { + "branch" : "main", + "revision" : "b7f41dd0b33c61577a4f0c8dd3674e5933719da0" + } + }, + { + "identity" : "lottieui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/jasudev/LottieUI.git", + "state" : { + "branch" : "main", + "revision" : "0cd5b54a1c8467b19c01f56395aec179087b62e2" + } + }, { "identity" : "pow", "kind" : "remoteSourceControl", diff --git a/boringNotch/ContentView.swift b/boringNotch/ContentView.swift index dd27fcc..f365a16 100644 --- a/boringNotch/ContentView.swift +++ b/boringNotch/ContentView.swift @@ -30,10 +30,12 @@ struct ContentView: View { @Namespace var albumArtNamespace + @Default(.useMusicVisualizer) var useMusicVisualizer + var body: some View { ZStack(alignment: .top) { NotchLayout() - .padding(.horizontal, vm.notchState == .open ? Defaults[.cornerRadiusScaling] ? (vm.sizes.cornerRadius.opened.inset! - 5) : (vm.sizes.cornerRadius.closed.inset! - 5) : 12) + .padding(.horizontal, vm.notchState == .open ? Defaults[.cornerRadiusScaling] ? (vm.sizes.cornerRadius.opened.inset!) : (vm.sizes.cornerRadius.closed.inset! - 5) : 12) .padding([.horizontal, .bottom], vm.notchState == .open ? 12 : 0) .frame(maxWidth: (((musicManager.isPlaying || !musicManager.isPlayerIdle) && vm.notchState == .closed && vm.showMusicLiveActivityOnClosed) || (vm.expandingView.show && (vm.expandingView.type == .battery)) || Defaults[.inlineHUD]) ? nil : vm.notchSize.width + ((hoverAnimation || (vm.notchState == .closed)) ? 20 : 0) + gestureProgress, maxHeight: ((vm.sneakPeek.show && vm.sneakPeek.type != .music) || (vm.sneakPeek.show && vm.sneakPeek.type == .music && vm.notchState == .closed)) ? nil : vm.notchSize.height + (hoverAnimation ? 8 : 0) + gestureProgress / 3, alignment: .top) .background(.black) @@ -234,7 +236,7 @@ struct ContentView: View { HStack(alignment: .center) { Image(systemName: "music.note") GeometryReader { geo in - MarqueeText(musicManager.songTitle + " - " + musicManager.artistName, textColor: .gray, minDuration: 1, frameWidth: geo.size.width) + MarqueeText(.constant(musicManager.songTitle + " - " + musicManager.artistName), textColor: .gray, minDuration: 1, frameWidth: geo.size.width) } } .foregroundStyle(.gray) @@ -263,6 +265,7 @@ struct ContentView: View { .zIndex(1) .allowsHitTesting(vm.notchState == .open) .blur(radius: abs(gestureProgress) > 0.3 ? min(abs(gestureProgress), 8) : 0) + .opacity(abs(gestureProgress) > 0.3 ? min(abs(gestureProgress * 2), 0.8) : 1) } } @@ -289,14 +292,19 @@ struct ContentView: View { .frame(width: Sizes().size.closed.width! - 20) HStack { - Rectangle() - .fill(Defaults[.coloredSpectrogram] ? Color(nsColor: musicManager.avgColor).gradient : Color.gray.gradient) - .mask { - AudioSpectrumView( - isPlaying: $musicManager.isPlaying - ) - .frame(width: 16, height: 12) - } + if useMusicVisualizer { + Rectangle() + .fill(Defaults[.coloredSpectrogram] ? Color(nsColor: musicManager.avgColor).gradient : Color.gray.gradient) + .mask { + AudioSpectrumView( + isPlaying: $musicManager.isPlaying + ) + .frame(width: 16, height: 12) + } + } else { + LottieAnimationView() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } } .frame(width: max(0, Sizes().size.closed.height! - (hoverAnimation ? 0 : 12) + gestureProgress / 2), height: max(0,Sizes().size.closed.height! - (hoverAnimation ? 0 : 12)), alignment: .center) diff --git a/boringNotch/Localizable.xcstrings b/boringNotch/Localizable.xcstrings index b17fd18..51c2ad9 100644 --- a/boringNotch/Localizable.xcstrings +++ b/boringNotch/Localizable.xcstrings @@ -40,9 +40,6 @@ }, "• New feature 1" : { - }, - "⚠️ Important" : { - }, "About" : { "localizations" : { @@ -72,9 +69,15 @@ }, "Activation" : { + }, + "Add" : { + }, "Add manually" : { + }, + "Add new visualizer" : { + }, "Additional features" : { @@ -220,9 +223,15 @@ }, "Custom height" : { + }, + "Custom music live activity animation" : { + }, "Custom notch size - %.0f" : { + }, + "Custom vizualizers (Lottie)" : { + }, "Default" : { @@ -386,12 +395,6 @@ }, "High" : { - }, - "Hover over the notch after changing the height to see the effect." : { - - }, - "Hover over the notch after changing the screen to adapt the new size" : { - }, "HUD style" : { @@ -424,6 +427,9 @@ }, "License key" : { + }, + "Lottie JSON URL" : { + }, "Low" : { @@ -508,9 +514,18 @@ }, "muted" : { + }, + "Name" : { + }, "Need Restart" : { + }, + "No custom animation available" : { + + }, + "No custom visualizer" : { + }, "No events today" : { @@ -649,6 +664,12 @@ } } } + }, + "selected" : { + + }, + "Selected animation" : { + }, "Settings" : { "localizations" : { @@ -728,6 +749,9 @@ } } } + }, + "Speed" : { + }, "Square" : { @@ -771,6 +795,9 @@ }, "Upgrade to Pro" : { + }, + "Use music visualizer spectrogram" : { + }, "Version" : { "localizations" : { diff --git a/boringNotch/components/Live activities/MarqueeTextView.swift b/boringNotch/components/Live activities/MarqueeTextView.swift index 97420b1..82b64a7 100644 --- a/boringNotch/components/Live activities/MarqueeTextView.swift +++ b/boringNotch/components/Live activities/MarqueeTextView.swift @@ -23,7 +23,7 @@ struct MeasureSizeModifier: ViewModifier { } struct MarqueeText: View { - let text: String + @Binding var text: String let font: Font let nsFont: NSFont.TextStyle let textColor: Color @@ -33,9 +33,10 @@ struct MarqueeText: View { @State private var animate = false @State private var textSize: CGSize = .zero + @State private var offset: CGFloat = 0 - init(_ text: String, font: Font = .body, nsFont: NSFont.TextStyle = .body, textColor: Color = .primary, backgroundColor: Color = .clear, minDuration: Double = 3.0, frameWidth: CGFloat = 200) { - self.text = text + init(_ text: Binding, font: Font = .body, nsFont: NSFont.TextStyle = .body, textColor: Color = .primary, backgroundColor: Color = .clear, minDuration: Double = 3.0, frameWidth: CGFloat = 200) { + _text = text self.font = font self.nsFont = nsFont self.textColor = textColor @@ -44,32 +45,56 @@ struct MarqueeText: View { self.frameWidth = frameWidth } + private var needsScrolling: Bool { + textSize.width > frameWidth + } + var body: some View { GeometryReader { geometry in ZStack(alignment: .leading) { HStack(spacing: 20) { Text(text) Text(text) - .opacity((textSize.width > frameWidth) ? 1 : 0) + .opacity(needsScrolling ? 1 : 0) } .font(font) .foregroundColor(textColor) .fixedSize(horizontal: true, vertical: false) - .offset(x: animate ? -textSize.width - 10 : 0) + .offset(x: animate ? offset : 0) .animation( - .linear(duration: Double(textSize.width / 30)) - .delay(minDuration) - .repeatForever(autoreverses: false), + animate ? + .linear(duration: Double(textSize.width / 30)) + .delay(minDuration) + .repeatForever(autoreverses: false) : .none, value: animate ) .background(backgroundColor) .modifier(MeasureSizeModifier()) .onPreferenceChange(SizePreferenceKey.self) { size in self.textSize = CGSize(width: size.width / 2, height: NSFont.preferredFont(forTextStyle: nsFont).pointSize) - if textSize.width > frameWidth { - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + } + .onChange(of: text) { + + offset = 0 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + if needsScrolling { + offset = -(textSize.width + 10) + withAnimation { + animate = true + } + } + } + } + .onAppear { + withAnimation { + animate = false + } + offset = 0 + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + if needsScrolling { + offset = -(textSize.width + 10) withAnimation { - self.animate = true + animate = true } } } diff --git a/boringNotch/components/Music/LottieAnimationView.swift b/boringNotch/components/Music/LottieAnimationView.swift new file mode 100644 index 0000000..b0b7433 --- /dev/null +++ b/boringNotch/components/Music/LottieAnimationView.swift @@ -0,0 +1,33 @@ +// +// LottieAnimationView.swift +// boringNotch +// +// Created by Richard Kunkli on 2024. 10. 29.. +// + +import SwiftUI +import Lottie +import LottieUI +import Defaults + +struct LottieAnimationView: View { + let state1 = LUStateData(type: .loadedFrom(URL(string: "https://assets9.lottiefiles.com/packages/lf20_mniampqn.json")!), speed: 1.0, loopMode: .loop) + @Default(.selectedVisualizer) var selectedVisualizer + var body: some View { + if selectedVisualizer == nil { + LottieView(state: state1) + } else { + LottieView( + state: LUStateData( + type: .loadedFrom(selectedVisualizer!.url), + speed: selectedVisualizer!.speed, + loopMode: .loop + ) + ) + } + } +} + +#Preview { + LottieAnimationView() +} diff --git a/boringNotch/components/Notch/NotchHomeView.swift b/boringNotch/components/Notch/NotchHomeView.swift index 65cac8a..473896b 100644 --- a/boringNotch/components/Notch/NotchHomeView.swift +++ b/boringNotch/components/Notch/NotchHomeView.swift @@ -77,9 +77,9 @@ struct NotchHomeView: View { VStack(alignment: .leading) { GeometryReader { geo in VStack(alignment: .leading, spacing: 4){ - MarqueeText(musicManager.songTitle, font: .headline, nsFont: .headline, textColor: .white, frameWidth: geo.size.width) + MarqueeText($musicManager.songTitle, font: .headline, nsFont: .headline, textColor: .white, frameWidth: geo.size.width) MarqueeText( - musicManager.artistName, + $musicManager.artistName, font: .headline, nsFont: .headline, textColor: Defaults[.playerColorTinting] ? Color(nsColor: musicManager.avgColor) diff --git a/boringNotch/components/Settings/SettingsView.swift b/boringNotch/components/Settings/SettingsView.swift index 5366e15..9d7010c 100644 --- a/boringNotch/components/Settings/SettingsView.swift +++ b/boringNotch/components/Settings/SettingsView.swift @@ -12,6 +12,7 @@ import KeyboardShortcuts import Defaults import SwiftUIIntrospect import AVFoundation +import LottieUI struct SettingsView: View { @EnvironmentObject var vm: BoringViewModel @@ -403,7 +404,6 @@ struct Media: View { "Enable music live activity", isOn: $vm.showMusicLiveActivityOnClosed.animation() ) - Defaults.Toggle("Enable colored spectrograms", key: .coloredSpectrogram) Defaults.Toggle("Enable sneak peek", key: .enableSneakPeek) HStack { Stepper(value: $waitInterval, in: 0...10, step: 1) { @@ -665,8 +665,17 @@ struct Appearance: View { @EnvironmentObject var vm: BoringViewModel @Default(.mirrorShape) var mirrorShape @Default(.sliderColor) var sliderColor + @Default(.useMusicVisualizer) var useMusicVisualizer + @Default(.customVisualizers) var customVisualizers + @Default(.selectedVisualizer) var selectedVisualizer let icons: [String] = ["logo2"] @State private var selectedIcon: String = "logo2" + @State private var selectedListVisualizer: CustomVisualizer? = nil + + @State private var isPresented: Bool = false + @State private var name: String = "" + @State private var url: String = "" + @State private var speed: CGFloat = 1.0 var body: some View { Form { Section { @@ -674,16 +683,190 @@ struct Appearance: View { Defaults.Toggle("Settings icon in notch", key: .settingsIconInNotch) Defaults.Toggle("Enable window shadow", key: .enableShadow) Defaults.Toggle("Corner radius scaling", key: .cornerRadiusScaling) + } header: { + Text("General") + } + + Section { + Defaults.Toggle("Enable colored spectrograms", key: .coloredSpectrogram) + Defaults + .Toggle("Player tinting", key: .playerColorTinting) + Defaults.Toggle("Enable blur effect behind album art", key: .lightingEffect) Picker("Slider color", selection: $sliderColor) { ForEach(SliderColorEnum.allCases, id: \.self) { option in Text(option.rawValue) } } - Defaults - .Toggle("Player tinting", key: .playerColorTinting) - Defaults.Toggle("Enable blur effect behind album art", key: .lightingEffect) } header: { - Text("General") + Text("Media") + } + + Section { + Toggle( + "Use music visualizer spectrogram", + isOn: $useMusicVisualizer.animation() + ) + .disabled(true) + if !useMusicVisualizer { + if customVisualizers.count > 0 { + Picker( + "Selected animation", + selection: $selectedVisualizer + ) { + ForEach( + customVisualizers, + id: \.self + ) { visualizer in + Text(visualizer.name) + .tag(visualizer) + } + } + } else { + HStack { + Text("Selected animation") + Spacer() + Text("No custom animation available") + .foregroundStyle(.secondary) + } + } + } + } header: { + HStack { + Text("Custom music live activity animation") + customBadge(text: "Coming soon") + } + } + + Section { + List { + ForEach(customVisualizers, id: \.self) { visualizer in + HStack { + LottieView(state: LUStateData(type: .loadedFrom(visualizer.url), speed: visualizer.speed, loopMode: .loop)) + .frame(width: 30, height: 30, alignment: .center) + Text(visualizer.name) + Spacer(minLength: 0) + if selectedVisualizer == visualizer { + Text("selected") + .font(.caption) + .fontWeight(.medium) + .foregroundStyle(.secondary) + .padding(.trailing, 8) + } + } + .buttonStyle(PlainButtonStyle()) + .padding(.vertical, 2) + .background( + selectedListVisualizer != nil ? selectedListVisualizer == visualizer ? Defaults[.accentColor] : Color.clear : Color.clear, + in: RoundedRectangle(cornerRadius: 5) + ) + .contentShape(Rectangle()) + .onTapGesture { + if selectedListVisualizer == visualizer { + selectedListVisualizer = nil + return + } + selectedListVisualizer = visualizer + } + } + } + .safeAreaPadding( + EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0) + ) + .frame(minHeight: 120) + .actionBar { + HStack(spacing: 5) { + Button { + name = "" + url = "" + speed = 1.0 + isPresented.toggle() + } label: { + Image(systemName: "plus") + .foregroundStyle(.secondary) + .contentShape(Rectangle()) + } + Divider() + Button { + if selectedListVisualizer != nil { + let visualizer = selectedListVisualizer! + selectedListVisualizer = nil + customVisualizers.remove(at: customVisualizers.firstIndex(of: visualizer)!) + if visualizer == selectedVisualizer && customVisualizers.count > 0 { + selectedVisualizer = customVisualizers[0] + } + } + } label: { + Image(systemName: "minus") + .foregroundStyle(.secondary) + .contentShape(Rectangle()) + } + } + } + .controlSize(.small) + .buttonStyle(PlainButtonStyle()) + .overlay { + if customVisualizers.isEmpty { + Text("No custom visualizer") + .foregroundStyle(Color(.secondaryLabelColor)) + .padding(.bottom, 22) + } + } + .sheet(isPresented: $isPresented) { + VStack(alignment: .leading) { + Text("Add new visualizer") + .font(.largeTitle.bold()) + .padding(.vertical) + TextField("Name", text: $name) + TextField("Lottie JSON URL", text: $url) + HStack { + Text("Speed") + Spacer(minLength: 80) + Text("\(speed, specifier: "%.1f")s") + .multilineTextAlignment(.trailing) + .foregroundStyle(.secondary) + Slider(value: $speed, in: 0...2, step: 0.1) + } + .padding(.vertical) + HStack { + Button { + isPresented.toggle() + } label: { + Text("Cancel") + .frame(maxWidth: .infinity, alignment: .center) + } + + Button { + let visualizer: CustomVisualizer = .init( + UUID: UUID(), + name: name, + url: URL(string: url)!, + speed: speed + ) + + if !customVisualizers.contains(visualizer) { + customVisualizers.append(visualizer) + } + + isPresented.toggle() + } label: { + Text("Add") + .frame(maxWidth: .infinity, alignment: .center) + } + .buttonStyle(BorderedProminentButtonStyle()) + } + } + .textFieldStyle(RoundedBorderTextFieldStyle()) + .controlSize(.extraLarge) + .padding() + } + } header: { + HStack(spacing: 0) { + Text("Custom vizualizers (Lottie)") + if !Defaults[.customVisualizers].isEmpty { + Text(" – \(Defaults[.customVisualizers].count)") + .foregroundStyle(.secondary) + } + } } Section { diff --git a/boringNotch/models/BoringViewModel.swift b/boringNotch/models/BoringViewModel.swift index dda4b30..4b9884c 100644 --- a/boringNotch/models/BoringViewModel.swift +++ b/boringNotch/models/BoringViewModel.swift @@ -216,7 +216,7 @@ class BoringViewModel: NSObject, ObservableObject { func toggleMusicLiveActivity(status: Bool) { withAnimation(.smooth) { - self.showMusicLiveActivityOnClosed = status + // self.showMusicLiveActivityOnClosed = status } } diff --git a/boringNotch/models/Constants.swift b/boringNotch/models/Constants.swift index b667c53..4693e0f 100644 --- a/boringNotch/models/Constants.swift +++ b/boringNotch/models/Constants.swift @@ -8,6 +8,13 @@ import SwiftUI import Defaults +struct CustomVisualizer: Codable, Hashable, Equatable, Defaults.Serializable { + let UUID: UUID + var name: String + var url: URL + var speed: CGFloat = 1.0 +} + extension Defaults.Keys { // MARK: General static let menubarIcon = Key("menubarIcon", default: true) @@ -42,6 +49,9 @@ extension Defaults.Keys { default: SliderColorEnum.white ) static let playerColorTinting = Key("playerColorTinting", default: true) + static let useMusicVisualizer = Key("useMusicVisualizer", default: true) + static let customVisualizers = Key<[CustomVisualizer]>("customVisualizers", default: []) + static let selectedVisualizer = Key("selectedVisualizer", default: nil) // MARK: Gestures static let enableGestures = Key("enableGestures", default: true)