mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
just 2 small features
This commit is contained in:
parent
bbba30551d
commit
53f8b297c9
9 changed files with 139 additions and 4 deletions
|
|
@ -28,6 +28,7 @@ struct ContentView: View {
|
|||
@ObservedObject var privacyManager = PrivacyIndicatorManager.shared
|
||||
@ObservedObject var doNotDisturbManager = DoNotDisturbManager.shared
|
||||
@ObservedObject var lockScreenManager = LockScreenManager.shared
|
||||
@ObservedObject var capsLockManager = CapsLockManager.shared
|
||||
@State private var downloadManager = DownloadManager.shared
|
||||
|
||||
@Default(.enableStatsFeature) var enableStatsFeature
|
||||
|
|
@ -485,6 +486,9 @@ struct ContentView: View {
|
|||
} else if coordinator.sneakPeek.show && Defaults[.inlineHUD] && (coordinator.sneakPeek.type != .music) && (coordinator.sneakPeek.type != .battery) && (coordinator.sneakPeek.type != .timer) && (coordinator.sneakPeek.type != .reminder) && (coordinator.sneakPeek.type != .volume || vm.notchState == .closed) {
|
||||
InlineHUD(type: $coordinator.sneakPeek.type, value: $coordinator.sneakPeek.value, icon: $coordinator.sneakPeek.icon, hoverAnimation: $isHovering, gestureProgress: $gestureProgress)
|
||||
.transition(.opacity)
|
||||
} else if vm.notchState == .closed && capsLockManager.isCapsLockActive && Defaults[.enableCapsLockIndicator] && !vm.hideOnClosed && !lockScreenManager.isLocked {
|
||||
InlineHUD(type: .constant(.capsLock), value: .constant(1.0), icon: .constant(""), hoverAnimation: $isHovering, gestureProgress: $gestureProgress)
|
||||
.transition(.opacity)
|
||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .music) && vm.notchState == .closed && (musicManager.isPlaying || !musicManager.isPlayerIdle) && coordinator.musicLiveActivityEnabled && !vm.hideOnClosed && !lockScreenManager.isLocked {
|
||||
MusicLiveActivity()
|
||||
} else if (!coordinator.expandingView.show || coordinator.expandingView.type == .timer) && vm.notchState == .closed && timerManager.isTimerActive && coordinator.timerLiveActivityEnabled && !vm.hideOnClosed {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ enum SneakContentType {
|
|||
case bluetoothAudio
|
||||
case privacy
|
||||
case lockScreen
|
||||
case capsLock
|
||||
}
|
||||
|
||||
struct sneakPeek {
|
||||
|
|
@ -190,7 +191,7 @@ class DynamicIslandViewCoordinator: ObservableObject {
|
|||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
withAnimation(.smooth) {
|
||||
withAnimation(.smooth(duration: 0.3)) {
|
||||
self.sneakPeek.show = status
|
||||
self.sneakPeek.type = type
|
||||
self.sneakPeek.value = value
|
||||
|
|
@ -205,6 +206,9 @@ class DynamicIslandViewCoordinator: ObservableObject {
|
|||
// Helper function to manage sneakPeek timer using Swift Concurrency
|
||||
private func scheduleSneakPeekHide(after duration: TimeInterval) {
|
||||
sneakPeekTask?.cancel()
|
||||
|
||||
// Don't schedule auto-hide if duration is infinite (for persistent indicators like Caps Lock)
|
||||
guard duration.isFinite else { return }
|
||||
|
||||
sneakPeekTask = Task { [weak self] in
|
||||
try? await Task.sleep(for: .seconds(duration))
|
||||
|
|
|
|||
|
|
@ -4622,6 +4622,9 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Caps Lock" : {
|
||||
|
||||
},
|
||||
"Center" : {
|
||||
"extractionState" : "stale",
|
||||
|
|
|
|||
|
|
@ -128,6 +128,12 @@ struct InlineHUD: View {
|
|||
.symbolRenderingMode(.hierarchical)
|
||||
.contentTransition(.interpolate)
|
||||
.frame(width: 20, height: 15, alignment: .center)
|
||||
case .capsLock:
|
||||
Image(systemName: "capslock.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.contentTransition(.interpolate)
|
||||
.frame(width: 20, height: 15, alignment: .center)
|
||||
.foregroundStyle(Defaults[.capsLockIndicatorUseGreenColor] ? .green : .white)
|
||||
default:
|
||||
EmptyView()
|
||||
}
|
||||
|
|
@ -147,7 +153,7 @@ struct InlineHUD: View {
|
|||
frameWidth: infoWidth
|
||||
)
|
||||
}
|
||||
} else {
|
||||
} else if type != .capsLock {
|
||||
Text(Type2Name(type))
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
|
|
@ -180,6 +186,16 @@ struct InlineHUD: View {
|
|||
.multilineTextAlignment(.trailing)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.contentTransition(.interpolate)
|
||||
} else if (type == .capsLock) {
|
||||
Text("Caps Lock")
|
||||
.foregroundStyle(Defaults[.capsLockIndicatorUseGreenColor] ? .green : .white)
|
||||
.font(.subheadline)
|
||||
.fontWeight(.medium)
|
||||
.lineLimit(1)
|
||||
.allowsTightening(true)
|
||||
.multilineTextAlignment(.trailing)
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.contentTransition(.interpolate)
|
||||
} else if (type == .bluetoothAudio) {
|
||||
if hasBatteryLevel {
|
||||
let indicatorSpacing: CGFloat = {
|
||||
|
|
@ -374,6 +390,8 @@ struct InlineHUD: View {
|
|||
return "Mic"
|
||||
case .bluetoothAudio:
|
||||
return BluetoothAudioManager.shared.lastConnectedDevice?.name ?? "Bluetooth"
|
||||
case .capsLock:
|
||||
return "Caps Lock"
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ struct LockScreenLiveActivityOverlay: View {
|
|||
Self.collapsedScale(for: notchSize)
|
||||
}
|
||||
|
||||
@State private var isHovering: Bool = false
|
||||
|
||||
var body: some View {
|
||||
HStack(spacing: 0) {
|
||||
Color.clear
|
||||
|
|
@ -52,8 +54,18 @@ struct LockScreenLiveActivityOverlay: View {
|
|||
)
|
||||
)
|
||||
.frame(width: totalWidth, height: notchSize.height)
|
||||
.scaleEffect(x: max(model.scale, collapsedScale), y: 1, anchor: .center)
|
||||
.scaleEffect(x: max(model.scale, collapsedScale) * (isHovering ? 1.03 : 1.0),
|
||||
y: 1 * (isHovering ? 1.03 : 1.0),
|
||||
anchor: .center)
|
||||
.opacity(model.opacity)
|
||||
.onHover { hovering in
|
||||
withAnimation(.smooth(duration: 0.2)) {
|
||||
isHovering = hovering
|
||||
}
|
||||
if hovering {
|
||||
NSHapticFeedbackManager.defaultPerformer.perform(.alignment, performanceTime: .default)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1735,6 +1735,11 @@ private struct DevicesSettingsView: View {
|
|||
Section {
|
||||
Defaults.Toggle("Show Bluetooth device connections", key: .showBluetoothDeviceConnections)
|
||||
.settingsHighlight(id: highlightID("Show Bluetooth device connections"))
|
||||
Defaults.Toggle("Show Caps Lock Indicator", key: .enableCapsLockIndicator)
|
||||
.settingsHighlight(id: highlightID("Show Caps Lock Indicator"))
|
||||
Defaults.Toggle("Use green color for Caps Lock text", key: .capsLockIndicatorUseGreenColor)
|
||||
.disabled(!Defaults[.enableCapsLockIndicator])
|
||||
.settingsHighlight(id: highlightID("Use green color for Caps Lock text"))
|
||||
Defaults.Toggle("Use circular battery indicator", key: .useCircularBluetoothBatteryIndicator)
|
||||
.settingsHighlight(id: highlightID("Use circular battery indicator"))
|
||||
Defaults.Toggle("Show battery percentage text in HUD", key: .showBluetoothBatteryPercentageText)
|
||||
|
|
|
|||
85
DynamicIsland/managers/CapsLockManager.swift
Normal file
85
DynamicIsland/managers/CapsLockManager.swift
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// CapsLockManager.swift
|
||||
// DynamicIsland
|
||||
//
|
||||
// Created for Caps Lock indicator feature
|
||||
// Monitors Caps Lock state and integrates with Dynamic Island
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Combine
|
||||
import AppKit
|
||||
import Defaults
|
||||
|
||||
@MainActor
|
||||
class CapsLockManager: ObservableObject {
|
||||
static let shared = CapsLockManager()
|
||||
|
||||
@Published var isCapsLockActive: Bool = false
|
||||
|
||||
private var localEventMonitor: Any?
|
||||
private var globalEventMonitor: Any?
|
||||
private let coordinator = DynamicIslandViewCoordinator.shared
|
||||
|
||||
private init() {
|
||||
// Get initial state
|
||||
isCapsLockActive = NSEvent.modifierFlags.contains(.capsLock)
|
||||
|
||||
// Monitor flag changes when app is focused
|
||||
localEventMonitor = NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { [weak self] event in
|
||||
self?.handleFlagsChanged(event)
|
||||
return event
|
||||
}
|
||||
|
||||
// Monitor flag changes globally (even when app is not focused)
|
||||
globalEventMonitor = NSEvent.addGlobalMonitorForEvents(matching: .flagsChanged) { [weak self] event in
|
||||
Task { @MainActor in
|
||||
self?.handleFlagsChanged(event)
|
||||
}
|
||||
}
|
||||
|
||||
print("CapsLockManager: ✅ Initialized with Caps Lock \(isCapsLockActive ? "ON" : "OFF")")
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let monitor = localEventMonitor {
|
||||
NSEvent.removeMonitor(monitor)
|
||||
}
|
||||
if let monitor = globalEventMonitor {
|
||||
NSEvent.removeMonitor(monitor)
|
||||
}
|
||||
}
|
||||
|
||||
private func handleFlagsChanged(_ event: NSEvent) {
|
||||
let newState = event.modifierFlags.contains(.capsLock)
|
||||
|
||||
guard newState != isCapsLockActive else { return }
|
||||
|
||||
isCapsLockActive = newState
|
||||
|
||||
print("CapsLockManager: Caps Lock \(newState ? "ACTIVATED" : "DEACTIVATED")")
|
||||
|
||||
// Only show/hide if feature is enabled
|
||||
guard Defaults[.enableCapsLockIndicator] else { return }
|
||||
|
||||
if newState {
|
||||
// Show inline indicator
|
||||
coordinator.toggleSneakPeek(
|
||||
status: true,
|
||||
type: .capsLock,
|
||||
duration: .infinity, // Stay visible until deactivated
|
||||
value: 1.0,
|
||||
icon: ""
|
||||
)
|
||||
} else {
|
||||
// Hide indicator
|
||||
coordinator.toggleSneakPeek(
|
||||
status: false,
|
||||
type: .capsLock,
|
||||
duration: 0,
|
||||
value: 0,
|
||||
icon: ""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ class LockScreenLiveActivityWindowManager {
|
|||
window.isOpaque = false
|
||||
window.backgroundColor = .clear
|
||||
window.isReleasedWhenClosed = false
|
||||
window.ignoresMouseEvents = true
|
||||
window.ignoresMouseEvents = false
|
||||
window.hasShadow = false
|
||||
window.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()))
|
||||
window.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary]
|
||||
|
|
|
|||
|
|
@ -738,6 +738,10 @@ extension Defaults.Keys {
|
|||
static let enableLockScreenLiveActivity = Key<Bool>("enableLockScreenLiveActivity", default: true)
|
||||
static let enableLockSounds = Key<Bool>("enableLockSounds", default: true)
|
||||
|
||||
// MARK: Caps Lock Indicator
|
||||
static let enableCapsLockIndicator = Key<Bool>("enableCapsLockIndicator", default: true)
|
||||
static let capsLockIndicatorUseGreenColor = Key<Bool>("capsLockIndicatorUseGreenColor", default: false)
|
||||
|
||||
// MARK: ImageService
|
||||
static let didClearLegacyURLCacheV1 = Key<Bool>("didClearLegacyURLCacheV1", default: false)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue