diff --git a/.gitignore b/.gitignore index ba9d400..e488eb9 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,6 @@ rough_note.md Solid SettingsView.swift.md logic.md -TheBoringWorker-HUD \ No newline at end of file +TheBoringWorker-HUD +boringnotch + meteo + airpods + traduzione modificabile +segmentedhud.swift.ref \ No newline at end of file diff --git a/DynamicIsland/DynamicIslandApp.swift b/DynamicIsland/DynamicIslandApp.swift index fc9bf04..04c6ac4 100644 --- a/DynamicIsland/DynamicIslandApp.swift +++ b/DynamicIsland/DynamicIslandApp.swift @@ -378,6 +378,9 @@ class AppDelegate: NSObject, NSApplicationDelegate { coordinator.setupWorkersNotificationObservers() + // Migrate legacy progress bar settings + Defaults.Keys.migrateProgressBarStyle() + // Setup SystemHUD Manager SystemHUDManager.shared.setup(coordinator: coordinator) diff --git a/DynamicIsland/Localizable.xcstrings b/DynamicIsland/Localizable.xcstrings index 372ee37..570f136 100644 --- a/DynamicIsland/Localizable.xcstrings +++ b/DynamicIsland/Localizable.xcstrings @@ -1086,6 +1086,9 @@ }, "Seconds" : { + }, + "Segmented" : { + }, "Select a color to view details" : { diff --git a/DynamicIsland/components/Live activities/SystemEventIndicatorModifier.swift b/DynamicIsland/components/Live activities/SystemEventIndicatorModifier.swift index 70f33c7..911293b 100644 --- a/DynamicIsland/components/Live activities/SystemEventIndicatorModifier.swift +++ b/DynamicIsland/components/Live activities/SystemEventIndicatorModifier.swift @@ -98,23 +98,34 @@ struct DraggableProgressBar: View { var body: some View { VStack { GeometryReader { geo in - ZStack(alignment: .leading) { - Capsule() - .fill(.tertiary) - Group { - if Defaults[.enableGradient] { + Group { + if Defaults[.progressBarStyle] == .segmented { + // Segmented progress bar - completely different layout + SegmentedProgressContent(value: value, geometry: geo) + } else { + // Traditional capsule-based progress bar + ZStack(alignment: .leading) { Capsule() - .fill(LinearGradient(colors: Defaults[.systemEventIndicatorUseAccent] ? [Defaults[.accentColor], Defaults[.accentColor].ensureMinimumBrightness(factor: 0.2)] : [.white, .white.opacity(0.2)], startPoint: .trailing, endPoint: .leading)) - .frame(width: max(0, min(geo.size.width * value, geo.size.width))) - .shadow(color: Defaults[.systemEventIndicatorShadow] ? Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) : .white : .clear, radius: 8, x: 3) - } else { - Capsule() - .fill(Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor] : .white) - .frame(width: max(0, min(geo.size.width * value, geo.size.width))) - .shadow(color: Defaults[.systemEventIndicatorShadow] ? Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) : .white : .clear, radius: 8, x: 3) + .fill(.tertiary) + Group { + switch Defaults[.progressBarStyle] { + case .gradient: + Capsule() + .fill(LinearGradient(colors: Defaults[.systemEventIndicatorUseAccent] ? [Defaults[.accentColor], Defaults[.accentColor].ensureMinimumBrightness(factor: 0.2)] : [.white, .white.opacity(0.2)], startPoint: .trailing, endPoint: .leading)) + .frame(width: max(0, min(geo.size.width * value, geo.size.width))) + .shadow(color: Defaults[.systemEventIndicatorShadow] ? Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) : .white : .clear, radius: 8, x: 3) + case .hierarchical: + Capsule() + .fill(Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor] : .white) + .frame(width: max(0, min(geo.size.width * value, geo.size.width))) + .shadow(color: Defaults[.systemEventIndicatorShadow] ? Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) : .white : .clear, radius: 8, x: 3) + case .segmented: + EmptyView() // This case won't be reached due to the outer if condition + } + } + .opacity(value.isZero ? 0 : 1) } } - .opacity(value.isZero ? 0 : 1) } .gesture( DragGesture(minimumDistance: 0) @@ -142,3 +153,105 @@ struct DraggableProgressBar: View { value = max(0, min(newValue, 1)) } } + +struct SegmentedProgressContent: View { + let value: CGFloat + let geometry: GeometryProxy + + private let segmentCount = 16 + @State private var glowIndex: Int? = nil + @State private var lastValue: CGFloat = 0 + + var body: some View { + let spacing: CGFloat = 1.5 + let computed = (geometry.size.width - CGFloat(segmentCount - 1) * spacing) / CGFloat(segmentCount) + let barWidth = max(3.0, min(6.0, computed)) + let activeCount = Int(round(value * CGFloat(segmentCount))) + + HStack(spacing: spacing) { + ForEach(0.. Color { + if isActive { + if Defaults[.systemEventIndicatorUseAccent] { + return Defaults[.accentColor] + } + return .white + } else { + return .white.opacity(0.15) + } + } + + private func glowShadowColor(for isActive: Bool, index: Int) -> Color { + if isActive { + if let glowIndex = glowIndex, index == glowIndex { + return Defaults[.systemEventIndicatorUseAccent] + ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.8) + : .white + } else { + return Defaults[.systemEventIndicatorUseAccent] + ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) + : .white + } + } + return .clear + } + + private func handleGlow(oldValue: CGFloat, newValue: CGFloat) { + defer { lastValue = newValue } + let oldIndex = Int(round(oldValue * CGFloat(segmentCount))) + let newIndex = Int(round(newValue * CGFloat(segmentCount))) + guard oldIndex != newIndex else { return } + + if newIndex > oldIndex { + animateWave(from: oldIndex, to: newIndex, step: 1) + } else { + animateWave(from: oldIndex - 1, to: newIndex - 1, step: -1) + } + } + + private func animateWave(from start: Int, to end: Int, step: Int) { + let clampedStart = max(0, min(segmentCount - 1, start)) + let clampedEnd = max(-1, min(segmentCount - 1, end)) + let range = stride(from: clampedStart, through: clampedEnd, by: step) + var delay: Double = 0 + for i in range { + DispatchQueue.main.asyncAfter(deadline: .now() + delay) { + withAnimation(.spring(response: 0.2, dampingFraction: 0.7)) { + glowIndex = i + } + } + DispatchQueue.main.asyncAfter(deadline: .now() + delay + 0.2) { + if glowIndex == i { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + glowIndex = nil + } + } + } + delay += 0.015 + } + } +} diff --git a/DynamicIsland/components/Settings/SettingsView.swift b/DynamicIsland/components/Settings/SettingsView.swift index 9f8c4ed..e6d33c1 100644 --- a/DynamicIsland/components/Settings/SettingsView.swift +++ b/DynamicIsland/components/Settings/SettingsView.swift @@ -419,7 +419,7 @@ struct Downloads: View { struct HUD: View { @EnvironmentObject var vm: DynamicIslandViewModel @Default(.inlineHUD) var inlineHUD - @Default(.enableGradient) var enableGradient + @Default(.progressBarStyle) var progressBarStyle @Default(.enableSystemHUD) var enableSystemHUD @Default(.enableVolumeHUD) var enableVolumeHUD @Default(.enableBrightnessHUD) var enableBrightnessHUD @@ -491,15 +491,17 @@ struct HUD: View { if Defaults[.inlineHUD] { withAnimation { Defaults[.systemEventIndicatorShadow] = false - Defaults[.enableGradient] = false + Defaults[.progressBarStyle] = .hierarchical } } } - Picker("Progressbar style", selection: $enableGradient) { + Picker("Progressbar style", selection: $progressBarStyle) { Text("Hierarchical") - .tag(false) + .tag(ProgressBarStyle.hierarchical) Text("Gradient") - .tag(true) + .tag(ProgressBarStyle.gradient) + Text("Segmented") + .tag(ProgressBarStyle.segmented) } Defaults.Toggle("Enable glowing effect", key: .systemEventIndicatorShadow) Defaults.Toggle("Use accent color", key: .systemEventIndicatorUseAccent) diff --git a/DynamicIsland/models/Constants.swift b/DynamicIsland/models/Constants.swift index 18fd8f3..b52e256 100644 --- a/DynamicIsland/models/Constants.swift +++ b/DynamicIsland/models/Constants.swift @@ -97,6 +97,14 @@ enum SneakPeekStyle: String, CaseIterable, Identifiable, Defaults.Serializable { var id: String { self.rawValue } } +enum ProgressBarStyle: String, CaseIterable, Identifiable, Defaults.Serializable { + case hierarchical = "Hierarchical" + case gradient = "Gradient" + case segmented = "Segmented" + + var id: String { self.rawValue } +} + extension Defaults.Keys { // MARK: General static let menubarIcon = Key("menubarIcon", default: true) @@ -171,6 +179,8 @@ extension Defaults.Keys { // MARK: HUD static let inlineHUD = Key("inlineHUD", default: false) + static let progressBarStyle = Key("progressBarStyle", default: .hierarchical) + // Legacy support - keeping for backward compatibility static let enableGradient = Key("enableGradient", default: false) static let systemEventIndicatorShadow = Key("systemEventIndicatorShadow", default: false) static let systemEventIndicatorUseAccent = Key("systemEventIndicatorUseAccent", default: false) @@ -236,4 +246,15 @@ extension Defaults.Keys { return .nowPlaying } } + + // Migration helper to convert from legacy enableGradient Boolean to new ProgressBarStyle enum + static func migrateProgressBarStyle() { + // Check if migration is needed by seeing if the old Boolean was set to gradient + let wasGradientEnabled = Defaults[.enableGradient] + + // Only migrate if we're still using the default hierarchical value but gradient was enabled + if wasGradientEnabled && Defaults[.progressBarStyle] == .hierarchical { + Defaults[.progressBarStyle] = .gradient + } + } }