diff --git a/.gitignore b/.gitignore index ba9d400..2791606 100644 --- a/.gitignore +++ b/.gitignore @@ -66,4 +66,5 @@ rough_note.md Solid SettingsView.swift.md logic.md -TheBoringWorker-HUD \ No newline at end of file +TheBoringWorker-HUD +boringnotch + meteo + airpods + traduzione modificabile \ 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..f6c58ea 100644 --- a/DynamicIsland/components/Live activities/SystemEventIndicatorModifier.swift +++ b/DynamicIsland/components/Live activities/SystemEventIndicatorModifier.swift @@ -102,16 +102,19 @@ struct DraggableProgressBar: View { Capsule() .fill(.tertiary) Group { - if Defaults[.enableGradient] { + 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) - } else { + 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: + SegmentedProgressContent(value: value, geometry: geo) } } .opacity(value.isZero ? 0 : 1) @@ -142,3 +145,30 @@ struct DraggableProgressBar: View { value = max(0, min(newValue, 1)) } } + +struct SegmentedProgressContent: View { + let value: CGFloat + let geometry: GeometryProxy + + private let segmentCount = 20 + + var body: some View { + HStack(spacing: 1) { + ForEach(0..= segmentValue + + RoundedRectangle(cornerRadius: 1) + .fill(isActive ? + (Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor] : .white) : + .clear) + .frame(width: max(1, (geometry.size.width - CGFloat(segmentCount - 1) * 1) / CGFloat(segmentCount))) + .shadow(color: isActive && Defaults[.systemEventIndicatorShadow] ? + (Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) : .white) : + .clear, radius: 4, x: 1) + .opacity(value.isZero ? 0 : (isActive ? 1 : 0.3)) + } + } + .frame(width: geometry.size.width) + } +} 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 + } + } } diff --git a/segmentedhud.swift.ref b/segmentedhud.swift.ref new file mode 100644 index 0000000..7fb545e --- /dev/null +++ b/segmentedhud.swift.ref @@ -0,0 +1,173 @@ +import SwiftUI +import Defaults + +struct SegmentedHUD: View { + @EnvironmentObject var vm: BoringViewModel + @Binding var type: SneakContentType + @Binding var value: CGFloat // 0...1 + @Binding var icon: String + + private let segmentCount: Int = 40 + @State private var glowIndex: Int? = nil + @State private var lastValue: CGFloat = 0 + + var body: some View { + HStack(spacing: 16) { + // Icona a sinistra + hudIcon + .frame(width: 25, height: 20) + .offset(x:0, y: 10) + + // Contenuto centrale: percentuale + segmenti + VStack(spacing: 6) { + Text("\(Int(value * 100))%") + .font(.system(size: 14, weight: .medium, design: .rounded)) + .foregroundStyle(.white) + .frame(maxWidth: .infinity, alignment: .center) + .offset(x:182, y:20) + segments + .frame(height: vm.notchState == .open ? 10 : 14) + } + .frame(maxWidth: .infinity) + + // Spacer per bilanciare il layout (stesso width dell'icona) + Spacer() + .frame(width: 20) + } + .padding(.horizontal, 16) + .padding(.vertical, 12) + .frame(maxWidth: .infinity) + .frame(height: vm.notchState == .open ? vm.closedNotchSize.height + 8 : 70) + .onChange(of: value) { old, newVal in + handleGlow(oldValue: old, newValue: newVal) + } + } + + @ViewBuilder + private var hudIcon: some View { + Group { + switch type { + case .volume: + if icon.isEmpty { + Image(systemName: speakerSymbol(value)) + .symbolVariant(value > 0 ? .none : .slash) + } else { + Image(systemName: icon) + .opacity(value.isZero ? 0.6 : 1) + .scaleEffect(value.isZero ? 0.85 : 1) + } + case .brightness: + Image(systemName: "sun.max.fill") + case .backlight: + Image(systemName: "keyboard") + case .mic: + Image(systemName: "mic") + default: + EmptyView() + } + } + .foregroundStyle(.white) + } + + // Il resto del codice rimane uguale... + private var segments: some View { + GeometryReader { geo in + let width = geo.size.width + let spacing: CGFloat = vm.notchState == .open ? 2.5 : 3.5 // Aumentato spacing + let computed = (width - CGFloat(segmentCount - 1) * spacing) / CGFloat(segmentCount) + let barWidth = max(2.5, min(4.5, computed)) // Aumentata larghezza segmenti + let activeCount = Int(round(value * CGFloat(segmentCount))) + + HStack(spacing: spacing) { + ForEach(0.. AnyShapeStyle { + if isActive { + if Defaults[.systemEventIndicatorUseAccent] { + return AnyShapeStyle(Color.accentColor) + } + return AnyShapeStyle(Color.white) + } else { + return AnyShapeStyle(Color.white.opacity(0.2)) + } + } + + private func glowShadowColor(for isActive: Bool, index: Int) -> Color { + if isActive { + if let glowIndex = glowIndex, index == glowIndex { + return Defaults[.systemEventIndicatorUseAccent] + ? Color.accentColor.ensureMinimumBrightness(factor: 0.8) + : Color.white + } else { + return Defaults[.systemEventIndicatorUseAccent] + ? Color.accentColor.ensureMinimumBrightness(factor: 0.7) + : Color.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.25) { + if glowIndex == i { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + glowIndex = nil + } + } + } + delay += 0.02 + } + } + + private func speakerSymbol(_ value: CGFloat) -> String { + switch value { + case 0: return "speaker" + case 0...0.3: return "speaker.wave.1" + case 0.3...0.8: return "speaker.wave.2" + default: return "speaker.wave.3" + } + } +}