mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-28 23:54:32 +00:00
Restored files from old Mac, force pushing
This commit is contained in:
parent
5dd95228b8
commit
5040381840
126 changed files with 10431 additions and 67 deletions
52
DynamicIsland/components/Live activities/DownloadView.swift
Normal file
52
DynamicIsland/components/Live activities/DownloadView.swift
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// DownloadView.swift
|
||||
// boringNotch
|
||||
//
|
||||
// Created by Harsh Vardhan Goswami on 17/08/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum Browser {
|
||||
case safari
|
||||
case chrome
|
||||
}
|
||||
|
||||
struct DownloadFile {
|
||||
var name: String
|
||||
var size: Int
|
||||
var formattedSize: String
|
||||
var browser: Browser
|
||||
}
|
||||
|
||||
class DownloadWatcher: ObservableObject {
|
||||
@Published var downloadFiles: [DownloadFile] = []
|
||||
}
|
||||
|
||||
struct DownloadArea: View {
|
||||
@EnvironmentObject var watcher: DownloadWatcher
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .center) {
|
||||
HStack {
|
||||
if watcher.downloadFiles.first!.browser == .safari {
|
||||
AppIcon(for: "com.apple.safari")
|
||||
} else {
|
||||
Image(.chrome).resizable().scaledToFit().frame(width: 30, height: 30)
|
||||
}
|
||||
VStack(alignment: .leading) {
|
||||
Text("Download")
|
||||
Text("In progress").font(.system(.footnote)).foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
HStack(spacing: 12) {
|
||||
VStack(alignment: .trailing) {
|
||||
Text(watcher.downloadFiles.first!.formattedSize)
|
||||
Text(watcher.downloadFiles.first!.name).font(.caption2).foregroundStyle(.gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,256 @@
|
|||
import SwiftUI
|
||||
import Defaults
|
||||
|
||||
/// A view that displays the battery status with an icon and charging indicator.
|
||||
struct BatteryView: View {
|
||||
|
||||
var levelBattery: Float
|
||||
var isPluggedIn: Bool
|
||||
var isCharging: Bool
|
||||
var isInLowPowerMode: Bool
|
||||
var batteryWidth: CGFloat = 26
|
||||
var isForNotification: Bool
|
||||
|
||||
var animationStyle: BoringAnimations = BoringAnimations()
|
||||
|
||||
var icon: String = "battery.0"
|
||||
|
||||
/// Determines the icon to display when charging.
|
||||
var iconStatus: String {
|
||||
if isCharging {
|
||||
return "bolt"
|
||||
}
|
||||
else if isPluggedIn {
|
||||
return "plug"
|
||||
}
|
||||
else {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines the color of the battery based on its status.
|
||||
var batteryColor: Color {
|
||||
if isInLowPowerMode {
|
||||
return .yellow
|
||||
} else if levelBattery <= 20 && !isCharging && !isPluggedIn {
|
||||
return .red
|
||||
} else if isCharging || isPluggedIn || levelBattery == 100 {
|
||||
return .green
|
||||
} else {
|
||||
return .white
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .leading) {
|
||||
|
||||
Image(systemName: icon)
|
||||
.resizable()
|
||||
.fontWeight(.thin)
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.white.opacity(0.5))
|
||||
.frame(
|
||||
width: batteryWidth + 1
|
||||
)
|
||||
|
||||
RoundedRectangle(cornerRadius: 2.5)
|
||||
.fill(batteryColor)
|
||||
.frame(
|
||||
width: CGFloat(((CGFloat(CFloat(levelBattery)) / 100) * (batteryWidth - 6))),
|
||||
height: (batteryWidth - 2.75) - 18
|
||||
)
|
||||
.padding(.leading, 2)
|
||||
|
||||
if iconStatus != "" && (isForNotification || Defaults[.showPowerStatusIcons]) {
|
||||
ZStack {
|
||||
Image(iconStatus)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(.white)
|
||||
.frame(
|
||||
width: 17,
|
||||
height: 17
|
||||
)
|
||||
}
|
||||
.frame(width: batteryWidth, height: batteryWidth)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A view that displays detailed battery information and settings.
|
||||
struct BatteryMenuView: View {
|
||||
|
||||
var isPluggedIn: Bool
|
||||
var isCharging: Bool
|
||||
var levelBattery: Float
|
||||
var maxCapacity: Float
|
||||
var timeToFullCharge: Int
|
||||
var isInLowPowerMode: Bool
|
||||
var onDismiss: () -> Void
|
||||
|
||||
@Environment(\.openURL) private var openURL
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
|
||||
HStack {
|
||||
Text("Battery Status")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
Spacer()
|
||||
Text("\(Int(levelBattery))%")
|
||||
.font(.headline)
|
||||
.fontWeight(.semibold)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Max Capacity: \(Int(maxCapacity))%")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.regular)
|
||||
if isInLowPowerMode {
|
||||
Label("Low Power Mode", systemImage: "bolt.circle")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.regular)
|
||||
}
|
||||
if isCharging {
|
||||
Label("Charging", systemImage: "bolt.fill")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.regular)
|
||||
}
|
||||
if isPluggedIn {
|
||||
Label("Plugged In", systemImage: "powerplug.fill")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.regular)
|
||||
}
|
||||
if timeToFullCharge > 0 {
|
||||
Label("Time to Full Charge: \(timeToFullCharge) min", systemImage: "clock")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.regular)
|
||||
}
|
||||
if !isCharging && isPluggedIn && levelBattery >= 80 {
|
||||
Label("Charging on Hold: Desktop Mode", systemImage: "desktopcomputer")
|
||||
.font(.subheadline)
|
||||
.fontWeight(.regular)
|
||||
}
|
||||
|
||||
}
|
||||
.padding(.vertical, 8)
|
||||
|
||||
Divider().background(Color.white)
|
||||
|
||||
Button(action: openBatteryPreferences) {
|
||||
Label("Battery Settings", systemImage: "gearshape")
|
||||
.fontWeight(.regular)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.buttonStyle(.plain)
|
||||
.padding(.vertical, 8)
|
||||
}
|
||||
.padding()
|
||||
.frame(width: 280)
|
||||
.foregroundColor(.white)
|
||||
}
|
||||
|
||||
private func openBatteryPreferences() {
|
||||
if let url = URL(string: "x-apple.systempreferences:com.apple.preference.battery") {
|
||||
openURL(url)
|
||||
onDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A view that displays the battery status and allows interaction to show detailed information.
|
||||
struct DynamicIslandBatteryView: View {
|
||||
|
||||
@State var batteryWidth: CGFloat = 26
|
||||
var isCharging: Bool = false
|
||||
var isInLowPowerMode: Bool = false
|
||||
var isPluggedIn: Bool = false
|
||||
var levelBattery: Float = 0
|
||||
var maxCapacity: Float = 0
|
||||
var timeToFullCharge: Int = 0
|
||||
@State var isForNotification: Bool = false
|
||||
|
||||
@State private var showPopupMenu: Bool = false
|
||||
@State private var isPressed: Bool = false
|
||||
@State private var isHoveringPopover: Bool = false
|
||||
|
||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
if Defaults[.showBatteryPercentage] {
|
||||
Text("\(Int32(levelBattery))%")
|
||||
.font(.callout)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
BatteryView(
|
||||
levelBattery: levelBattery,
|
||||
isPluggedIn: isPluggedIn,
|
||||
isCharging: isCharging,
|
||||
isInLowPowerMode: isInLowPowerMode,
|
||||
batteryWidth: batteryWidth,
|
||||
isForNotification: isForNotification
|
||||
)
|
||||
}
|
||||
.scaleEffect(isPressed ? 0.95 : 1.0)
|
||||
.animation(.easeInOut(duration: 0.2), value: isPressed)
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { _ in
|
||||
withAnimation {
|
||||
isPressed = true
|
||||
}
|
||||
}
|
||||
.onEnded { _ in
|
||||
withAnimation {
|
||||
isPressed = false
|
||||
showPopupMenu.toggle()
|
||||
}
|
||||
}
|
||||
)
|
||||
.popover(
|
||||
isPresented: $showPopupMenu,
|
||||
arrowEdge: .bottom) {
|
||||
BatteryMenuView(
|
||||
isPluggedIn: isPluggedIn,
|
||||
isCharging: isCharging,
|
||||
levelBattery: levelBattery,
|
||||
maxCapacity: maxCapacity,
|
||||
timeToFullCharge: timeToFullCharge,
|
||||
isInLowPowerMode: isInLowPowerMode,
|
||||
onDismiss: {
|
||||
showPopupMenu = false
|
||||
}
|
||||
)
|
||||
.onHover { hovering in
|
||||
isHoveringPopover = hovering
|
||||
}
|
||||
}
|
||||
.onChange(of: showPopupMenu) { _, _ in
|
||||
updateBatteryPopoverActiveState()
|
||||
}
|
||||
.onChange(of: isHoveringPopover) { _, _ in
|
||||
updateBatteryPopoverActiveState()
|
||||
}
|
||||
}
|
||||
|
||||
private func updateBatteryPopoverActiveState() {
|
||||
vm.isBatteryPopoverActive = showPopupMenu && isHoveringPopover
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
DynamicIslandBatteryView(
|
||||
batteryWidth: 30,
|
||||
isCharging: false,
|
||||
isInLowPowerMode: false,
|
||||
isPluggedIn: true,
|
||||
levelBattery: 80,
|
||||
maxCapacity: 100,
|
||||
timeToFullCharge: 10,
|
||||
isForNotification: false
|
||||
).frame(width: 200, height: 200)
|
||||
}
|
||||
147
DynamicIsland/components/Live activities/InlineHUD.swift
Normal file
147
DynamicIsland/components/Live activities/InlineHUD.swift
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
//
|
||||
// InlineHUDs.swift
|
||||
// boringNotch
|
||||
//
|
||||
// Created by Richard Kunkli on 14/09/2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
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
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(spacing: 5) {
|
||||
Group {
|
||||
switch (type) {
|
||||
case .volume:
|
||||
if icon.isEmpty {
|
||||
Image(systemName: SpeakerSymbol(value))
|
||||
.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)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
}
|
||||
.foregroundStyle(.white)
|
||||
.symbolVariant(.fill)
|
||||
|
||||
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 {
|
||||
HStack {
|
||||
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"
|
||||
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())
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// LiveActivityModifier.swift
|
||||
// boringNotch
|
||||
//
|
||||
// Created by Richard Kunkli on 12/08/2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
enum ActivityType {
|
||||
case mediaPlayback
|
||||
case charging
|
||||
case download
|
||||
}
|
||||
|
||||
struct LiveActivityModifier<Left: View, Right: View>: ViewModifier {
|
||||
let `for`: ActivityType
|
||||
let leftContent: () -> Left
|
||||
let rightContent: () -> Right
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.overlay(
|
||||
HStack {
|
||||
leftContent()
|
||||
Spacer()
|
||||
//.frame(minWidth: vm.closedNotchSize.width)
|
||||
rightContent()
|
||||
}
|
||||
.padding()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
extension View {
|
||||
func liveActivity<Left: View, Right: View>(
|
||||
for activityId: ActivityType,
|
||||
@ViewBuilder left: @escaping () -> Left,
|
||||
@ViewBuilder right: @escaping () -> Right
|
||||
) -> some View {
|
||||
self.modifier(LiveActivityModifier(for: activityId, leftContent: left, rightContent: right))
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
//
|
||||
// MarqueeTextView.swift
|
||||
// boringNotch
|
||||
//
|
||||
// Created by Richard Kunkli on 08/08/2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SizePreferenceKey: PreferenceKey {
|
||||
static var defaultValue: CGSize = .zero
|
||||
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
|
||||
value = nextValue()
|
||||
}
|
||||
}
|
||||
|
||||
struct MeasureSizeModifier: ViewModifier {
|
||||
func body(content: Content) -> some View {
|
||||
content.background(GeometryReader { geometry in
|
||||
Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct MarqueeText: View {
|
||||
@Binding var text: String
|
||||
let font: Font
|
||||
let nsFont: NSFont.TextStyle
|
||||
let textColor: Color
|
||||
let backgroundColor: Color
|
||||
let minDuration: Double
|
||||
let frameWidth: CGFloat
|
||||
|
||||
@State private var animate = false
|
||||
@State private var textSize: CGSize = .zero
|
||||
@State private var offset: CGFloat = 0
|
||||
|
||||
init(_ text: Binding<String>, 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
|
||||
self.backgroundColor = backgroundColor
|
||||
self.minDuration = minDuration
|
||||
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(needsScrolling ? 1 : 0)
|
||||
}
|
||||
.id(text)
|
||||
.font(font)
|
||||
.foregroundColor(textColor)
|
||||
.fixedSize(horizontal: true, vertical: false)
|
||||
.offset(x: self.animate ? offset : 0)
|
||||
.animation(
|
||||
self.animate ?
|
||||
.linear(duration: Double(textSize.width / 30))
|
||||
.delay(minDuration)
|
||||
.repeatForever(autoreverses: false) : .none,
|
||||
value: self.animate
|
||||
)
|
||||
.background(backgroundColor)
|
||||
.modifier(MeasureSizeModifier())
|
||||
.onPreferenceChange(SizePreferenceKey.self) { size in
|
||||
self.textSize = CGSize(width: size.width / 2, height: NSFont.preferredFont(forTextStyle: nsFont).pointSize)
|
||||
self.animate = false
|
||||
self.offset = 0
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01){
|
||||
if needsScrolling {
|
||||
self.animate = true
|
||||
self.offset = -(textSize.width + 10)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: frameWidth, alignment: .leading)
|
||||
.clipped()
|
||||
}
|
||||
.frame(height: textSize.height * 1.3)
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// SystemEventIndicatorModifier.swift
|
||||
// boringNotch
|
||||
//
|
||||
// Created by Richard Kunkli on 12/08/2024.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import Defaults
|
||||
|
||||
struct SystemEventIndicatorModifier: View {
|
||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||
@Binding var eventType: SneakContentType
|
||||
@Binding var value: CGFloat {
|
||||
didSet {
|
||||
DispatchQueue.main.async {
|
||||
self.sendEventBack(value)
|
||||
self.vm.objectWillChange.send()
|
||||
}
|
||||
}
|
||||
}
|
||||
@Binding var icon: String
|
||||
let showSlider: Bool = false
|
||||
var sendEventBack: (CGFloat) -> Void
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 14) {
|
||||
switch (eventType) {
|
||||
case .volume:
|
||||
if icon.isEmpty {
|
||||
Image(systemName: SpeakerSymbol(value))
|
||||
.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: "sun.max.fill")
|
||||
.contentTransition(.symbolEffect)
|
||||
.frame(width: 20, height: 15)
|
||||
.foregroundStyle(.white)
|
||||
case .backlight:
|
||||
Image(systemName: "keyboard")
|
||||
.contentTransition(.symbolEffect)
|
||||
.frame(width: 20, height: 15)
|
||||
.foregroundStyle(.white)
|
||||
case .mic:
|
||||
Image(systemName: "mic")
|
||||
.symbolVariant(value > 0 ? .none : .slash)
|
||||
.contentTransition(.interpolate)
|
||||
.frame(width: 20, height: 15)
|
||||
.foregroundStyle(.white)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
if (eventType != .mic) {
|
||||
DraggableProgressBar(value: $value)
|
||||
} else {
|
||||
Text("Mic \(value > 0 ? "unmuted" : "muted")")
|
||||
.foregroundStyle(.gray)
|
||||
.lineLimit(1)
|
||||
.allowsTightening(true)
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.symbolVariant(.fill)
|
||||
.imageScale(.large)
|
||||
}
|
||||
|
||||
func SpeakerSymbol(_ value: CGFloat) -> String {
|
||||
switch(value) {
|
||||
case 0:
|
||||
return "speaker.slash"
|
||||
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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DraggableProgressBar: View {
|
||||
@EnvironmentObject var vm: DynamicIslandViewModel
|
||||
@Binding var value: CGFloat
|
||||
|
||||
@State private var isDragging = false
|
||||
@State private var dragOffset: CGFloat = 0
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
GeometryReader { geo in
|
||||
ZStack(alignment: .leading) {
|
||||
Capsule()
|
||||
.fill(.tertiary)
|
||||
Group {
|
||||
if Defaults[.enableGradient] {
|
||||
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)
|
||||
}
|
||||
}
|
||||
.opacity(value.isZero ? 0 : 1)
|
||||
}
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 0)
|
||||
.onChanged { gesture in
|
||||
withAnimation(.smooth(duration: 0.3)) {
|
||||
isDragging = true
|
||||
updateValue(gesture: gesture, in: geo)
|
||||
}
|
||||
}
|
||||
.onEnded { _ in
|
||||
withAnimation(.smooth(duration: 0.3)) {
|
||||
isDragging = false
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
.frame(height: Defaults[.inlineHUD] ? isDragging ? 8 : 5 : isDragging ? 9 : 6)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateValue(gesture: DragGesture.Value, in geometry: GeometryProxy) {
|
||||
let dragPosition = gesture.location.x
|
||||
let newValue = dragPosition / geometry.size.width
|
||||
|
||||
value = max(0, min(newValue, 1))
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue