mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Merge pull request #131 from Ebullioscopic/feature/timer
Update Timer in Minimalistic UI
This commit is contained in:
commit
97ede14380
8 changed files with 1586 additions and 553 deletions
|
|
@ -203,6 +203,9 @@
|
||||||
knownRegions = (
|
knownRegions = (
|
||||||
en,
|
en,
|
||||||
Base,
|
Base,
|
||||||
|
hi,
|
||||||
|
ta,
|
||||||
|
gu,
|
||||||
);
|
);
|
||||||
mainGroup = F54406DE2CD4987A00C7AFD9;
|
mainGroup = F54406DE2CD4987A00C7AFD9;
|
||||||
minimizedProjectReferenceProxies = 1;
|
minimizedProjectReferenceProxies = 1;
|
||||||
|
|
|
||||||
|
|
@ -401,6 +401,14 @@ class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
TimerManager.shared.$activeSource
|
||||||
|
.combineLatest(TimerManager.shared.$isTimerActive)
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.debouncedUpdateWindowSize()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
Defaults.publisher(.enableShortcuts, options: []).sink { change in
|
Defaults.publisher(.enableShortcuts, options: []).sink { change in
|
||||||
KeyboardShortcuts.isEnabled = change.newValue
|
KeyboardShortcuts.isEnabled = change.newValue
|
||||||
}.store(in: &cancellables)
|
}.store(in: &cancellables)
|
||||||
|
|
|
||||||
|
|
@ -5228,6 +5228,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Clear search" : {
|
||||||
|
"comment" : "An accessibility label for the button that clears the search field in the sidebar.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Click 'Pick Color' or use Cmd+Shift+P to start picking colors" : {
|
"Click 'Pick Color' or use Cmd+Shift+P to start picking colors" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -5410,6 +5414,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Collect the latest crash report to share with the developer when reporting lock screen or overlay issues." : {
|
||||||
|
"comment" : "A footer for the \"Diagnostics\" section of the lock screen settings.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Color Details" : {
|
"Color Details" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -5741,6 +5749,14 @@
|
||||||
},
|
},
|
||||||
"Controls how often system metrics refresh while monitoring is active." : {
|
"Controls how often system metrics refresh while monitoring is active." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Controls the optional timer widget that floats above the media panel. Blur adds a frosted finish behind the compact view." : {
|
||||||
|
"comment" : "A footer for the \"Timer Widget\" section in the Lock Screen Settings view.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
|
"Controls whether Dynamic Island mirrors lock/unlock events with its own live activity and audible chimes." : {
|
||||||
|
"comment" : "A footer for the \"Live Activity & Feedback\" section in the Lock Screen Settings.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Copy event link" : {
|
"Copy event link" : {
|
||||||
"comment" : "A tooltip for a button that copies the link of a reminder event to the clipboard.",
|
"comment" : "A tooltip for a button that copies the link of a reminder event to the clipboard.",
|
||||||
|
|
@ -6675,6 +6691,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Diagnostics" : {
|
||||||
|
"comment" : "A section header in the Lock Screen Settings view, describing diagnostics-related functionality.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Disable" : {
|
"Disable" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ar" : {
|
"ar" : {
|
||||||
|
|
@ -7536,6 +7556,10 @@
|
||||||
"comment" : "A message displayed below the section header in the IdleAnimationsSettingsSection, explaining that users can customize animations by enabling this feature.",
|
"comment" : "A message displayed below the section header in the IdleAnimationsSettingsSection, explaining that users can customize animations by enabling this feature.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Enable and style the media controls that appear above the system clock when the screen is locked." : {
|
||||||
|
"comment" : "A description of the media panel settings in the lock screen settings view.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Enable Built-in System HUD" : {
|
"Enable Built-in System HUD" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -7990,6 +8014,10 @@
|
||||||
},
|
},
|
||||||
"Enable stats monitoring in Settings to view system performance data." : {
|
"Enable stats monitoring in Settings to view system performance data." : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Enable the weather capsule and configure its layout, provider, units, and optional battery/AQI indicators." : {
|
||||||
|
"comment" : "A footer for the \"Weather Widget\" section in the Lock Screen Settings view, describing its purpose.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Enable Thinking Mode" : {
|
"Enable Thinking Mode" : {
|
||||||
|
|
||||||
|
|
@ -10629,6 +10657,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Layout" : {
|
||||||
|
"comment" : "A label for the segment control that lets the user choose the layout of the weather widget.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"License key" : {
|
"License key" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -10666,6 +10698,10 @@
|
||||||
"comment" : "A label indicating that the music is live.",
|
"comment" : "A label indicating that the music is live.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Live Activity & Feedback" : {
|
||||||
|
"comment" : "The header for the \"Live Activity & Feedback\" section in the lock screen settings.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Live Monitoring" : {
|
"Live Monitoring" : {
|
||||||
"extractionState" : "stale",
|
"extractionState" : "stale",
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -10713,7 +10749,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"Lock screen material" : {
|
"Lock Screen Integration" : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Lock screen panel preview" : {
|
"Lock screen panel preview" : {
|
||||||
|
|
@ -11038,112 +11074,9 @@
|
||||||
"Mac battery at %d percent" : {
|
"Mac battery at %d percent" : {
|
||||||
"comment" : "Mac battery gauge accessibility label"
|
"comment" : "Mac battery gauge accessibility label"
|
||||||
},
|
},
|
||||||
"MacBook icon is available in the circular layout." : {
|
"Made with ❤️ by Ebullioscopic" : {
|
||||||
|
"comment" : "A footer text that credits the developer of the app.",
|
||||||
},
|
"isCommentAutoGenerated" : true
|
||||||
"Made ❤️ by Ebullioscopic" : {
|
|
||||||
|
|
||||||
},
|
|
||||||
"Made with 🫶🏻 by not so boring not.people" : {
|
|
||||||
"extractionState" : "stale",
|
|
||||||
"localizations" : {
|
|
||||||
"ar" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Made with 🫶🏻 by not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"cs" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Vyrobeno s 🫶🏻 ne tak nudnými ne lidmi."
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"de" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Gemacht mit 🫶🏻 durch nicht so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"en" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Made with 🫶🏻 by not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"en-GB" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "translated",
|
|
||||||
"value" : "Made with 🫶🏻 by not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"es" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Hecho con 🫶🏻 no tan aburrido no.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"fr" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Fait avec 🫶🏻 par des personnes pas si ennuyeuses - boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"hu" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Made with 🫶🏻 by not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"it" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Realizzato col 🫶🏻 da not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ko" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "지루하지 않은 비사람들이 🫶🏻 마음을 담아 만들었습니다"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pl" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Zrobione z 🫶🏻 przez niezbyt nudnych ludzi"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"pt-BR" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Made with 🫶🏻 by not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"ru" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Made with 🫶🏻 by not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"tr" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Made with 🫶🏻 by not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"uk" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "Made with 🫶🏻 by not so boring not.people"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"zh-Hans" : {
|
|
||||||
"stringUnit" : {
|
|
||||||
"state" : "needs_review",
|
|
||||||
"value" : "由 not.people 团队用心制作 🫶🏻"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"Manage indicator preferences from Settings → Privacy. No manual microphone toggle is required." : {
|
"Manage indicator preferences from Settings → Privacy. No manual microphone toggle is required." : {
|
||||||
"comment" : "A description of how to manage the microphone privacy indicator preferences.",
|
"comment" : "A description of how to manage the microphone privacy indicator preferences.",
|
||||||
|
|
@ -11352,6 +11285,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Material" : {
|
||||||
|
"comment" : "A picker that lets the user select the material of the lock screen glass.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Max Capacity: %lld%%" : {
|
"Max Capacity: %lld%%" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ar" : {
|
"ar" : {
|
||||||
|
|
@ -11858,6 +11795,10 @@
|
||||||
"comment" : "Accessibility label describing the control that opens the media output selector.",
|
"comment" : "Accessibility label describing the control that opens the media output selector.",
|
||||||
"isCommentAutoGenerated" : true
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
|
"Media Panel" : {
|
||||||
|
"comment" : "A section header in the lock screen settings view.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Media playback live activity" : {
|
"Media playback live activity" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"ar" : {
|
"ar" : {
|
||||||
|
|
@ -12738,6 +12679,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"Mirrors the toggle found under Lock Screen settings so timer-specific workflows can enable or disable the widget without switching tabs." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"Monitor clipboard changes and keep a history of recent copies. Use Cmd+Shift+V to quickly access clipboard history." : {
|
"Monitor clipboard changes and keep a history of recent copies. Use Cmd+Shift+V to quickly access clipboard history." : {
|
||||||
|
|
||||||
|
|
@ -15958,6 +15902,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Search Settings" : {
|
||||||
|
"comment" : "A placeholder text for a search bar in the settings view.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Search..." : {
|
"Search..." : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
"fr" : {
|
"fr" : {
|
||||||
|
|
@ -17245,9 +17193,6 @@
|
||||||
},
|
},
|
||||||
"Show timer name" : {
|
"Show timer name" : {
|
||||||
|
|
||||||
},
|
|
||||||
"Shows a lock icon in the Dynamic Island when the screen is locked. Use the toggles above to control the lock screen media panel and weather capsule." : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Shows green camera icon and yellow microphone icon when in use. Uses event-driven CoreAudio and CoreMediaIO APIs." : {
|
"Shows green camera icon and yellow microphone icon when in use. Uses event-driven CoreAudio and CoreMediaIO APIs." : {
|
||||||
|
|
||||||
|
|
@ -18456,9 +18401,16 @@
|
||||||
},
|
},
|
||||||
"Temperature unit" : {
|
"Temperature unit" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"The MacBook icon is available only in the circular layout." : {
|
||||||
|
"comment" : "A message displayed below a toggle that lets the user choose whether to show a MacBook icon in the battery gauge of the weather widget. The message explains that the MacBook icon is only available when the weather widget is in a",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"TheDynamicIsland" : {
|
"TheDynamicIsland" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"These controls mirror the Lock Screen tab so you can tune the media overlay while focusing on playback settings." : {
|
||||||
|
|
||||||
},
|
},
|
||||||
"These controls sit beside the notch while a timer runs. They require the timer name to stay hidden for spacing." : {
|
"These controls sit beside the notch while a timer runs. They require the timer name to stay hidden for spacing." : {
|
||||||
|
|
||||||
|
|
@ -18637,6 +18589,10 @@
|
||||||
},
|
},
|
||||||
"Timer tint" : {
|
"Timer tint" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Timer Widget" : {
|
||||||
|
"comment" : "A section header for the timer widget settings.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Toggle Notch Open:" : {
|
"Toggle Notch Open:" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
@ -19920,6 +19876,10 @@
|
||||||
},
|
},
|
||||||
"Weather data provider" : {
|
"Weather data provider" : {
|
||||||
|
|
||||||
|
},
|
||||||
|
"Weather Widget" : {
|
||||||
|
"comment" : "A section header in the Lock Screen Settings view.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
},
|
},
|
||||||
"Weather: %@ %@" : {
|
"Weather: %@ %@" : {
|
||||||
"comment" : "Weather description and temperature",
|
"comment" : "Weather description and temperature",
|
||||||
|
|
@ -20154,9 +20114,6 @@
|
||||||
},
|
},
|
||||||
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
|
"When enabled, the Stats tab will display real-time system performance graphs. This feature requires system permissions and may use additional battery." : {
|
||||||
|
|
||||||
},
|
|
||||||
"Widget layout" : {
|
|
||||||
|
|
||||||
},
|
},
|
||||||
"Width:" : {
|
"Width:" : {
|
||||||
"localizations" : {
|
"localizations" : {
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,11 @@ struct MinimalisticMusicPlayerView: View {
|
||||||
@Default(.musicAuxRightControl) private var rightAuxControl
|
@Default(.musicAuxRightControl) private var rightAuxControl
|
||||||
@Default(.musicSkipBehavior) private var musicSkipBehavior
|
@Default(.musicSkipBehavior) private var musicSkipBehavior
|
||||||
@ObservedObject private var reminderManager = ReminderLiveActivityManager.shared
|
@ObservedObject private var reminderManager = ReminderLiveActivityManager.shared
|
||||||
|
@ObservedObject private var timerManager = TimerManager.shared
|
||||||
|
@ObservedObject private var coordinator = DynamicIslandViewCoordinator.shared
|
||||||
@Default(.enableReminderLiveActivity) private var enableReminderLiveActivity
|
@Default(.enableReminderLiveActivity) private var enableReminderLiveActivity
|
||||||
@Default(.enableLyrics) private var enableLyrics
|
@Default(.enableLyrics) private var enableLyrics
|
||||||
|
@Default(.timerPresets) private var timerPresets
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
VStack(spacing: 0) {
|
||||||
|
|
@ -76,6 +79,8 @@ struct MinimalisticMusicPlayerView: View {
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
timerCountdownSection
|
||||||
|
|
||||||
reminderList
|
reminderList
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 12)
|
.padding(.horizontal, 12)
|
||||||
|
|
@ -83,6 +88,7 @@ struct MinimalisticMusicPlayerView: View {
|
||||||
.padding(.bottom, ReminderLiveActivityManager.baselineMinimalisticBottomPadding)
|
.padding(.bottom, ReminderLiveActivityManager.baselineMinimalisticBottomPadding)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.frame(height: calculateDynamicHeight(), alignment: .top)
|
.frame(height: calculateDynamicHeight(), alignment: .top)
|
||||||
|
.animation(.smooth(duration: 0.3), value: dynamicHeightSignature)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - TypingLyricView
|
// MARK: - TypingLyricView
|
||||||
|
|
@ -156,6 +162,37 @@ struct MinimalisticMusicPlayerView: View {
|
||||||
ReminderLiveActivityManager.additionalHeight(forRowCount: reminderEntries.count)
|
ReminderLiveActivityManager.additionalHeight(forRowCount: reminderEntries.count)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var shouldShowTimerCountdown: Bool {
|
||||||
|
coordinator.timerLiveActivityEnabled && timerManager.isExternalTimerActive
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timerCountdownColor: Color {
|
||||||
|
let baseColor: Color
|
||||||
|
if let presetId = timerManager.activePresetId,
|
||||||
|
let preset = timerPresets.first(where: { $0.id == presetId }) {
|
||||||
|
baseColor = preset.color
|
||||||
|
} else {
|
||||||
|
baseColor = timerManager.timerColor
|
||||||
|
}
|
||||||
|
return baseColor.ensureMinimumBrightness(factor: 0.75)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timerCountdownText: String {
|
||||||
|
timerManager.formattedRemainingTime()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timerDisplayName: String {
|
||||||
|
let trimmed = timerManager.timerName.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||||
|
return trimmed.isEmpty ? "Timer" : trimmed
|
||||||
|
}
|
||||||
|
|
||||||
|
private var dynamicHeightSignature: Int {
|
||||||
|
var signature = reminderEntries.count * 10
|
||||||
|
if enableLyrics { signature += 1 }
|
||||||
|
if shouldShowTimerCountdown { signature += 100 }
|
||||||
|
return signature
|
||||||
|
}
|
||||||
|
|
||||||
private func calculateDynamicHeight() -> CGFloat {
|
private func calculateDynamicHeight() -> CGFloat {
|
||||||
var height: CGFloat = 50 // Base height for header
|
var height: CGFloat = 50 // Base height for header
|
||||||
|
|
||||||
|
|
@ -172,6 +209,10 @@ struct MinimalisticMusicPlayerView: View {
|
||||||
height += lyricsTopPadding + lyricsEstimatedHeight
|
height += lyricsTopPadding + lyricsEstimatedHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldShowTimerCountdown {
|
||||||
|
height += minimalisticTimerCountdownBlockHeight
|
||||||
|
}
|
||||||
|
|
||||||
// Add reminder list height if showing
|
// Add reminder list height if showing
|
||||||
if shouldShowReminderList {
|
if shouldShowReminderList {
|
||||||
height += reminderListHeight
|
height += reminderListHeight
|
||||||
|
|
@ -184,6 +225,60 @@ struct MinimalisticMusicPlayerView: View {
|
||||||
return height
|
return height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var timerCountdownSection: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
if shouldShowTimerCountdown {
|
||||||
|
timerCountdownView
|
||||||
|
.transition(.opacity.combined(with: .move(edge: .top)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.padding(.top, shouldShowTimerCountdown ? minimalisticTimerCountdownTopPadding : 0)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.frame(height: shouldShowTimerCountdown ? minimalisticTimerCountdownBlockHeight : 0, alignment: .top)
|
||||||
|
.animation(.smooth(duration: 0.25), value: shouldShowTimerCountdown)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func displayFont(size: CGFloat) -> Font {
|
||||||
|
.custom("SF Pro Display", size: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var timerCountdownView: some View {
|
||||||
|
GeometryReader { geometry in
|
||||||
|
let availableWidth = geometry.size.width
|
||||||
|
let preferredCountdownWidth = max(availableWidth * 0.42, 150)
|
||||||
|
let maxCountdownWidth = max(availableWidth - 60, 0)
|
||||||
|
let countdownWidth = min(preferredCountdownWidth, maxCountdownWidth)
|
||||||
|
let marqueeWidth = max(availableWidth - countdownWidth - 12, 0)
|
||||||
|
|
||||||
|
HStack(alignment: .lastTextBaseline, spacing: 12) {
|
||||||
|
MarqueeText(
|
||||||
|
.init(get: { timerDisplayName }, set: { _ in }),
|
||||||
|
font: .system(size: 16, weight: .semibold),
|
||||||
|
nsFont: .title3,
|
||||||
|
textColor: timerCountdownColor.opacity(0.85),
|
||||||
|
frameWidth: max(marqueeWidth, 1)
|
||||||
|
)
|
||||||
|
.alignmentGuide(.lastTextBaseline) { dimensions in
|
||||||
|
dimensions[VerticalAlignment.bottom]
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(minLength: 8)
|
||||||
|
|
||||||
|
Text(timerCountdownText)
|
||||||
|
.font(displayFont(size: 56))
|
||||||
|
.monospacedDigit()
|
||||||
|
.foregroundStyle(timerManager.isOvertime ? Color.red : timerCountdownColor)
|
||||||
|
.contentTransition(.numericText())
|
||||||
|
.animation(.smooth(duration: 0.25), value: timerManager.remainingTime)
|
||||||
|
.lineLimit(1)
|
||||||
|
.frame(width: countdownWidth, alignment: .trailing)
|
||||||
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 4)
|
||||||
|
.frame(height: minimalisticTimerCountdownContentHeight, alignment: .top)
|
||||||
|
}
|
||||||
|
|
||||||
private var reminderList: some View {
|
private var reminderList: some View {
|
||||||
MinimalisticReminderEventListView(reminders: reminderEntries)
|
MinimalisticReminderEventListView(reminders: reminderEntries)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -37,6 +37,7 @@ struct TimerLiveActivity: View {
|
||||||
@Default(.timerSolidColor) private var solidColor
|
@Default(.timerSolidColor) private var solidColor
|
||||||
@Default(.timerPresets) private var timerPresets
|
@Default(.timerPresets) private var timerPresets
|
||||||
@Default(.timerControlWindowEnabled) private var controlWindowEnabled
|
@Default(.timerControlWindowEnabled) private var controlWindowEnabled
|
||||||
|
@Default(.enableMinimalisticUI) private var enableMinimalisticUI
|
||||||
|
|
||||||
private var notchContentHeight: CGFloat {
|
private var notchContentHeight: CGFloat {
|
||||||
max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))
|
max(0, vm.effectiveClosedNotchHeight - (isHovering ? 0 : 12))
|
||||||
|
|
@ -614,6 +615,7 @@ struct TimerLiveActivity: View {
|
||||||
|
|
||||||
private func triggerTransientLabel() {
|
private func triggerTransientLabel() {
|
||||||
guard !showsLabel else { return }
|
guard !showsLabel else { return }
|
||||||
|
guard !enableMinimalisticUI else { return }
|
||||||
cancelTransientLabel()
|
cancelTransientLabel()
|
||||||
withAnimation(.smooth) {
|
withAnimation(.smooth) {
|
||||||
showTransientLabel = true
|
showTransientLabel = true
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,14 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
TimerManager.shared.$activeSource
|
||||||
|
.combineLatest(TimerManager.shared.$isTimerActive)
|
||||||
|
.receive(on: RunLoop.main)
|
||||||
|
.sink { [weak self] _, _ in
|
||||||
|
self?.handleMinimalisticTimerHeightChange()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
coordinator.$statsSecondRowExpansion
|
coordinator.$statsSecondRowExpansion
|
||||||
.removeDuplicates()
|
.removeDuplicates()
|
||||||
.receive(on: RunLoop.main)
|
.receive(on: RunLoop.main)
|
||||||
|
|
@ -140,6 +148,23 @@ class DynamicIslandViewModel: NSObject, ObservableObject {
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handleMinimalisticTimerHeightChange() {
|
||||||
|
guard Defaults[.enableMinimalisticUI] else { return }
|
||||||
|
guard notchState == .open else { return }
|
||||||
|
let updatedTarget = calculateDynamicNotchSize()
|
||||||
|
guard notchSize != updatedTarget else { return }
|
||||||
|
withAnimation(.smooth) {
|
||||||
|
notchSize = updatedTarget
|
||||||
|
}
|
||||||
|
if let delegate = AppDelegate.shared {
|
||||||
|
delegate.ensureWindowSize(
|
||||||
|
addShadowPadding(to: updatedTarget, isMinimalistic: Defaults[.enableMinimalisticUI]),
|
||||||
|
animated: true,
|
||||||
|
force: false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func setupDetectorObserver() {
|
private func setupDetectorObserver() {
|
||||||
// 1) Publisher for the user’s fullscreen detection setting
|
// 1) Publisher for the user’s fullscreen detection setting
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,9 @@ let batterySneakSize: CGSize = .init(width: 160, height: 1)
|
||||||
let openNotchSize: CGSize = .init(width: 640, height: 190)
|
let openNotchSize: CGSize = .init(width: 640, height: 190)
|
||||||
private let minimalisticBaseOpenNotchSize: CGSize = .init(width: 420, height: 180)
|
private let minimalisticBaseOpenNotchSize: CGSize = .init(width: 420, height: 180)
|
||||||
private let minimalisticLyricsExtraHeight: CGFloat = 40
|
private let minimalisticLyricsExtraHeight: CGFloat = 40
|
||||||
|
let minimalisticTimerCountdownTopPadding: CGFloat = 12
|
||||||
|
let minimalisticTimerCountdownContentHeight: CGFloat = 82
|
||||||
|
let minimalisticTimerCountdownBlockHeight: CGFloat = minimalisticTimerCountdownTopPadding + minimalisticTimerCountdownContentHeight
|
||||||
let statsSecondRowContentHeight: CGFloat = 120
|
let statsSecondRowContentHeight: CGFloat = 120
|
||||||
let statsGridSpacingHeight: CGFloat = 12
|
let statsGridSpacingHeight: CGFloat = 12
|
||||||
let notchShadowPaddingStandard: CGFloat = 18
|
let notchShadowPaddingStandard: CGFloat = 18
|
||||||
|
|
@ -36,6 +39,10 @@ var minimalisticOpenNotchSize: CGSize {
|
||||||
size.height += reminderHeight
|
size.height += reminderHeight
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if DynamicIslandViewCoordinator.shared.timerLiveActivityEnabled && TimerManager.shared.isExternalTimerActive {
|
||||||
|
size.height += minimalisticTimerCountdownBlockHeight
|
||||||
|
}
|
||||||
|
|
||||||
return size
|
return size
|
||||||
}
|
}
|
||||||
let cornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 19, bottom: 24), closed: (top: 6, bottom: 14))
|
let cornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 19, bottom: 24), closed: (top: 6, bottom: 14))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue