mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Working but small
This commit is contained in:
parent
1f4812222f
commit
9bed85bdf0
7 changed files with 241 additions and 8 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -66,4 +66,5 @@ rough_note.md
|
||||||
Solid
|
Solid
|
||||||
SettingsView.swift.md
|
SettingsView.swift.md
|
||||||
logic.md
|
logic.md
|
||||||
TheBoringWorker-HUD
|
TheBoringWorker-HUD
|
||||||
|
boringnotch + meteo + airpods + traduzione modificabile
|
||||||
|
|
@ -378,6 +378,9 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
|
|
||||||
coordinator.setupWorkersNotificationObservers()
|
coordinator.setupWorkersNotificationObservers()
|
||||||
|
|
||||||
|
// Migrate legacy progress bar settings
|
||||||
|
Defaults.Keys.migrateProgressBarStyle()
|
||||||
|
|
||||||
// Setup SystemHUD Manager
|
// Setup SystemHUD Manager
|
||||||
SystemHUDManager.shared.setup(coordinator: coordinator)
|
SystemHUDManager.shared.setup(coordinator: coordinator)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1086,6 +1086,9 @@
|
||||||
},
|
},
|
||||||
"Seconds" : {
|
"Seconds" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Segmented" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Select a color to view details" : {
|
"Select a color to view details" : {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -102,16 +102,19 @@ struct DraggableProgressBar: View {
|
||||||
Capsule()
|
Capsule()
|
||||||
.fill(.tertiary)
|
.fill(.tertiary)
|
||||||
Group {
|
Group {
|
||||||
if Defaults[.enableGradient] {
|
switch Defaults[.progressBarStyle] {
|
||||||
|
case .gradient:
|
||||||
Capsule()
|
Capsule()
|
||||||
.fill(LinearGradient(colors: Defaults[.systemEventIndicatorUseAccent] ? [Defaults[.accentColor], Defaults[.accentColor].ensureMinimumBrightness(factor: 0.2)] : [.white, .white.opacity(0.2)], startPoint: .trailing, endPoint: .leading))
|
.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)))
|
.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)
|
.shadow(color: Defaults[.systemEventIndicatorShadow] ? Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor].ensureMinimumBrightness(factor: 0.7) : .white : .clear, radius: 8, x: 3)
|
||||||
} else {
|
case .hierarchical:
|
||||||
Capsule()
|
Capsule()
|
||||||
.fill(Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor] : .white)
|
.fill(Defaults[.systemEventIndicatorUseAccent] ? Defaults[.accentColor] : .white)
|
||||||
.frame(width: max(0, min(geo.size.width * value, geo.size.width)))
|
.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)
|
.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)
|
.opacity(value.isZero ? 0 : 1)
|
||||||
|
|
@ -142,3 +145,30 @@ struct DraggableProgressBar: View {
|
||||||
value = max(0, min(newValue, 1))
|
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..<segmentCount, id: \.self) { index in
|
||||||
|
let segmentValue = CGFloat(index + 1) / CGFloat(segmentCount)
|
||||||
|
let isActive = value >= 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -419,7 +419,7 @@ struct Downloads: View {
|
||||||
struct HUD: View {
|
struct HUD: View {
|
||||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||||
@Default(.inlineHUD) var inlineHUD
|
@Default(.inlineHUD) var inlineHUD
|
||||||
@Default(.enableGradient) var enableGradient
|
@Default(.progressBarStyle) var progressBarStyle
|
||||||
@Default(.enableSystemHUD) var enableSystemHUD
|
@Default(.enableSystemHUD) var enableSystemHUD
|
||||||
@Default(.enableVolumeHUD) var enableVolumeHUD
|
@Default(.enableVolumeHUD) var enableVolumeHUD
|
||||||
@Default(.enableBrightnessHUD) var enableBrightnessHUD
|
@Default(.enableBrightnessHUD) var enableBrightnessHUD
|
||||||
|
|
@ -491,15 +491,17 @@ struct HUD: View {
|
||||||
if Defaults[.inlineHUD] {
|
if Defaults[.inlineHUD] {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
Defaults[.systemEventIndicatorShadow] = false
|
Defaults[.systemEventIndicatorShadow] = false
|
||||||
Defaults[.enableGradient] = false
|
Defaults[.progressBarStyle] = .hierarchical
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Picker("Progressbar style", selection: $enableGradient) {
|
Picker("Progressbar style", selection: $progressBarStyle) {
|
||||||
Text("Hierarchical")
|
Text("Hierarchical")
|
||||||
.tag(false)
|
.tag(ProgressBarStyle.hierarchical)
|
||||||
Text("Gradient")
|
Text("Gradient")
|
||||||
.tag(true)
|
.tag(ProgressBarStyle.gradient)
|
||||||
|
Text("Segmented")
|
||||||
|
.tag(ProgressBarStyle.segmented)
|
||||||
}
|
}
|
||||||
Defaults.Toggle("Enable glowing effect", key: .systemEventIndicatorShadow)
|
Defaults.Toggle("Enable glowing effect", key: .systemEventIndicatorShadow)
|
||||||
Defaults.Toggle("Use accent color", key: .systemEventIndicatorUseAccent)
|
Defaults.Toggle("Use accent color", key: .systemEventIndicatorUseAccent)
|
||||||
|
|
|
||||||
|
|
@ -97,6 +97,14 @@ enum SneakPeekStyle: String, CaseIterable, Identifiable, Defaults.Serializable {
|
||||||
var id: String { self.rawValue }
|
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 {
|
extension Defaults.Keys {
|
||||||
// MARK: General
|
// MARK: General
|
||||||
static let menubarIcon = Key<Bool>("menubarIcon", default: true)
|
static let menubarIcon = Key<Bool>("menubarIcon", default: true)
|
||||||
|
|
@ -171,6 +179,8 @@ extension Defaults.Keys {
|
||||||
|
|
||||||
// MARK: HUD
|
// MARK: HUD
|
||||||
static let inlineHUD = Key<Bool>("inlineHUD", default: false)
|
static let inlineHUD = Key<Bool>("inlineHUD", default: false)
|
||||||
|
static let progressBarStyle = Key<ProgressBarStyle>("progressBarStyle", default: .hierarchical)
|
||||||
|
// Legacy support - keeping for backward compatibility
|
||||||
static let enableGradient = Key<Bool>("enableGradient", default: false)
|
static let enableGradient = Key<Bool>("enableGradient", default: false)
|
||||||
static let systemEventIndicatorShadow = Key<Bool>("systemEventIndicatorShadow", default: false)
|
static let systemEventIndicatorShadow = Key<Bool>("systemEventIndicatorShadow", default: false)
|
||||||
static let systemEventIndicatorUseAccent = Key<Bool>("systemEventIndicatorUseAccent", default: false)
|
static let systemEventIndicatorUseAccent = Key<Bool>("systemEventIndicatorUseAccent", default: false)
|
||||||
|
|
@ -236,4 +246,15 @@ extension Defaults.Keys {
|
||||||
return .nowPlaying
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
173
segmentedhud.swift.ref
Normal file
173
segmentedhud.swift.ref
Normal file
|
|
@ -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..<segmentCount, id: \.self) { index in
|
||||||
|
RoundedRectangle(cornerRadius: 3)
|
||||||
|
.fill(segmentColor(isActive: index < activeCount))
|
||||||
|
.shadow(
|
||||||
|
color: Defaults[.systemEventIndicatorShadow]
|
||||||
|
? glowShadowColor(for: index < activeCount, index: index)
|
||||||
|
: .clear,
|
||||||
|
radius: Defaults[.systemEventIndicatorShadow]
|
||||||
|
? (glowIndex == index ? 18 : (index < activeCount ? 6 : 0))
|
||||||
|
: 0,
|
||||||
|
x: 0, y: 0
|
||||||
|
)
|
||||||
|
.frame(width: barWidth)
|
||||||
|
.animation(.spring(response: 0.3, dampingFraction: 0.8), value: activeCount)
|
||||||
|
.scaleEffect(glowIndex == index ? 1.08 : 1.0)
|
||||||
|
.animation(.spring(response: 0.2, dampingFraction: 0.6), value: glowIndex == index)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .center)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func segmentColor(isActive: Bool) -> 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue