mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +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" : {
|
||||
|
||||
},
|
||||
"Liquid Glass requires macOS 26 or later." : {
|
||||
|
||||
},
|
||||
"Live Monitoring" : {
|
||||
|
||||
|
|
@ -9198,6 +9201,9 @@
|
|||
},
|
||||
"Lock Screen" : {
|
||||
|
||||
},
|
||||
"Lock screen material" : {
|
||||
|
||||
},
|
||||
"Loop Mode" : {
|
||||
|
||||
|
|
|
|||
|
|
@ -9,15 +9,24 @@ import SwiftUI
|
|||
import Defaults
|
||||
|
||||
struct LockScreenMusicPanel: View {
|
||||
static let defaultSize = CGSize(width: 420, height: 180)
|
||||
|
||||
@ObservedObject var musicManager = MusicManager.shared
|
||||
@State private var sliderValue: Double = 0
|
||||
@State private var dragging: Bool = false
|
||||
@State private var lastDragged: Date = .distantPast
|
||||
@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 var panelSize: CGSize { Self.defaultSize }
|
||||
|
||||
private var usesLiquidGlass: Bool {
|
||||
if #available(macOS 26.0, *) {
|
||||
return lockScreenGlassStyle == .liquid
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
if isActive {
|
||||
|
|
@ -27,7 +36,7 @@ struct LockScreenMusicPanel: View {
|
|||
}
|
||||
} else {
|
||||
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(.vertical, 16)
|
||||
.frame(width: panelWidth, height: panelHeight)
|
||||
.background {
|
||||
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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
.frame(width: panelSize.width, height: panelSize.height)
|
||||
.background(panelBackground)
|
||||
.overlay {
|
||||
RoundedRectangle(cornerRadius: cornerRadius)
|
||||
.stroke(Color.white.opacity(0.35), lineWidth: 1.4)
|
||||
|
|
@ -115,9 +106,7 @@ struct LockScreenMusicPanel: View {
|
|||
.onAppear {
|
||||
sliderValue = musicManager.elapsedTime
|
||||
isActive = true
|
||||
let formatter = DateFormatter()
|
||||
formatter.dateFormat = "HH:mm:ss.SSS"
|
||||
print("[\(formatter.string(from: Date()))] LockScreenMusicPanel: ✅ View appeared")
|
||||
logPanelAppearance()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,9 +230,8 @@ struct LockScreenMusicPanel: View {
|
|||
Image(systemName: musicManager.isPlaying ? "pause.fill" : "play.fill")
|
||||
.font(.system(size: 24, weight: .semibold))
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 54, height: 54)
|
||||
.background(Color.white.opacity(0.15))
|
||||
.clipShape(Circle())
|
||||
.frame(width: 48, height: 48)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
|
@ -253,6 +241,8 @@ struct LockScreenMusicPanel: View {
|
|||
Image(systemName: icon)
|
||||
.font(.system(size: size, weight: .medium))
|
||||
.foregroundColor(isActive ? .red : .white.opacity(0.8))
|
||||
.frame(width: 32, height: 32)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
|
|
@ -264,4 +254,38 @@ struct LockScreenMusicPanel: View {
|
|||
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 {
|
||||
Button(action: {
|
||||
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())
|
||||
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() }
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private func controlButton(icon: String, size: CGFloat = 18, isActive: Bool = false, action: @escaping () -> Void) -> some View {
|
||||
Button(action: action) {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: size, weight: .medium))
|
||||
.foregroundColor(isActive ? .red : .white.opacity(0.8))
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
MinimalisticSquircircleButton(
|
||||
icon: icon,
|
||||
fontSize: size,
|
||||
fontWeight: .medium,
|
||||
frameSize: CGSize(width: 40, height: 40),
|
||||
cornerRadius: 16,
|
||||
foregroundColor: isActive ? .red : .white.opacity(0.85),
|
||||
action: action
|
||||
)
|
||||
}
|
||||
|
||||
private var repeatIcon: String {
|
||||
|
|
@ -274,3 +279,37 @@ struct MinimalisticAlbumArtView: View {
|
|||
.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(.showRecordingIndicator) var showRecordingIndicator
|
||||
@Default(.enableMinimalisticUI) var enableMinimalisticUI
|
||||
@Default(.lockScreenGlassStyle) var lockScreenGlassStyle
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
|
|
@ -298,6 +299,23 @@ struct GeneralSettings: View {
|
|||
|
||||
Section {
|
||||
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") {
|
||||
copyLatestCrashReport()
|
||||
|
|
|
|||
|
|
@ -70,3 +70,10 @@ enum SliderColorEnum: String, CaseIterable, Defaults.Serializable {
|
|||
case albumArt = "Match album art"
|
||||
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
|
||||
}
|
||||
|
||||
let panelWidth: CGFloat = 460
|
||||
let panelHeight: CGFloat = 180
|
||||
let panelSize = LockScreenMusicPanel.defaultSize
|
||||
let screenFrame = screen.frame
|
||||
let originX = screenFrame.origin.x + (screenFrame.width / 2) - (panelWidth / 2)
|
||||
let originY = screenFrame.origin.y + (screenFrame.height / 2) - panelHeight - 32
|
||||
let targetFrame = NSRect(x: originX, y: originY, width: panelWidth, height: panelHeight)
|
||||
let originX = screenFrame.origin.x + (screenFrame.width / 2) - (panelSize.width / 2)
|
||||
let originY = screenFrame.origin.y + (screenFrame.height / 2) - panelSize.height - 32
|
||||
let targetFrame = NSRect(x: originX, y: originY, width: panelSize.width, height: panelSize.height)
|
||||
|
||||
let window: NSWindow
|
||||
|
||||
|
|
|
|||
|
|
@ -336,6 +336,7 @@ extension Defaults.Keys {
|
|||
static let showShuffleAndRepeat = Key<Bool>("showShuffleAndRepeat", default: false)
|
||||
// Enable lock screen media widget (shows the standalone panel when screen is locked)
|
||||
static let enableLockScreenMediaWidget = Key<Bool>("enableLockScreenMediaWidget", default: true)
|
||||
static let lockScreenGlassStyle = Key<LockScreenGlassStyle>("lockScreenGlassStyle", default: .liquid)
|
||||
|
||||
// MARK: Battery
|
||||
static let showPowerStatusNotifications = Key<Bool>("showPowerStatusNotifications", default: true)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue