mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-29 08:04:32 +00:00
Fully working stuff
This commit is contained in:
parent
d0448ec2cb
commit
070f88df6f
7 changed files with 143 additions and 49 deletions
|
|
@ -9189,6 +9189,9 @@
|
||||||
},
|
},
|
||||||
"LinkedIn" : {
|
"LinkedIn" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Liquid Glass requires macOS 26 or later." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Live Monitoring" : {
|
"Live Monitoring" : {
|
||||||
|
|
||||||
|
|
@ -9198,6 +9201,9 @@
|
||||||
},
|
},
|
||||||
"Lock Screen" : {
|
"Lock Screen" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Lock screen material" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Loop Mode" : {
|
"Loop Mode" : {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,24 @@ import SwiftUI
|
||||||
import Defaults
|
import Defaults
|
||||||
|
|
||||||
struct LockScreenMusicPanel: View {
|
struct LockScreenMusicPanel: View {
|
||||||
|
static let defaultSize = CGSize(width: 420, height: 180)
|
||||||
|
|
||||||
@ObservedObject var musicManager = MusicManager.shared
|
@ObservedObject var musicManager = MusicManager.shared
|
||||||
@State private var sliderValue: Double = 0
|
@State private var sliderValue: Double = 0
|
||||||
@State private var dragging: Bool = false
|
@State private var dragging: Bool = false
|
||||||
@State private var lastDragged: Date = .distantPast
|
@State private var lastDragged: Date = .distantPast
|
||||||
@State private var isActive = true
|
@State private var isActive = true
|
||||||
|
@Default(.lockScreenGlassStyle) var lockScreenGlassStyle
|
||||||
|
|
||||||
private let panelWidth: CGFloat = 460
|
|
||||||
private let panelHeight: CGFloat = 180
|
|
||||||
private let cornerRadius: CGFloat = 28
|
private let cornerRadius: CGFloat = 28
|
||||||
|
private var panelSize: CGSize { Self.defaultSize }
|
||||||
|
|
||||||
|
private var usesLiquidGlass: Bool {
|
||||||
|
if #available(macOS 26.0, *) {
|
||||||
|
return lockScreenGlassStyle == .liquid
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if isActive {
|
if isActive {
|
||||||
|
|
@ -27,7 +36,7 @@ struct LockScreenMusicPanel: View {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: panelWidth, height: panelHeight)
|
.frame(width: panelSize.width, height: panelSize.height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,26 +96,8 @@ struct LockScreenMusicPanel: View {
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 20)
|
.padding(.horizontal, 20)
|
||||||
.padding(.vertical, 16)
|
.padding(.vertical, 16)
|
||||||
.frame(width: panelWidth, height: panelHeight)
|
.frame(width: panelSize.width, height: panelSize.height)
|
||||||
.background {
|
.background(panelBackground)
|
||||||
if #available(macOS 26.0, *) {
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.glassEffect(in: .rect(cornerRadius: cornerRadius))
|
|
||||||
.onAppear {
|
|
||||||
let formatter = DateFormatter()
|
|
||||||
formatter.dateFormat = "HH:mm:ss.SSS"
|
|
||||||
print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: ✨ Using macOS 26.0+ Liquid Glass effect")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
|
||||||
.fill(.ultraThinMaterial)
|
|
||||||
.onAppear {
|
|
||||||
let formatter = DateFormatter()
|
|
||||||
formatter.dateFormat = "HH:mm:ss.SSS"
|
|
||||||
print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: 🪟 Using ultraThinMaterial fallback (macOS < 26.0)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.overlay {
|
.overlay {
|
||||||
RoundedRectangle(cornerRadius: cornerRadius)
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
.stroke(Color.white.opacity(0.35), lineWidth: 1.4)
|
.stroke(Color.white.opacity(0.35), lineWidth: 1.4)
|
||||||
|
|
@ -115,9 +106,7 @@ struct LockScreenMusicPanel: View {
|
||||||
.onAppear {
|
.onAppear {
|
||||||
sliderValue = musicManager.elapsedTime
|
sliderValue = musicManager.elapsedTime
|
||||||
isActive = true
|
isActive = true
|
||||||
let formatter = DateFormatter()
|
logPanelAppearance()
|
||||||
formatter.dateFormat = "HH:mm:ss.SSS"
|
|
||||||
print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: ✅ View appeared")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,9 +230,8 @@ struct LockScreenMusicPanel: View {
|
||||||
Image(systemName: musicManager.isPlaying ? "pause.fill" : "play.fill")
|
Image(systemName: musicManager.isPlaying ? "pause.fill" : "play.fill")
|
||||||
.font(.system(size: 24, weight: .semibold))
|
.font(.system(size: 24, weight: .semibold))
|
||||||
.foregroundColor(.white)
|
.foregroundColor(.white)
|
||||||
.frame(width: 54, height: 54)
|
.frame(width: 48, height: 48)
|
||||||
.background(Color.white.opacity(0.15))
|
.contentShape(Rectangle())
|
||||||
.clipShape(Circle())
|
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
}
|
}
|
||||||
|
|
@ -253,6 +241,8 @@ struct LockScreenMusicPanel: View {
|
||||||
Image(systemName: icon)
|
Image(systemName: icon)
|
||||||
.font(.system(size: size, weight: .medium))
|
.font(.system(size: size, weight: .medium))
|
||||||
.foregroundColor(isActive ? .red : .white.opacity(0.8))
|
.foregroundColor(isActive ? .red : .white.opacity(0.8))
|
||||||
|
.frame(width: 32, height: 32)
|
||||||
|
.contentShape(Rectangle())
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
.buttonStyle(PlainButtonStyle())
|
||||||
}
|
}
|
||||||
|
|
@ -264,4 +254,38 @@ struct LockScreenMusicPanel: View {
|
||||||
case .one: return "repeat.1"
|
case .one: return "repeat.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var panelBackground: some View {
|
||||||
|
if usesLiquidGlass {
|
||||||
|
liquidPanelBackground
|
||||||
|
} else {
|
||||||
|
frostedPanelBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var liquidPanelBackground: some View {
|
||||||
|
if #available(macOS 26.0, *) {
|
||||||
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
|
.glassEffect(
|
||||||
|
.regular
|
||||||
|
.tint(Color.white.opacity(0.12))
|
||||||
|
.interactive(),
|
||||||
|
in: .rect(cornerRadius: cornerRadius)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var frostedPanelBackground: some View {
|
||||||
|
RoundedRectangle(cornerRadius: cornerRadius)
|
||||||
|
.fill(.ultraThinMaterial)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func logPanelAppearance(event: String = "✅ View appeared") {
|
||||||
|
let formatter = DateFormatter()
|
||||||
|
formatter.dateFormat = "HH:mm:ss.SSS"
|
||||||
|
let styleDescriptor = usesLiquidGlass ? "Liquid Glass" : "Frosted"
|
||||||
|
print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: \(event) – \(styleDescriptor)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -192,24 +192,29 @@ struct MinimalisticMusicPlayerView: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var playPauseButton: some View {
|
private var playPauseButton: some View {
|
||||||
Button(action: {
|
MinimalisticSquircircleButton(
|
||||||
|
icon: musicManager.isPlaying ? "pause.fill" : "play.fill",
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: .semibold,
|
||||||
|
frameSize: CGSize(width: 54, height: 54),
|
||||||
|
cornerRadius: 20,
|
||||||
|
foregroundColor: .white,
|
||||||
|
action: {
|
||||||
Task { await musicManager.togglePlay() }
|
Task { await musicManager.togglePlay() }
|
||||||
}) {
|
|
||||||
Image(systemName: musicManager.isPlaying ? "pause.fill" : "play.fill")
|
|
||||||
.font(.system(size: 24, weight: .semibold))
|
|
||||||
.foregroundColor(.white)
|
|
||||||
.frame(width: 50, height: 50)
|
|
||||||
}
|
}
|
||||||
.buttonStyle(PlainButtonStyle())
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func controlButton(icon: String, size: CGFloat = 18, isActive: Bool = false, action: @escaping () -> Void) -> some View {
|
private func controlButton(icon: String, size: CGFloat = 18, isActive: Bool = false, action: @escaping () -> Void) -> some View {
|
||||||
Button(action: action) {
|
MinimalisticSquircircleButton(
|
||||||
Image(systemName: icon)
|
icon: icon,
|
||||||
.font(.system(size: size, weight: .medium))
|
fontSize: size,
|
||||||
.foregroundColor(isActive ? .red : .white.opacity(0.8))
|
fontWeight: .medium,
|
||||||
}
|
frameSize: CGSize(width: 40, height: 40),
|
||||||
.buttonStyle(PlainButtonStyle())
|
cornerRadius: 16,
|
||||||
|
foregroundColor: isActive ? .red : .white.opacity(0.85),
|
||||||
|
action: action
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private var repeatIcon: String {
|
private var repeatIcon: String {
|
||||||
|
|
@ -274,3 +279,37 @@ struct MinimalisticAlbumArtView: View {
|
||||||
.scaleEffect(musicManager.isPlaying ? 1 : 0.85)
|
.scaleEffect(musicManager.isPlaying ? 1 : 0.85)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Hover-highlighted control button
|
||||||
|
|
||||||
|
private struct MinimalisticSquircircleButton: View {
|
||||||
|
let icon: String
|
||||||
|
let fontSize: CGFloat
|
||||||
|
let fontWeight: Font.Weight
|
||||||
|
let frameSize: CGSize
|
||||||
|
let cornerRadius: CGFloat
|
||||||
|
let foregroundColor: Color
|
||||||
|
let action: () -> Void
|
||||||
|
|
||||||
|
@State private var isHovering = false
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
Button(action: action) {
|
||||||
|
Image(systemName: icon)
|
||||||
|
.font(.system(size: fontSize, weight: fontWeight))
|
||||||
|
.foregroundColor(foregroundColor)
|
||||||
|
.frame(width: frameSize.width, height: frameSize.height)
|
||||||
|
.background(
|
||||||
|
RoundedRectangle(cornerRadius: cornerRadius, style: .continuous)
|
||||||
|
.fill(isHovering ? Color.white.opacity(0.18) : .clear)
|
||||||
|
)
|
||||||
|
.contentShape(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous))
|
||||||
|
}
|
||||||
|
.buttonStyle(PlainButtonStyle())
|
||||||
|
.onHover { hovering in
|
||||||
|
withAnimation(.easeOut(duration: 0.18)) {
|
||||||
|
isHovering = hovering
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -174,6 +174,7 @@ struct GeneralSettings: View {
|
||||||
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
|
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
|
||||||
@Default(.showRecordingIndicator) var showRecordingIndicator
|
@Default(.showRecordingIndicator) var showRecordingIndicator
|
||||||
@Default(.enableMinimalisticUI) var enableMinimalisticUI
|
@Default(.enableMinimalisticUI) var enableMinimalisticUI
|
||||||
|
@Default(.lockScreenGlassStyle) var lockScreenGlassStyle
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
|
|
@ -298,6 +299,23 @@ struct GeneralSettings: View {
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Enable Lock Screen Live Activity", key: .enableLockScreenLiveActivity)
|
Defaults.Toggle("Enable Lock Screen Live Activity", key: .enableLockScreenLiveActivity)
|
||||||
|
if #available(macOS 26.0, *) {
|
||||||
|
Picker("Lock screen material", selection: $lockScreenGlassStyle) {
|
||||||
|
ForEach(LockScreenGlassStyle.allCases) { style in
|
||||||
|
Text(style.rawValue).tag(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Picker("Lock screen material", selection: $lockScreenGlassStyle) {
|
||||||
|
ForEach(LockScreenGlassStyle.allCases) { style in
|
||||||
|
Text(style.rawValue).tag(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.disabled(true)
|
||||||
|
Text("Liquid Glass requires macOS 26 or later.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
|
||||||
Button("Copy Latest Crash Report") {
|
Button("Copy Latest Crash Report") {
|
||||||
copyLatestCrashReport()
|
copyLatestCrashReport()
|
||||||
|
|
|
||||||
|
|
@ -70,3 +70,10 @@ enum SliderColorEnum: String, CaseIterable, Defaults.Serializable {
|
||||||
case albumArt = "Match album art"
|
case albumArt = "Match album art"
|
||||||
case accent = "Accent color"
|
case accent = "Accent color"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum LockScreenGlassStyle: String, CaseIterable, Defaults.Serializable, Identifiable {
|
||||||
|
case liquid = "Liquid Glass"
|
||||||
|
case frosted = "Frosted Glass"
|
||||||
|
|
||||||
|
var id: String { rawValue }
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -41,12 +41,11 @@ class LockScreenPanelManager {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let panelWidth: CGFloat = 460
|
let panelSize = LockScreenMusicPanel.defaultSize
|
||||||
let panelHeight: CGFloat = 180
|
|
||||||
let screenFrame = screen.frame
|
let screenFrame = screen.frame
|
||||||
let originX = screenFrame.origin.x + (screenFrame.width / 2) - (panelWidth / 2)
|
let originX = screenFrame.origin.x + (screenFrame.width / 2) - (panelSize.width / 2)
|
||||||
let originY = screenFrame.origin.y + (screenFrame.height / 2) - panelHeight - 32
|
let originY = screenFrame.origin.y + (screenFrame.height / 2) - panelSize.height - 32
|
||||||
let targetFrame = NSRect(x: originX, y: originY, width: panelWidth, height: panelHeight)
|
let targetFrame = NSRect(x: originX, y: originY, width: panelSize.width, height: panelSize.height)
|
||||||
|
|
||||||
let window: NSWindow
|
let window: NSWindow
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -336,6 +336,7 @@ extension Defaults.Keys {
|
||||||
static let showShuffleAndRepeat = Key<Bool>("showShuffleAndRepeat", default: false)
|
static let showShuffleAndRepeat = Key<Bool>("showShuffleAndRepeat", default: false)
|
||||||
// Enable lock screen media widget (shows the standalone panel when screen is locked)
|
// Enable lock screen media widget (shows the standalone panel when screen is locked)
|
||||||
static let enableLockScreenMediaWidget = Key<Bool>("enableLockScreenMediaWidget", default: true)
|
static let enableLockScreenMediaWidget = Key<Bool>("enableLockScreenMediaWidget", default: true)
|
||||||
|
static let lockScreenGlassStyle = Key<LockScreenGlassStyle>("lockScreenGlassStyle", default: .liquid)
|
||||||
|
|
||||||
// MARK: Battery
|
// MARK: Battery
|
||||||
static let showPowerStatusNotifications = Key<Bool>("showPowerStatusNotifications", default: true)
|
static let showPowerStatusNotifications = Key<Bool>("showPowerStatusNotifications", default: true)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue