mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
236 lines
10 KiB
Swift
236 lines
10 KiB
Swift
//
|
|
// InlineHUDs.swift
|
|
// DynamicIsland
|
|
//
|
|
// Created by Richard Kunkli on 14/09/2024.
|
|
//
|
|
|
|
import SwiftUI
|
|
import Defaults
|
|
|
|
struct InlineHUD: View {
|
|
@EnvironmentObject var vm: DynamicIslandViewModel
|
|
@Binding var type: SneakContentType
|
|
@Binding var value: CGFloat
|
|
@Binding var icon: String
|
|
@Binding var hoverAnimation: Bool
|
|
@Binding var gestureProgress: CGFloat
|
|
|
|
@Default(.useColorCodedBatteryDisplay) var useColorCodedBatteryDisplay
|
|
@Default(.useColorCodedVolumeDisplay) var useColorCodedVolumeDisplay
|
|
@Default(.useSmoothColorGradient) var useSmoothColorGradient
|
|
@Default(.progressBarStyle) var progressBarStyle
|
|
@ObservedObject var bluetoothManager = BluetoothAudioManager.shared
|
|
|
|
@State private var displayName: String = ""
|
|
|
|
var body: some View {
|
|
HStack {
|
|
HStack(spacing: 5) {
|
|
Group {
|
|
switch (type) {
|
|
case .volume:
|
|
if icon.isEmpty {
|
|
// Show headphone icon if Bluetooth audio is connected, otherwise speaker
|
|
let baseIcon = bluetoothManager.isBluetoothAudioConnected ? "headphones" : SpeakerSymbol(value)
|
|
Image(systemName: baseIcon)
|
|
.contentTransition(.interpolate)
|
|
.symbolVariant(value > 0 ? .none : .slash)
|
|
.frame(width: 20, height: 15, alignment: .leading)
|
|
} else {
|
|
Image(systemName: icon)
|
|
.contentTransition(.interpolate)
|
|
.opacity(value.isZero ? 0.6 : 1)
|
|
.scaleEffect(value.isZero ? 0.85 : 1)
|
|
.frame(width: 20, height: 15, alignment: .leading)
|
|
}
|
|
case .brightness:
|
|
Image(systemName: BrightnessSymbol(value))
|
|
.contentTransition(.interpolate)
|
|
.frame(width: 20, height: 15, alignment: .center)
|
|
case .backlight:
|
|
Image(systemName: "keyboard")
|
|
.contentTransition(.interpolate)
|
|
.frame(width: 20, height: 15, alignment: .center)
|
|
case .mic:
|
|
Image(systemName: "mic")
|
|
.symbolRenderingMode(.hierarchical)
|
|
.symbolVariant(value > 0 ? .none : .slash)
|
|
.contentTransition(.interpolate)
|
|
.frame(width: 20, height: 15, alignment: .center)
|
|
case .timer:
|
|
Image(systemName: "timer")
|
|
.symbolRenderingMode(.hierarchical)
|
|
.contentTransition(.interpolate)
|
|
.frame(width: 20, height: 15, alignment: .center)
|
|
case .bluetoothAudio:
|
|
Image(systemName: icon.isEmpty ? "bluetooth" : icon)
|
|
.symbolRenderingMode(.hierarchical)
|
|
.contentTransition(.interpolate)
|
|
.frame(width: 20, height: 15, alignment: .center)
|
|
default:
|
|
EmptyView()
|
|
}
|
|
}
|
|
.foregroundStyle(.white)
|
|
.symbolVariant(.fill)
|
|
|
|
// Use marquee text for device names to handle long names
|
|
if type == .bluetoothAudio {
|
|
MarqueeText(
|
|
$displayName,
|
|
font: .system(size: 13, weight: .medium),
|
|
nsFont: .body,
|
|
textColor: .white,
|
|
minDuration: 0.5,
|
|
frameWidth: 85 - (hoverAnimation ? 0 : 12) + gestureProgress / 2
|
|
)
|
|
.onAppear {
|
|
displayName = Type2Name(type)
|
|
}
|
|
.onChange(of: type) { _, _ in
|
|
displayName = Type2Name(type)
|
|
}
|
|
.onChange(of: bluetoothManager.lastConnectedDevice?.name) { _, _ in
|
|
displayName = Type2Name(type)
|
|
}
|
|
} else {
|
|
Text(Type2Name(type))
|
|
.font(.subheadline)
|
|
.fontWeight(.medium)
|
|
.lineLimit(1)
|
|
.allowsTightening(true)
|
|
.contentTransition(.numericText())
|
|
}
|
|
}
|
|
.frame(width: 100 - (hoverAnimation ? 0 : 12) + gestureProgress / 2, height: vm.notchSize.height - (hoverAnimation ? 0 : 12), alignment: .leading)
|
|
|
|
Rectangle()
|
|
.fill(.black)
|
|
.frame(width: vm.closedNotchSize.width - 20)
|
|
|
|
HStack {
|
|
if (type == .mic) {
|
|
Text(value.isZero ? "muted" : "unmuted")
|
|
.foregroundStyle(.gray)
|
|
.lineLimit(1)
|
|
.allowsTightening(true)
|
|
.multilineTextAlignment(.trailing)
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
.contentTransition(.interpolate)
|
|
} else if (type == .timer) {
|
|
Text(TimerManager.shared.formattedRemainingTime())
|
|
.foregroundStyle(TimerManager.shared.timerColor)
|
|
.lineLimit(1)
|
|
.allowsTightening(true)
|
|
.multilineTextAlignment(.trailing)
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
.contentTransition(.interpolate)
|
|
} else if (type == .bluetoothAudio) {
|
|
// Bluetooth device battery display
|
|
HStack(spacing: 4) {
|
|
if value > 0 {
|
|
// Show color-coded battery bar if battery info available
|
|
// Color-coding disabled for segmented or hierarchical mode
|
|
if useColorCodedBatteryDisplay && progressBarStyle == .gradient {
|
|
ColorCodedProgressBar.battery(value: value, width: 60, height: 4, smoothGradient: useSmoothColorGradient)
|
|
} else {
|
|
DraggableProgressBar(value: .constant(value))
|
|
.frame(width: 60)
|
|
}
|
|
Text("\(Int(value * 100))%")
|
|
.font(.caption)
|
|
.fontWeight(.medium)
|
|
.foregroundStyle(.white)
|
|
.lineLimit(1)
|
|
} else {
|
|
// No battery info available
|
|
Text("Connected")
|
|
.font(.caption)
|
|
.fontWeight(.medium)
|
|
.foregroundStyle(.gray)
|
|
.lineLimit(1)
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
|
} else {
|
|
// Volume and brightness displays
|
|
HStack {
|
|
// Color-coding only available in gradient mode, not hierarchical or segmented
|
|
if type == .volume && useColorCodedVolumeDisplay && progressBarStyle == .gradient {
|
|
// Color-coded volume (reversed: red at high, green at low)
|
|
ColorCodedProgressBar.volume(value: value, width: 70, height: 4, smoothGradient: useSmoothColorGradient)
|
|
} else {
|
|
// Default draggable progress bar
|
|
DraggableProgressBar(value: $value)
|
|
}
|
|
|
|
if (type == .volume && value.isZero) {
|
|
Text("muted")
|
|
.font(.caption)
|
|
.fontWeight(.medium)
|
|
.foregroundStyle(.gray)
|
|
.lineLimit(1)
|
|
.allowsTightening(true)
|
|
.multilineTextAlignment(.trailing)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
.padding(.trailing, 4)
|
|
.frame(width: 100 - (hoverAnimation ? 0 : 12) + gestureProgress / 2, height: vm.closedNotchSize.height - (hoverAnimation ? 0 : 12), alignment: .center)
|
|
}
|
|
.frame(height: vm.closedNotchSize.height + (hoverAnimation ? 8 : 0), alignment: .center)
|
|
}
|
|
|
|
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"
|
|
case 0.8...1:
|
|
return "speaker.wave.3"
|
|
default:
|
|
return "speaker.wave.2"
|
|
}
|
|
}
|
|
|
|
func BrightnessSymbol(_ value: CGFloat) -> String {
|
|
switch(value) {
|
|
case 0...0.6:
|
|
return "sun.min"
|
|
case 0.6...1:
|
|
return "sun.max"
|
|
default:
|
|
return "sun.min"
|
|
}
|
|
}
|
|
|
|
func Type2Name(_ type: SneakContentType) -> String {
|
|
switch(type) {
|
|
case .volume:
|
|
return "Volume"
|
|
case .brightness:
|
|
return "Brightness"
|
|
case .backlight:
|
|
return "Backlight"
|
|
case .mic:
|
|
return "Mic"
|
|
case .bluetoothAudio:
|
|
return BluetoothAudioManager.shared.lastConnectedDevice?.name ?? "Bluetooth"
|
|
default:
|
|
return ""
|
|
}
|
|
}
|
|
}
|
|
|
|
#Preview {
|
|
InlineHUD(type: .constant(.brightness), value: .constant(0.4), icon: .constant(""), hoverAnimation: .constant(false), gestureProgress: .constant(0))
|
|
.padding(.horizontal, 8)
|
|
.background(Color.black)
|
|
.padding()
|
|
.environmentObject(DynamicIslandViewModel())
|
|
}
|