mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 23:24:32 +00:00
Merge pull request #140 from Ebullioscopic/hotfix/volume-bug
Hotfix/volume bug
This commit is contained in:
commit
805f367c44
7 changed files with 81 additions and 218 deletions
|
|
@ -2033,6 +2033,7 @@
|
|||
}
|
||||
},
|
||||
"Add manually" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -6674,6 +6675,7 @@
|
|||
}
|
||||
},
|
||||
"Description" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -6790,6 +6792,7 @@
|
|||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Disable" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -8581,6 +8584,7 @@
|
|||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Extensions" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -10537,6 +10541,7 @@
|
|||
}
|
||||
},
|
||||
"Installed extensions" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -13893,6 +13898,7 @@
|
|||
}
|
||||
},
|
||||
"No extension installed" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -15721,6 +15727,7 @@
|
|||
"isCommentAutoGenerated" : true
|
||||
},
|
||||
"Restart" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -15863,6 +15870,7 @@
|
|||
}
|
||||
},
|
||||
"Running" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
@ -19150,6 +19158,7 @@
|
|||
}
|
||||
},
|
||||
"Uninstall" : {
|
||||
"extractionState" : "stale",
|
||||
"localizations" : {
|
||||
"ar" : {
|
||||
"stringUnit" : {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,6 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
|
|||
case downloads
|
||||
case shelf
|
||||
case shortcuts
|
||||
case extensions
|
||||
case about
|
||||
|
||||
var id: String { rawValue }
|
||||
|
|
@ -58,7 +57,6 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
|
|||
case .downloads: return "Downloads"
|
||||
case .shelf: return "Shelf"
|
||||
case .shortcuts: return "Shortcuts"
|
||||
case .extensions: return "Extensions"
|
||||
case .about: return "About"
|
||||
}
|
||||
}
|
||||
|
|
@ -82,7 +80,6 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
|
|||
case .downloads: return "square.and.arrow.down"
|
||||
case .shelf: return "books.vertical"
|
||||
case .shortcuts: return "keyboard"
|
||||
case .extensions: return "puzzlepiece.extension"
|
||||
case .about: return "info.circle"
|
||||
}
|
||||
}
|
||||
|
|
@ -106,7 +103,6 @@ private enum SettingsTab: String, CaseIterable, Identifiable {
|
|||
case .downloads: return .gray
|
||||
case .shelf: return .brown
|
||||
case .shortcuts: return .orange
|
||||
case .extensions: return .blue
|
||||
case .about: return .secondary
|
||||
}
|
||||
}
|
||||
|
|
@ -233,7 +229,6 @@ private struct SettingsForm<Content: View>: View {
|
|||
}
|
||||
|
||||
struct SettingsView: View {
|
||||
@StateObject var extensionManager = DynamicIslandExtensionManager()
|
||||
@State private var selectedTab: SettingsTab = .general
|
||||
@State private var searchText: String = ""
|
||||
@StateObject private var highlightCoordinator = SettingsHighlightCoordinator()
|
||||
|
|
@ -260,11 +255,13 @@ struct SettingsView: View {
|
|||
.padding(.horizontal, 12)
|
||||
|
||||
List(filteredTabs, selection: selectionBinding) { tab in
|
||||
let isComingSoon = tab == .downloads
|
||||
NavigationLink(value: tab) {
|
||||
HStack(spacing: 10) {
|
||||
sidebarIcon(for: tab)
|
||||
.opacity(isComingSoon ? 0.45 : 1)
|
||||
Text(tab.title)
|
||||
.foregroundStyle(Color.primary)
|
||||
.foregroundStyle(isComingSoon ? Color.secondary : Color.primary)
|
||||
if tab == .osd {
|
||||
Spacer()
|
||||
Text("ALPHA")
|
||||
|
|
@ -276,11 +273,15 @@ struct SettingsView: View {
|
|||
Capsule()
|
||||
.fill(Color.orange)
|
||||
)
|
||||
} else if isComingSoon {
|
||||
Spacer()
|
||||
comingSoonTag()
|
||||
}
|
||||
}
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
.tag(tab)
|
||||
.disabled(isComingSoon)
|
||||
.listRowInsets(EdgeInsets(top: 4, leading: 12, bottom: 4, trailing: 10))
|
||||
}
|
||||
.listStyle(SidebarListStyle())
|
||||
|
|
@ -303,13 +304,12 @@ struct SettingsView: View {
|
|||
.opacity(0) // Invisible, but reserves space for a consistent look between tabs
|
||||
.disabled(true)
|
||||
}
|
||||
.environmentObject(extensionManager)
|
||||
.environmentObject(highlightCoordinator)
|
||||
.formStyle(.grouped)
|
||||
.frame(width: 700)
|
||||
.onChange(of: searchText) { _, newValue in
|
||||
let matches = tabsMatchingSearch(newValue)
|
||||
guard let firstMatch = matches.first else { return }
|
||||
guard let firstMatch = matches.first(where: { $0 != .downloads }) else { return }
|
||||
if !matches.contains(resolvedSelection) {
|
||||
selectedTab = firstMatch
|
||||
}
|
||||
|
|
@ -347,7 +347,10 @@ struct SettingsView: View {
|
|||
private var selectionBinding: Binding<SettingsTab> {
|
||||
Binding(
|
||||
get: { resolvedSelection },
|
||||
set: { selectedTab = $0 }
|
||||
set: { newValue in
|
||||
guard newValue != .downloads else { return }
|
||||
selectedTab = newValue
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -382,7 +385,6 @@ struct SettingsView: View {
|
|||
.downloads,
|
||||
.shelf,
|
||||
.shortcuts,
|
||||
.extensions,
|
||||
.about
|
||||
]
|
||||
|
||||
|
|
@ -402,10 +404,11 @@ struct SettingsView: View {
|
|||
}
|
||||
|
||||
private var searchSuggestions: [SettingsSearchEntry] {
|
||||
Array(searchEntries(matching: searchText).prefix(8))
|
||||
Array(searchEntries(matching: searchText).filter { $0.tab != .downloads }.prefix(8))
|
||||
}
|
||||
|
||||
private func handleSearchSuggestionSelection(_ suggestion: SettingsSearchEntry) {
|
||||
guard suggestion.tab != .downloads else { return }
|
||||
highlightCoordinator.focus(on: suggestion)
|
||||
selectedTab = suggestion.tab
|
||||
}
|
||||
|
|
@ -583,12 +586,6 @@ struct SettingsView: View {
|
|||
SettingsSearchEntry(tab: .battery, title: "Show power status notifications", keywords: ["notifications", "power"], highlightID: SettingsTab.battery.highlightID(for: "Show power status notifications")),
|
||||
SettingsSearchEntry(tab: .battery, title: "Show power status icons", keywords: ["power icons", "charging icon"], highlightID: SettingsTab.battery.highlightID(for: "Show power status icons")),
|
||||
|
||||
// Downloads
|
||||
SettingsSearchEntry(tab: .downloads, title: "Show download progress", keywords: ["downloads", "progress"], highlightID: SettingsTab.downloads.highlightID(for: "Show download progress")),
|
||||
SettingsSearchEntry(tab: .downloads, title: "Enable Safari Downloads", keywords: ["safari", "browser"], highlightID: SettingsTab.downloads.highlightID(for: "Enable Safari Downloads")),
|
||||
SettingsSearchEntry(tab: .downloads, title: "Download indicator style", keywords: ["indicator", "style"], highlightID: SettingsTab.downloads.highlightID(for: "Download indicator style")),
|
||||
SettingsSearchEntry(tab: .downloads, title: "Download icon style", keywords: ["icon", "style"], highlightID: SettingsTab.downloads.highlightID(for: "Download icon style")),
|
||||
|
||||
// HUDs
|
||||
SettingsSearchEntry(tab: .hud, title: "Show Bluetooth device connections", keywords: ["bluetooth", "hud"], highlightID: SettingsTab.hud.highlightID(for: "Show Bluetooth device connections")),
|
||||
SettingsSearchEntry(tab: .hud, title: "Use circular battery indicator", keywords: ["battery", "circular"], highlightID: SettingsTab.hud.highlightID(for: "Use circular battery indicator")),
|
||||
|
|
@ -714,8 +711,6 @@ struct SettingsView: View {
|
|||
switch tab {
|
||||
case .timer, .stats, .clipboard, .screenAssistant, .colorPicker, .shelf:
|
||||
return !enableMinimalisticUI
|
||||
case .downloads:
|
||||
return extensionManager.installedExtensions.contains { $0.bundleIdentifier == downloadManagerExtension }
|
||||
default:
|
||||
return true
|
||||
}
|
||||
|
|
@ -810,10 +805,6 @@ struct SettingsView: View {
|
|||
SettingsForm(tab: .shortcuts) {
|
||||
Shortcuts()
|
||||
}
|
||||
case .extensions:
|
||||
SettingsForm(tab: .extensions) {
|
||||
Extensions()
|
||||
}
|
||||
case .about:
|
||||
if let controller = updaterController {
|
||||
SettingsForm(tab: .about) {
|
||||
|
|
@ -1150,6 +1141,8 @@ struct Downloads: View {
|
|||
}
|
||||
}
|
||||
.navigationTitle("Downloads")
|
||||
.disabled(true)
|
||||
.opacity(0.6)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1928,125 +1921,6 @@ struct Shelf: View {
|
|||
}
|
||||
}
|
||||
|
||||
struct Extensions: View {
|
||||
@EnvironmentObject var extensionManager: DynamicIslandExtensionManager
|
||||
@State private var effectTrigger: Bool = false
|
||||
var body: some View {
|
||||
Form {
|
||||
//warningBadge("We don't support extensions yet") // Uhhhh You do? <><><> Oori.S
|
||||
Section {
|
||||
List {
|
||||
ForEach(extensionManager.installedExtensions.indices, id: \.self) { index in
|
||||
let item = extensionManager.installedExtensions[index]
|
||||
HStack {
|
||||
AppIcon(for: item.bundleIdentifier)
|
||||
.resizable()
|
||||
.frame(width: 24, height: 24)
|
||||
Text(item.name)
|
||||
ListItemPopover {
|
||||
Text("Description")
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
HStack(spacing: 6) {
|
||||
Circle()
|
||||
.frame(width: 6, height: 6)
|
||||
.foregroundColor(isExtensionRunning(item.bundleIdentifier) ? .green : item.status == .disabled ? .gray : .red)
|
||||
.conditionalModifier(isExtensionRunning(item.bundleIdentifier)) { view in
|
||||
view
|
||||
.shadow(color: .green, radius: 3)
|
||||
}
|
||||
Text(isExtensionRunning(item.bundleIdentifier) ? "Running" : item.status == .disabled ? "Disabled" : "Stopped")
|
||||
.contentTransition(.numericText())
|
||||
.foregroundStyle(.secondary)
|
||||
.font(.footnote)
|
||||
}
|
||||
.frame(width: 60, alignment: .leading)
|
||||
|
||||
Menu(content: {
|
||||
Button("Restart") {
|
||||
let ws = NSWorkspace.shared
|
||||
|
||||
if let ext = ws.runningApplications.first(where: { $0.bundleIdentifier == item.bundleIdentifier }) {
|
||||
ext.terminate()
|
||||
}
|
||||
|
||||
if let appURL = ws.urlForApplication(withBundleIdentifier: item.bundleIdentifier) {
|
||||
ws.openApplication(at: appURL, configuration: .init(), completionHandler: nil)
|
||||
}
|
||||
}
|
||||
.keyboardShortcut("R", modifiers: .command)
|
||||
Button("Disable") {
|
||||
if let ext = NSWorkspace.shared.runningApplications.first(where: { $0.bundleIdentifier == item.bundleIdentifier }) {
|
||||
ext.terminate()
|
||||
}
|
||||
extensionManager.installedExtensions[index].status = .disabled
|
||||
}
|
||||
.keyboardShortcut("D", modifiers: .command)
|
||||
Divider()
|
||||
Button("Uninstall", role: .destructive) {
|
||||
//
|
||||
}
|
||||
}, label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
.foregroundStyle(.secondary)
|
||||
})
|
||||
.controlSize(.regular)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.padding(.vertical, 5)
|
||||
}
|
||||
}
|
||||
.frame(minHeight: 120)
|
||||
.actionBar {
|
||||
Button {} label: {
|
||||
HStack(spacing: 3) {
|
||||
Image(systemName: "plus")
|
||||
Text("Add manually")
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
.disabled(true)
|
||||
Spacer()
|
||||
Button {
|
||||
withAnimation(.linear(duration: 1)) {
|
||||
effectTrigger.toggle()
|
||||
} completion: {
|
||||
effectTrigger.toggle()
|
||||
}
|
||||
extensionManager.checkIfExtensionsAreInstalled()
|
||||
} label: {
|
||||
HStack(spacing: 3) {
|
||||
Image(systemName: "arrow.triangle.2.circlepath")
|
||||
.rotationEffect(effectTrigger ? .degrees(360) : .zero)
|
||||
}
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.controlSize(.small)
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.overlay {
|
||||
if extensionManager.installedExtensions.isEmpty {
|
||||
Text("No extension installed")
|
||||
.foregroundStyle(Color(.secondaryLabelColor))
|
||||
.padding(.bottom, 22)
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
HStack(spacing: 0) {
|
||||
Text("Installed extensions")
|
||||
if !extensionManager.installedExtensions.isEmpty {
|
||||
Text(" – \(extensionManager.installedExtensions.count)")
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.navigationTitle("Extensions")
|
||||
// TipsView()
|
||||
// .padding(.horizontal, 19)
|
||||
}
|
||||
}
|
||||
|
||||
struct LiveActivitiesSettings: View {
|
||||
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||
@ObservedObject var recordingManager = ScreenRecordingManager.shared
|
||||
|
|
|
|||
|
|
@ -1,62 +0,0 @@
|
|||
//
|
||||
// DynamicIslandExtensionManager.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Created by Harsh Vardhan Goswami on 04/08/24.
|
||||
// DynamicIslandExtensionManager.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Created by Harsh Vardhan Goswami on 07/09/24.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
var clipboardExtension: String = "dynamicisland.TheDynamicClipboard"
|
||||
var hudExtension: String = "dynamicisland.TheDynamicHUDs"
|
||||
var downloadManagerExtension: String = "dynamicisland.TheDynamicDownloadManager"
|
||||
|
||||
struct Extension: Identifiable, Hashable {
|
||||
var id = UUID()
|
||||
var name: String
|
||||
var bundleIdentifier: String
|
||||
var status: StatusModel = .enabled
|
||||
}
|
||||
|
||||
enum StatusModel {
|
||||
case disabled
|
||||
case enabled
|
||||
}
|
||||
|
||||
class DynamicIslandExtensionManager: ObservableObject {
|
||||
|
||||
@Published var installedExtensions: [Extension] = [] {
|
||||
didSet {
|
||||
// Only log when there's an actual change in content, not just initialization
|
||||
if oldValue != installedExtensions {
|
||||
print("📦 Extensions installed: \(installedExtensions.map { $0.name })")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var extensions = [
|
||||
clipboardExtension,
|
||||
hudExtension
|
||||
]
|
||||
|
||||
init() {
|
||||
checkIfExtensionsAreInstalled()
|
||||
|
||||
DistributedNotificationCenter.default().addObserver(self, selector: #selector(checkIfExtensionsAreInstalled), name: NSNotification.Name("NSWorkspaceDidLaunchApplicationNotification"), object: nil)
|
||||
}
|
||||
|
||||
@objc func checkIfExtensionsAreInstalled() {
|
||||
installedExtensions = []
|
||||
for extensionName in extensions {
|
||||
if NSWorkspace.shared.urlForApplication(withBundleIdentifier: extensionName) != nil {
|
||||
let ext = Extension(name: extensionName.components(separatedBy: ".").last ?? extensionName, bundleIdentifier: extensionName)
|
||||
installedExtensions.append(ext)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import AppKit
|
||||
import SwiftUI
|
||||
import SkyLightWindow
|
||||
import QuartzCore
|
||||
|
||||
@MainActor
|
||||
final class LockScreenReminderWidgetPanelManager {
|
||||
|
|
@ -29,6 +30,20 @@ final class LockScreenReminderWidgetPanelManager {
|
|||
window.contentView = nil
|
||||
}
|
||||
|
||||
func refreshPosition(animated: Bool) {
|
||||
guard let window, window.isVisible, let screen = NSScreen.main else { return }
|
||||
let target = frame(for: window.frame.size, on: screen)
|
||||
if animated {
|
||||
NSAnimationContext.runAnimationGroup { context in
|
||||
context.duration = 0.22
|
||||
context.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
|
||||
window.animator().setFrame(target, display: true)
|
||||
}
|
||||
} else {
|
||||
window.setFrame(target, display: true)
|
||||
}
|
||||
}
|
||||
|
||||
private func render(snapshot: LockScreenReminderWidgetSnapshot, makeVisible: Bool) {
|
||||
guard let screen = NSScreen.main else { return }
|
||||
if !makeVisible, window == nil {
|
||||
|
|
@ -84,26 +99,35 @@ final class LockScreenReminderWidgetPanelManager {
|
|||
let screenFrame = screen.frame
|
||||
let originX = screenFrame.midX - (size.width / 2)
|
||||
let musicTop = LockScreenPanelManager.shared.latestFrame?.maxY ?? (screenFrame.midY - 32)
|
||||
|
||||
let defaultWeatherBottom = screenFrame.midY + (screenFrame.height * 0.14)
|
||||
let weatherBottom = LockScreenWeatherPanelManager.shared.latestFrame?.minY ?? min(screenFrame.maxY - 120, max(defaultWeatherBottom, musicTop + size.height + 80))
|
||||
let timerFrame = LockScreenTimerWidgetPanelManager.shared.latestFrame
|
||||
|
||||
let marginAboveMusic: CGFloat = 16
|
||||
let marginAboveTimer: CGFloat = 24
|
||||
let marginBelowWeather: CGFloat = 16
|
||||
|
||||
let constrainedWeatherBottom = max(weatherBottom, musicTop + size.height + marginAboveMusic + marginBelowWeather)
|
||||
let centerY = (musicTop + constrainedWeatherBottom) / 2
|
||||
let lowerBound = screenFrame.minY + 80
|
||||
let upperBound = constrainedWeatherBottom - marginBelowWeather - size.height
|
||||
let baseLowerBound = musicTop + marginAboveMusic
|
||||
let timerLowerBound = timerFrame.map { $0.maxY + marginAboveTimer }
|
||||
let minYFloor = screenFrame.minY + 80
|
||||
let lowerBound = max(timerLowerBound ?? baseLowerBound, minYFloor)
|
||||
|
||||
var proposedY = centerY - (size.height / 2)
|
||||
if proposedY < lowerBound {
|
||||
proposedY = lowerBound
|
||||
}
|
||||
if upperBound > lowerBound {
|
||||
proposedY = min(proposedY, upperBound)
|
||||
let defaultWeatherBottom = screenFrame.midY + (screenFrame.height * 0.14)
|
||||
let minimumWeatherBottom = lowerBound + size.height + marginBelowWeather
|
||||
let weatherBottom = LockScreenWeatherPanelManager.shared.latestFrame?.minY ?? min(screenFrame.maxY - 120, max(defaultWeatherBottom, minimumWeatherBottom))
|
||||
|
||||
let upperBound = weatherBottom - marginBelowWeather - size.height
|
||||
let clampedUpperBound = max(upperBound, lowerBound)
|
||||
|
||||
let prefersTopAlignment = timerFrame != nil
|
||||
var proposedY: CGFloat
|
||||
|
||||
if prefersTopAlignment {
|
||||
proposedY = clampedUpperBound
|
||||
} else {
|
||||
proposedY = (lowerBound + clampedUpperBound) / 2
|
||||
}
|
||||
|
||||
proposedY = max(lowerBound, min(proposedY, clampedUpperBound))
|
||||
|
||||
return NSRect(x: originX, y: proposedY, width: size.width, height: size.height)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -101,6 +101,7 @@ final class LockScreenTimerWidgetPanelManager {
|
|||
private var hasDelegated = false
|
||||
private let animator = LockScreenTimerWidgetAnimator()
|
||||
private var hideTask: Task<Void, Never>?
|
||||
private(set) var latestFrame: NSRect?
|
||||
|
||||
private init() {}
|
||||
|
||||
|
|
@ -109,11 +110,13 @@ final class LockScreenTimerWidgetPanelManager {
|
|||
let window = ensureWindow()
|
||||
let frame = targetFrame(on: screen)
|
||||
window.setFrame(frame, display: true)
|
||||
latestFrame = frame
|
||||
window.alphaValue = 1
|
||||
window.orderFrontRegardless()
|
||||
hideTask?.cancel()
|
||||
hideTask = nil
|
||||
animator.isPresented = true
|
||||
LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true)
|
||||
}
|
||||
|
||||
func hide(animated: Bool = true) {
|
||||
|
|
@ -125,6 +128,8 @@ final class LockScreenTimerWidgetPanelManager {
|
|||
if delay == 0 {
|
||||
window.orderOut(nil)
|
||||
hideTask = nil
|
||||
latestFrame = nil
|
||||
LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -133,6 +138,8 @@ final class LockScreenTimerWidgetPanelManager {
|
|||
await MainActor.run {
|
||||
window?.orderOut(nil)
|
||||
self?.hideTask = nil
|
||||
self?.latestFrame = nil
|
||||
LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -150,6 +157,8 @@ final class LockScreenTimerWidgetPanelManager {
|
|||
} else {
|
||||
window.setFrame(frame, display: true)
|
||||
}
|
||||
latestFrame = frame
|
||||
LockScreenReminderWidgetPanelManager.shared.refreshPosition(animated: animated)
|
||||
}
|
||||
|
||||
func refreshRelativeToMusicPanel(animated: Bool) {
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ final class SystemVolumeController {
|
|||
private var listenersInstalled = false
|
||||
private var volumeElement: AudioObjectPropertyElement?
|
||||
private var muteElement: AudioObjectPropertyElement?
|
||||
private let silenceThreshold: Float = 0.001 // Treat very low values as mute requests.
|
||||
|
||||
private let candidateElements: [AudioObjectPropertyElement] = [
|
||||
kAudioObjectPropertyElementMain,
|
||||
|
|
@ -101,7 +102,13 @@ final class SystemVolumeController {
|
|||
|
||||
func setVolume(_ value: Float) {
|
||||
let clamped = max(0, min(1, value))
|
||||
if isMuted && clamped > 0 {
|
||||
let currentlyMuted = isMuted
|
||||
|
||||
if clamped <= silenceThreshold {
|
||||
if !currentlyMuted {
|
||||
setMuted(true)
|
||||
}
|
||||
} else if currentlyMuted {
|
||||
setMuted(false)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -152,6 +152,8 @@ Atoll builds upon the work of several open-source projects and draws inspiration
|
|||
|
||||
- [**Stats**](https://github.com/exelban/stats) - source implementation for CPU temperature monitoring via SMC (System Management Controller) access, frequency sampling through IOReport bindings, and per-core CPU utilisation tracking. The system metrics collection architecture derives from Stats project readers.
|
||||
|
||||
- [**Open Meteo**](https://open-meteo.com) - weather apis for the lock screen widgets
|
||||
|
||||
## Contributors
|
||||
|
||||
<a href="https://github.com/Ebullioscopic/Atoll/graphs/contributors">
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue