mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-29 08:04:32 +00:00
Feature: Media Panel Disable in standard mode
This commit is contained in:
parent
465d6e089a
commit
f425b38a81
8 changed files with 240 additions and 6 deletions
|
|
@ -66,6 +66,7 @@ struct ContentView: View {
|
||||||
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
|
@Default(.enableScreenRecordingDetection) var enableScreenRecordingDetection
|
||||||
@Default(.enableCapsLockIndicator) var enableCapsLockIndicator
|
@Default(.enableCapsLockIndicator) var enableCapsLockIndicator
|
||||||
@Default(.enableExtensionLiveActivities) var enableExtensionLiveActivities
|
@Default(.enableExtensionLiveActivities) var enableExtensionLiveActivities
|
||||||
|
@Default(.showStandardMediaControls) var showStandardMediaControls
|
||||||
|
|
||||||
// Dynamic sizing based on view type and graph count with smooth transitions
|
// Dynamic sizing based on view type and graph count with smooth transitions
|
||||||
var dynamicNotchSize: CGSize {
|
var dynamicNotchSize: CGSize {
|
||||||
|
|
@ -171,6 +172,14 @@ struct ContentView: View {
|
||||||
private let statsAdditionalRowHeight: CGFloat = 110
|
private let statsAdditionalRowHeight: CGFloat = 110
|
||||||
private let musicControlPauseGrace: TimeInterval = 5
|
private let musicControlPauseGrace: TimeInterval = 5
|
||||||
private let musicControlResumeDelay: TimeInterval = 0.24
|
private let musicControlResumeDelay: TimeInterval = 0.24
|
||||||
|
|
||||||
|
private var standardMediaControlsActive: Bool {
|
||||||
|
showStandardMediaControls && !enableMinimalisticUI
|
||||||
|
}
|
||||||
|
|
||||||
|
private var closedMusicContentEnabled: Bool {
|
||||||
|
enableMinimalisticUI || showStandardMediaControls
|
||||||
|
}
|
||||||
|
|
||||||
// Use minimalistic corner radius ONLY when opened, keep normal when closed
|
// Use minimalistic corner radius ONLY when opened, keep normal when closed
|
||||||
private var activeCornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) {
|
private var activeCornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) {
|
||||||
|
|
@ -449,6 +458,12 @@ struct ContentView: View {
|
||||||
enqueueMusicControlWindowSync(forceRefresh: true, delay: 0.05)
|
enqueueMusicControlWindowSync(forceRefresh: true, delay: 0.05)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onChange(of: showStandardMediaControls) { _, _ in
|
||||||
|
handleStandardMediaControlsAvailabilityChange()
|
||||||
|
}
|
||||||
|
.onChange(of: enableMinimalisticUI) { _, _ in
|
||||||
|
handleStandardMediaControlsAvailabilityChange()
|
||||||
|
}
|
||||||
.onChange(of: gestureProgress) { _, _ in
|
.onChange(of: gestureProgress) { _, _ in
|
||||||
if shouldShowMusicControlWindow() {
|
if shouldShowMusicControlWindow() {
|
||||||
enqueueMusicControlWindowSync(forceRefresh: true, delay: 0.05)
|
enqueueMusicControlWindowSync(forceRefresh: true, delay: 0.05)
|
||||||
|
|
@ -506,6 +521,7 @@ struct ContentView: View {
|
||||||
let musicPairingEligible = vm.notchState == .closed
|
let musicPairingEligible = vm.notchState == .closed
|
||||||
&& hasActiveMusicSnapshot
|
&& hasActiveMusicSnapshot
|
||||||
&& coordinator.musicLiveActivityEnabled
|
&& coordinator.musicLiveActivityEnabled
|
||||||
|
&& closedMusicContentEnabled
|
||||||
&& !vm.hideOnClosed
|
&& !vm.hideOnClosed
|
||||||
&& !lockScreenManager.isLocked
|
&& !lockScreenManager.isLocked
|
||||||
let musicSecondary = resolveMusicSecondaryLiveActivity(isMusicPairingEligible: musicPairingEligible)
|
let musicSecondary = resolveMusicSecondaryLiveActivity(isMusicPairingEligible: musicPairingEligible)
|
||||||
|
|
@ -1181,6 +1197,15 @@ struct ContentView: View {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard standardMediaControlsActive else {
|
||||||
|
ExtensionRoutingDiagnostics.shared.logSuppression(
|
||||||
|
.music,
|
||||||
|
reason: "standard media controls disabled",
|
||||||
|
pendingCount: candidates.count
|
||||||
|
)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
guard vm.notchState == .closed else {
|
guard vm.notchState == .closed else {
|
||||||
ExtensionRoutingDiagnostics.shared.logSuppression(
|
ExtensionRoutingDiagnostics.shared.logSuppression(
|
||||||
.music,
|
.music,
|
||||||
|
|
@ -1706,6 +1731,26 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handleStandardMediaControlsAvailabilityChange() {
|
||||||
|
guard musicControlWindowEnabled else {
|
||||||
|
hideMusicControlWindow()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if standardMediaControlsActive {
|
||||||
|
if musicManager.isPlaying || !musicManager.isPlayerIdle {
|
||||||
|
clearMusicControlVisibilityDeadline()
|
||||||
|
}
|
||||||
|
enqueueMusicControlWindowSync(forceRefresh: true)
|
||||||
|
} else {
|
||||||
|
cancelMusicControlWindowSync()
|
||||||
|
hideMusicControlWindow()
|
||||||
|
clearMusicControlVisibilityDeadline()
|
||||||
|
hasPendingMusicControlSync = false
|
||||||
|
pendingMusicControlForceRefresh = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func extendMusicControlVisibilityAfterPause() {
|
private func extendMusicControlVisibilityAfterPause() {
|
||||||
let deadline = Date().addingTimeInterval(musicControlPauseGrace)
|
let deadline = Date().addingTimeInterval(musicControlPauseGrace)
|
||||||
musicControlVisibilityDeadline = deadline
|
musicControlVisibilityDeadline = deadline
|
||||||
|
|
@ -1817,6 +1862,7 @@ struct ContentView: View {
|
||||||
private func shouldShowMusicControlWindow() -> Bool {
|
private func shouldShowMusicControlWindow() -> Bool {
|
||||||
guard musicControlWindowEnabled,
|
guard musicControlWindowEnabled,
|
||||||
coordinator.musicLiveActivityEnabled,
|
coordinator.musicLiveActivityEnabled,
|
||||||
|
standardMediaControlsActive,
|
||||||
vm.notchState == .closed,
|
vm.notchState == .closed,
|
||||||
!vm.hideOnClosed,
|
!vm.hideOnClosed,
|
||||||
!lockScreenManager.isLocked,
|
!lockScreenManager.isLocked,
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
let idleAnimationManager = IdleAnimationManager.shared // NEW: Custom idle animations
|
let idleAnimationManager = IdleAnimationManager.shared // NEW: Custom idle animations
|
||||||
let downloadManager = DownloadManager.shared // NEW: Chromium downloads detection
|
let downloadManager = DownloadManager.shared // NEW: Chromium downloads detection
|
||||||
let lockScreenPanelManager = LockScreenPanelManager.shared // NEW: Lock screen music panel
|
let lockScreenPanelManager = LockScreenPanelManager.shared // NEW: Lock screen music panel
|
||||||
|
let mediaControlsStateCoordinator = MediaControlsStateCoordinator.shared
|
||||||
let systemTimerBridge = SystemTimerBridge.shared
|
let systemTimerBridge = SystemTimerBridge.shared
|
||||||
let extensionXPCServiceHost = ExtensionXPCServiceHost.shared
|
let extensionXPCServiceHost = ExtensionXPCServiceHost.shared
|
||||||
var closeNotchWorkItem: DispatchWorkItem?
|
var closeNotchWorkItem: DispatchWorkItem?
|
||||||
|
|
@ -889,3 +890,98 @@ extension CGRect: @retroactive Hashable {
|
||||||
return lhs.origin == rhs.origin && lhs.size == rhs.size
|
return lhs.origin == rhs.origin && lhs.size == rhs.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MainActor
|
||||||
|
final class MediaControlsStateCoordinator {
|
||||||
|
static let shared = MediaControlsStateCoordinator()
|
||||||
|
|
||||||
|
private var cancellables = Set<AnyCancellable>()
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
let masterPublisher = Defaults.publisher(.showStandardMediaControls)
|
||||||
|
let minimalisticPublisher = Defaults.publisher(.enableMinimalisticUI)
|
||||||
|
|
||||||
|
Publishers.CombineLatest(masterPublisher, minimalisticPublisher)
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.sink { [weak self] masterChange, minimalisticChange in
|
||||||
|
self?.handleStateChange(
|
||||||
|
showStandard: masterChange.newValue,
|
||||||
|
minimalistic: minimalisticChange.newValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func handleStateChange(showStandard: Bool, minimalistic: Bool) {
|
||||||
|
if !showStandard && !minimalistic {
|
||||||
|
cacheAndDisableMusicLiveActivity()
|
||||||
|
} else {
|
||||||
|
restoreMusicLiveActivity(clearCache: showStandard)
|
||||||
|
}
|
||||||
|
|
||||||
|
if showStandard {
|
||||||
|
restoreLockScreenPanelIfNeeded()
|
||||||
|
restoreMusicControlWindowIfNeeded()
|
||||||
|
} else {
|
||||||
|
cacheAndDisableLockScreenPanel()
|
||||||
|
cacheAndDisableMusicControlWindow()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cacheAndDisableMusicLiveActivity() {
|
||||||
|
if Defaults[.cachedMusicLiveActivityPreference] == nil {
|
||||||
|
Defaults[.cachedMusicLiveActivityPreference] = DynamicIslandViewCoordinator.shared.musicLiveActivityEnabled
|
||||||
|
}
|
||||||
|
|
||||||
|
if DynamicIslandViewCoordinator.shared.musicLiveActivityEnabled {
|
||||||
|
DynamicIslandViewCoordinator.shared.musicLiveActivityEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func restoreMusicLiveActivity(clearCache: Bool) {
|
||||||
|
guard let cached = Defaults[.cachedMusicLiveActivityPreference] else { return }
|
||||||
|
|
||||||
|
if DynamicIslandViewCoordinator.shared.musicLiveActivityEnabled != cached {
|
||||||
|
DynamicIslandViewCoordinator.shared.musicLiveActivityEnabled = cached
|
||||||
|
}
|
||||||
|
|
||||||
|
if clearCache {
|
||||||
|
Defaults[.cachedMusicLiveActivityPreference] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cacheAndDisableLockScreenPanel() {
|
||||||
|
if Defaults[.cachedLockScreenMediaWidgetPreference] == nil {
|
||||||
|
Defaults[.cachedLockScreenMediaWidgetPreference] = Defaults[.enableLockScreenMediaWidget]
|
||||||
|
}
|
||||||
|
|
||||||
|
if Defaults[.enableLockScreenMediaWidget] {
|
||||||
|
Defaults[.enableLockScreenMediaWidget] = false
|
||||||
|
LockScreenPanelManager.shared.hidePanel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func restoreLockScreenPanelIfNeeded() {
|
||||||
|
guard let cached = Defaults[.cachedLockScreenMediaWidgetPreference] else { return }
|
||||||
|
Defaults[.enableLockScreenMediaWidget] = cached
|
||||||
|
Defaults[.cachedLockScreenMediaWidgetPreference] = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cacheAndDisableMusicControlWindow() {
|
||||||
|
if Defaults[.cachedMusicControlWindowPreference] == nil {
|
||||||
|
Defaults[.cachedMusicControlWindowPreference] = Defaults[.musicControlWindowEnabled]
|
||||||
|
}
|
||||||
|
|
||||||
|
if Defaults[.musicControlWindowEnabled] {
|
||||||
|
Defaults[.musicControlWindowEnabled] = false
|
||||||
|
}
|
||||||
|
|
||||||
|
MusicControlWindowManager.shared.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func restoreMusicControlWindowIfNeeded() {
|
||||||
|
guard let cached = Defaults[.cachedMusicControlWindowPreference] else { return }
|
||||||
|
Defaults[.musicControlWindowEnabled] = cached
|
||||||
|
Defaults[.cachedMusicControlWindowPreference] = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -7070,6 +7070,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Disable Minimalistic UI to configure the standard notch media controls." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Disabled" : {
|
"Disabled" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -7604,6 +7607,9 @@
|
||||||
"Dynamic Island Progress Bars" : {
|
"Dynamic Island Progress Bars" : {
|
||||||
"comment" : "A section header that lists the options for customizing the appearance of the progress bars displayed by Dynamic Island.",
|
"comment" : "A section header that lists the options for customizing the appearance of the progress bars displayed by Dynamic Island.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
|
"Dynamic Island Visibility" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"dynamic.island" : {
|
"dynamic.island" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -7990,6 +7996,9 @@
|
||||||
"Enable customizable controls" : {
|
"Enable customizable controls" : {
|
||||||
"comment" : "A toggle that allows users to enable or disable customizable controls for media playback.",
|
"comment" : "A toggle that allows users to enable or disable customizable controls for media playback.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
|
"Enable Dynamic Island media controls to manage the lock screen panel." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Enable extensions to allow third-party apps to display live activities and lock screen widgets in Atoll." : {
|
"Enable extensions to allow third-party apps to display live activities and lock screen widgets in Atoll." : {
|
||||||
"comment" : "A footer text that explains the purpose of enabling extensions.",
|
"comment" : "A footer text that explains the purpose of enabling extensions.",
|
||||||
|
|
@ -18463,6 +18472,12 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Standard notch media controls are hidden while this toggle is off." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Standard notch media controls are hidden. Re-enable the toggle above to restore them." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Start" : {
|
"Start" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
|
||||||
|
|
@ -383,6 +383,7 @@ struct NotchHomeView: View {
|
||||||
@ObservedObject var batteryModel = BatteryStatusViewModel.shared
|
@ObservedObject var batteryModel = BatteryStatusViewModel.shared
|
||||||
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
@ObservedObject var coordinator = DynamicIslandViewCoordinator.shared
|
||||||
@ObservedObject private var extensionNotchExperienceManager = ExtensionNotchExperienceManager.shared
|
@ObservedObject private var extensionNotchExperienceManager = ExtensionNotchExperienceManager.shared
|
||||||
|
@Default(.showStandardMediaControls) private var showStandardMediaControls
|
||||||
let albumArtNamespace: Namespace.ID
|
let albumArtNamespace: Namespace.ID
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
@ -407,8 +408,10 @@ struct NotchHomeView: View {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Normal mode: Show full music player with optional calendar and webcam
|
// Normal mode: Show full music player with optional calendar and webcam
|
||||||
MusicPlayerView(albumArtNamespace: albumArtNamespace)
|
if showStandardMediaControls {
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
MusicPlayerView(albumArtNamespace: albumArtNamespace)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
|
||||||
if Defaults[.showCalendar] {
|
if Defaults[.showCalendar] {
|
||||||
CalendarView()
|
CalendarView()
|
||||||
|
|
|
||||||
|
|
@ -1964,11 +1964,16 @@ struct Media: View {
|
||||||
@Default(.lockScreenGlassStyle) private var lockScreenGlassStyle
|
@Default(.lockScreenGlassStyle) private var lockScreenGlassStyle
|
||||||
@Default(.lockScreenGlassCustomizationMode) private var lockScreenGlassCustomizationMode
|
@Default(.lockScreenGlassCustomizationMode) private var lockScreenGlassCustomizationMode
|
||||||
@Default(.lockScreenMusicAlbumParallaxEnabled) private var lockScreenMusicAlbumParallaxEnabled
|
@Default(.lockScreenMusicAlbumParallaxEnabled) private var lockScreenMusicAlbumParallaxEnabled
|
||||||
|
@Default(.showStandardMediaControls) private var showStandardMediaControls
|
||||||
|
|
||||||
private func highlightID(_ title: String) -> String {
|
private func highlightID(_ title: String) -> String {
|
||||||
SettingsTab.media.highlightID(for: title)
|
SettingsTab.media.highlightID(for: title)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var standardControlsSuppressed: Bool {
|
||||||
|
!showStandardMediaControls && !enableMinimalisticUI
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Form {
|
Form {
|
||||||
Section {
|
Section {
|
||||||
|
|
@ -2002,6 +2007,23 @@ struct Media: View {
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Section {
|
||||||
|
Defaults.Toggle("Show media controls in Dynamic Island", key: .showStandardMediaControls)
|
||||||
|
.disabled(enableMinimalisticUI)
|
||||||
|
.settingsHighlight(id: highlightID("Show media controls in Dynamic Island"))
|
||||||
|
|
||||||
|
if enableMinimalisticUI {
|
||||||
|
Text("Disable Minimalistic UI to configure the standard notch media controls.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
} else if standardControlsSuppressed {
|
||||||
|
Text("Standard notch media controls are hidden. Re-enable the toggle above to restore them.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
}
|
||||||
|
} header: {
|
||||||
|
Text("Dynamic Island Visibility")
|
||||||
|
}
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle(key: .showShuffleAndRepeat) {
|
Defaults.Toggle(key: .showShuffleAndRepeat) {
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -2053,11 +2075,13 @@ struct Media: View {
|
||||||
"Enable music live activity",
|
"Enable music live activity",
|
||||||
isOn: $coordinator.musicLiveActivityEnabled.animation()
|
isOn: $coordinator.musicLiveActivityEnabled.animation()
|
||||||
)
|
)
|
||||||
|
.disabled(standardControlsSuppressed)
|
||||||
|
.help(standardControlsSuppressed ? "Standard notch media controls are hidden while this toggle is off." : "")
|
||||||
Defaults.Toggle(
|
Defaults.Toggle(
|
||||||
"Show floating media controls",
|
"Show floating media controls",
|
||||||
key: .musicControlWindowEnabled
|
key: .musicControlWindowEnabled
|
||||||
)
|
)
|
||||||
.disabled(!coordinator.musicLiveActivityEnabled)
|
.disabled(!coordinator.musicLiveActivityEnabled || standardControlsSuppressed)
|
||||||
.help("Displays play/pause and skip buttons beside the notch while music is active. Disabled by default.")
|
.help("Displays play/pause and skip buttons beside the notch while music is active. Disabled by default.")
|
||||||
Toggle("Enable sneak peek", isOn: $enableSneakPeek)
|
Toggle("Enable sneak peek", isOn: $enableSneakPeek)
|
||||||
Toggle("Show sneak peek on playback changes", isOn: $showSneakPeekOnTrackChange)
|
Toggle("Show sneak peek on playback changes", isOn: $showSneakPeekOnTrackChange)
|
||||||
|
|
@ -2124,6 +2148,8 @@ struct Media: View {
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("These controls mirror the Lock Screen tab so you can tune the media overlay while focusing on playback settings.")
|
Text("These controls mirror the Lock Screen tab so you can tune the media overlay while focusing on playback settings.")
|
||||||
}
|
}
|
||||||
|
.disabled(!showStandardMediaControls)
|
||||||
|
.opacity(showStandardMediaControls ? 1 : 0.5)
|
||||||
|
|
||||||
Picker(selection: $hideNotchOption, label:
|
Picker(selection: $hideNotchOption, label:
|
||||||
HStack {
|
HStack {
|
||||||
|
|
@ -3301,6 +3327,7 @@ struct LockScreenSettings: View {
|
||||||
@Default(.lockScreenWeatherShowsAQI) private var lockScreenWeatherShowsAQI
|
@Default(.lockScreenWeatherShowsAQI) private var lockScreenWeatherShowsAQI
|
||||||
@Default(.lockScreenWeatherShowsSunrise) private var lockScreenWeatherShowsSunrise
|
@Default(.lockScreenWeatherShowsSunrise) private var lockScreenWeatherShowsSunrise
|
||||||
@Default(.lockScreenWeatherAQIScale) private var lockScreenWeatherAQIScale
|
@Default(.lockScreenWeatherAQIScale) private var lockScreenWeatherAQIScale
|
||||||
|
@Default(.showStandardMediaControls) private var showStandardMediaControls
|
||||||
|
|
||||||
private func highlightID(_ title: String) -> String {
|
private func highlightID(_ title: String) -> String {
|
||||||
SettingsTab.lockScreen.highlightID(for: title)
|
SettingsTab.lockScreen.highlightID(for: title)
|
||||||
|
|
@ -3422,11 +3449,20 @@ struct LockScreenSettings: View {
|
||||||
.opacity(enableLockScreenMediaWidget ? 1 : 0.5)
|
.opacity(enableLockScreenMediaWidget ? 1 : 0.5)
|
||||||
.settingsHighlight(id: highlightID("Enable media panel blur"))
|
.settingsHighlight(id: highlightID("Enable media panel blur"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !showStandardMediaControls {
|
||||||
|
Text("Enable Dynamic Island media controls to manage the lock screen panel.")
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.secondary)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
} header: {
|
} header: {
|
||||||
Text("Media Panel")
|
Text("Media Panel")
|
||||||
} footer: {
|
} footer: {
|
||||||
Text("Enable and style the media controls that appear above the system clock when the screen is locked.")
|
Text("Enable and style the media controls that appear above the system clock when the screen is locked.")
|
||||||
}
|
}
|
||||||
|
.disabled(!showStandardMediaControls)
|
||||||
|
.opacity(showStandardMediaControls ? 1 : 0.5)
|
||||||
|
|
||||||
Section {
|
Section {
|
||||||
Defaults.Toggle("Show lock screen timer", key: .enableLockScreenTimerWidget)
|
Defaults.Toggle("Show lock screen timer", key: .enableLockScreenTimerWidget)
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,18 @@ struct TabSelectionView: View {
|
||||||
@Default(.enableThirdPartyExtensions) private var enableThirdPartyExtensions
|
@Default(.enableThirdPartyExtensions) private var enableThirdPartyExtensions
|
||||||
@Default(.enableExtensionNotchExperiences) private var enableExtensionNotchExperiences
|
@Default(.enableExtensionNotchExperiences) private var enableExtensionNotchExperiences
|
||||||
@Default(.enableExtensionNotchTabs) private var enableExtensionNotchTabs
|
@Default(.enableExtensionNotchTabs) private var enableExtensionNotchTabs
|
||||||
|
@Default(.showCalendar) private var showCalendar
|
||||||
|
@Default(.showMirror) private var showMirror
|
||||||
|
@Default(.showStandardMediaControls) private var showStandardMediaControls
|
||||||
|
@Default(.enableMinimalisticUI) private var enableMinimalisticUI
|
||||||
@Namespace var animation
|
@Namespace var animation
|
||||||
|
|
||||||
private var tabs: [TabModel] {
|
private var tabs: [TabModel] {
|
||||||
var tabsArray: [TabModel] = []
|
var tabsArray: [TabModel] = []
|
||||||
|
|
||||||
tabsArray.append(TabModel(label: "Home", icon: "house.fill", view: .home))
|
if homeTabVisible {
|
||||||
|
tabsArray.append(TabModel(label: "Home", icon: "house.fill", view: .home))
|
||||||
|
}
|
||||||
|
|
||||||
if Defaults[.dynamicShelf] {
|
if Defaults[.dynamicShelf] {
|
||||||
tabsArray.append(TabModel(label: "Shelf", icon: "tray.fill", view: .shelf))
|
tabsArray.append(TabModel(label: "Shelf", icon: "tray.fill", view: .shelf))
|
||||||
|
|
@ -77,7 +83,9 @@ struct TabSelectionView: View {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DispatchQueue.main.async {
|
||||||
|
ensureValidSelection(with: tabsArray)
|
||||||
|
}
|
||||||
return tabsArray
|
return tabsArray
|
||||||
}
|
}
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
|
@ -121,6 +129,13 @@ struct TabSelectionView: View {
|
||||||
extensionNotchExperienceManager.activeExperiences.filter { $0.descriptor.tab != nil }
|
extensionNotchExperienceManager.activeExperiences.filter { $0.descriptor.tab != nil }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var homeTabVisible: Bool {
|
||||||
|
if enableMinimalisticUI {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return showStandardMediaControls || showCalendar || showMirror
|
||||||
|
}
|
||||||
|
|
||||||
private func isSelected(_ tab: TabModel) -> Bool {
|
private func isSelected(_ tab: TabModel) -> Bool {
|
||||||
if tab.view == .extensionExperience {
|
if tab.view == .extensionExperience {
|
||||||
return coordinator.currentView == .extensionExperience
|
return coordinator.currentView == .extensionExperience
|
||||||
|
|
@ -128,6 +143,20 @@ struct TabSelectionView: View {
|
||||||
}
|
}
|
||||||
return coordinator.currentView == tab.view
|
return coordinator.currentView == tab.view
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func ensureValidSelection(with tabs: [TabModel]) {
|
||||||
|
guard !tabs.isEmpty else { return }
|
||||||
|
if tabs.contains(where: { isSelected($0) }) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let first = tabs.first else { return }
|
||||||
|
if first.view == .extensionExperience {
|
||||||
|
coordinator.selectedExtensionExperienceID = first.experienceID
|
||||||
|
} else {
|
||||||
|
coordinator.selectedExtensionExperienceID = nil
|
||||||
|
}
|
||||||
|
coordinator.currentView = first.view
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#Preview {
|
||||||
|
|
|
||||||
|
|
@ -477,6 +477,11 @@ class MusicManager: ObservableObject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private func updateSneakPeek() {
|
private func updateSneakPeek() {
|
||||||
|
let standardControlsEnabled = Defaults[.showStandardMediaControls]
|
||||||
|
let minimalisticEnabled = Defaults[.enableMinimalisticUI]
|
||||||
|
|
||||||
|
guard standardControlsEnabled || minimalisticEnabled else { return }
|
||||||
|
|
||||||
if isPlaying && Defaults[.enableSneakPeek] {
|
if isPlaying && Defaults[.enableSneakPeek] {
|
||||||
if Defaults[.sneakPeekStyles] == .standard {
|
if Defaults[.sneakPeekStyles] == .standard {
|
||||||
coordinator.toggleSneakPeek(status: true, type: .music)
|
coordinator.toggleSneakPeek(status: true, type: .music)
|
||||||
|
|
|
||||||
|
|
@ -683,6 +683,10 @@ extension Defaults.Keys {
|
||||||
static let didMigrateMusicControlSlots = Key<Bool>("didMigrateMusicControlSlots", default: false)
|
static let didMigrateMusicControlSlots = Key<Bool>("didMigrateMusicControlSlots", default: false)
|
||||||
static let musicSkipBehavior = Key<MusicSkipBehavior>("musicSkipBehavior", default: .track)
|
static let musicSkipBehavior = Key<MusicSkipBehavior>("musicSkipBehavior", default: .track)
|
||||||
static let musicControlWindowEnabled = Key<Bool>("musicControlWindowEnabled", default: false)
|
static let musicControlWindowEnabled = Key<Bool>("musicControlWindowEnabled", default: false)
|
||||||
|
static let showStandardMediaControls = Key<Bool>("showStandardMediaControls", default: true)
|
||||||
|
static let cachedMusicLiveActivityPreference = Key<Bool?>("cachedMusicLiveActivityPreference", default: nil)
|
||||||
|
static let cachedLockScreenMediaWidgetPreference = Key<Bool?>("cachedLockScreenMediaWidgetPreference", default: nil)
|
||||||
|
static let cachedMusicControlWindowPreference = Key<Bool?>("cachedMusicControlWindowPreference", default: nil)
|
||||||
// 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 enableLockScreenWeatherWidget = Key<Bool>("enableLockScreenWeatherWidget", default: true)
|
static let enableLockScreenWeatherWidget = Key<Bool>("enableLockScreenWeatherWidget", default: true)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue