mirror of
https://github.com/FBukovina/Atoll.git
synced 2026-10-27 15:14:33 +00:00
Nothing Playing
This commit is contained in:
parent
12f8bc28ba
commit
b46d88c79a
5 changed files with 118 additions and 60 deletions
|
|
@ -17793,6 +17793,10 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Nothing Playing" : {
|
||||||
|
"comment" : "A label displayed when there is nothing playing in the music player.",
|
||||||
|
"isCommentAutoGenerated" : true
|
||||||
|
},
|
||||||
"Notify before" : {
|
"Notify before" : {
|
||||||
"comment" : "A label describing the slider that allows the user to set a reminder lead time.",
|
"comment" : "A label describing the slider that allows the user to set a reminder lead time.",
|
||||||
"isCommentAutoGenerated" : true,
|
"isCommentAutoGenerated" : true,
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ struct LockScreenMusicPanel: View {
|
||||||
}
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if isActive {
|
if isActive && musicManager.hasActiveSession {
|
||||||
panelContent
|
panelContent
|
||||||
} else {
|
} else {
|
||||||
Color.clear
|
Color.clear
|
||||||
|
|
|
||||||
|
|
@ -45,72 +45,98 @@ struct MinimalisticMusicPlayerView: View {
|
||||||
private let skipMagnitude: CGFloat = 8
|
private let skipMagnitude: CGFloat = 8
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 0) {
|
if !musicManager.hasActiveSession {
|
||||||
// Header area with album art (matching DynamicIslandHeader height of 24pt)
|
// Nothing playing state
|
||||||
GeometryReader { headerGeo in
|
VStack(spacing: 0) {
|
||||||
let albumArtWidth: CGFloat = 50
|
Spacer(minLength: 0)
|
||||||
let spacing: CGFloat = 10
|
|
||||||
let visualizerWidth: CGFloat = useMusicVisualizer ? 24 : 0
|
VStack(spacing: 8) {
|
||||||
let textWidth = max(0, headerGeo.size.width - albumArtWidth - spacing - (useMusicVisualizer ? (visualizerWidth + spacing) : 0))
|
Image(systemName: "music.note.slash")
|
||||||
HStack(alignment: .center, spacing: spacing) {
|
.font(.system(size: 24, weight: .light))
|
||||||
MinimalisticAlbumArtView(vm: vm, albumArtNamespace: albumArtNamespace)
|
.foregroundColor(.gray)
|
||||||
.frame(width: albumArtWidth, height: albumArtWidth)
|
Text("Nothing Playing")
|
||||||
|
.font(.system(size: 12, weight: .medium))
|
||||||
|
.foregroundColor(.gray)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(minLength: 0)
|
||||||
|
|
||||||
|
timerCountdownSection
|
||||||
|
|
||||||
|
reminderList
|
||||||
|
}
|
||||||
|
.padding(.horizontal, 12)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: calculateDynamicHeight())
|
||||||
|
.animation(.smooth(duration: 0.3), value: dynamicHeightSignature)
|
||||||
|
} else {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
// Header area with album art (matching DynamicIslandHeader height of 24pt)
|
||||||
|
GeometryReader { headerGeo in
|
||||||
|
let albumArtWidth: CGFloat = 50
|
||||||
|
let spacing: CGFloat = 10
|
||||||
|
let visualizerWidth: CGFloat = useMusicVisualizer ? 24 : 0
|
||||||
|
let textWidth = max(0, headerGeo.size.width - albumArtWidth - spacing - (useMusicVisualizer ? (visualizerWidth + spacing) : 0))
|
||||||
|
HStack(alignment: .center, spacing: spacing) {
|
||||||
|
MinimalisticAlbumArtView(vm: vm, albumArtNamespace: albumArtNamespace)
|
||||||
|
.frame(width: albumArtWidth, height: albumArtWidth)
|
||||||
|
|
||||||
|
VStack(alignment: .leading, spacing: 1) {
|
||||||
|
if !musicManager.songTitle.isEmpty {
|
||||||
|
MarqueeText(
|
||||||
|
$musicManager.songTitle,
|
||||||
|
font: .system(size: 12, weight: .semibold),
|
||||||
|
nsFont: .subheadline,
|
||||||
|
textColor: .white,
|
||||||
|
frameWidth: textWidth
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text(musicManager.artistName)
|
||||||
|
.font(.system(size: 10, weight: .regular))
|
||||||
|
.foregroundColor(Defaults[.playerColorTinting] ? Color(nsColor: musicManager.avgColor).ensureMinimumBrightness(factor: 0.6) : .gray)
|
||||||
|
.lineLimit(1)
|
||||||
|
|
||||||
VStack(alignment: .leading, spacing: 1) {
|
|
||||||
if !musicManager.songTitle.isEmpty {
|
|
||||||
MarqueeText(
|
|
||||||
$musicManager.songTitle,
|
|
||||||
font: .system(size: 12, weight: .semibold),
|
|
||||||
nsFont: .subheadline,
|
|
||||||
textColor: .white,
|
|
||||||
frameWidth: textWidth
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
.frame(width: textWidth, alignment: .leading)
|
||||||
|
|
||||||
Text(musicManager.artistName)
|
if useMusicVisualizer {
|
||||||
.font(.system(size: 10, weight: .regular))
|
visualizer
|
||||||
.foregroundColor(Defaults[.playerColorTinting] ? Color(nsColor: musicManager.avgColor).ensureMinimumBrightness(factor: 0.6) : .gray)
|
.frame(width: visualizerWidth)
|
||||||
.lineLimit(1)
|
}
|
||||||
|
|
||||||
}
|
|
||||||
.frame(width: textWidth, alignment: .leading)
|
|
||||||
|
|
||||||
if useMusicVisualizer {
|
|
||||||
visualizer
|
|
||||||
.frame(width: visualizerWidth)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
.frame(height: 50)
|
||||||
.frame(height: 50)
|
|
||||||
|
// Compact progress bar
|
||||||
// Compact progress bar
|
progressBar
|
||||||
progressBar
|
.padding(.top, 6)
|
||||||
.padding(.top, 6)
|
|
||||||
|
// Compact playback controls
|
||||||
// Compact playback controls
|
if shouldShowControlHUDRow {
|
||||||
if shouldShowControlHUDRow {
|
controlHUDRow
|
||||||
controlHUDRow
|
.padding(.top, 4)
|
||||||
.padding(.top, 4)
|
} else {
|
||||||
} else {
|
playbackControls
|
||||||
playbackControls
|
.padding(.top, 4)
|
||||||
.padding(.top, 4)
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if enableLyrics {
|
if enableLyrics {
|
||||||
lyricsView
|
lyricsView
|
||||||
.padding(.top, 10)
|
.padding(.top, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
timerCountdownSection
|
||||||
|
|
||||||
|
reminderList
|
||||||
}
|
}
|
||||||
|
.padding(.horizontal, 12)
|
||||||
timerCountdownSection
|
.padding(.top, -15)
|
||||||
|
.padding(.bottom, ReminderLiveActivityManager.baselineMinimalisticBottomPadding)
|
||||||
reminderList
|
.frame(maxWidth: .infinity)
|
||||||
|
.frame(height: calculateDynamicHeight(), alignment: .top)
|
||||||
|
.animation(.smooth(duration: 0.3), value: dynamicHeightSignature)
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 12)
|
|
||||||
.padding(.top, -15)
|
|
||||||
.padding(.bottom, ReminderLiveActivityManager.baselineMinimalisticBottomPadding)
|
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.frame(height: calculateDynamicHeight(), alignment: .top)
|
|
||||||
.animation(.smooth(duration: 0.3), value: dynamicHeightSignature)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - TypingLyricView
|
// MARK: - TypingLyricView
|
||||||
|
|
|
||||||
|
|
@ -521,8 +521,14 @@ 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
|
||||||
|
@ObservedObject private var musicManager = MusicManager.shared
|
||||||
@Default(.showStandardMediaControls) private var showStandardMediaControls
|
@Default(.showStandardMediaControls) private var showStandardMediaControls
|
||||||
let albumArtNamespace: Namespace.ID
|
let albumArtNamespace: Namespace.ID
|
||||||
|
|
||||||
|
/// Whether the music player should actively display (enabled AND has real content).
|
||||||
|
private var shouldShowMusicPlayer: Bool {
|
||||||
|
showStandardMediaControls && musicManager.hasActiveSession
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
|
|
@ -546,14 +552,14 @@ 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
|
||||||
if showStandardMediaControls {
|
if shouldShowMusicPlayer {
|
||||||
MusicPlayerView(albumArtNamespace: albumArtNamespace)
|
MusicPlayerView(albumArtNamespace: albumArtNamespace)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
}
|
}
|
||||||
|
|
||||||
if Defaults[.showCalendar] {
|
if Defaults[.showCalendar] {
|
||||||
Group {
|
Group {
|
||||||
if showStandardMediaControls {
|
if shouldShowMusicPlayer {
|
||||||
CalendarView()
|
CalendarView()
|
||||||
} else {
|
} else {
|
||||||
StandaloneCalendarView()
|
StandaloneCalendarView()
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,28 @@ class MusicManager: ObservableObject {
|
||||||
@Published var isPlaying = false
|
@Published var isPlaying = false
|
||||||
@Published var album: String = "Self Love"
|
@Published var album: String = "Self Love"
|
||||||
@Published var isPlayerIdle: Bool = true
|
@Published var isPlayerIdle: Bool = true
|
||||||
|
|
||||||
|
/// Whether there is an active music session with real metadata.
|
||||||
|
/// Returns `false` only when the metadata is still placeholder/fallback defaults
|
||||||
|
/// (i.e. nothing has been played since app launch, or the controller returned
|
||||||
|
/// unknown/not-playing placeholders). Paused music with real metadata is still
|
||||||
|
/// considered an active session.
|
||||||
|
private static let placeholderTitles: Set<String> = [
|
||||||
|
"i'm handsome", "unknown", "not playing"
|
||||||
|
]
|
||||||
|
private static let placeholderArtists: Set<String> = [
|
||||||
|
"me", "unknown"
|
||||||
|
]
|
||||||
|
|
||||||
|
var hasActiveSession: Bool {
|
||||||
|
if isPlaying { return true }
|
||||||
|
let trimmedTitle = songTitle.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||||
|
let trimmedArtist = artistName.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
|
||||||
|
let hasRealTitle = !trimmedTitle.isEmpty && !Self.placeholderTitles.contains(trimmedTitle)
|
||||||
|
let hasRealArtist = !trimmedArtist.isEmpty && !Self.placeholderArtists.contains(trimmedArtist)
|
||||||
|
return hasRealTitle || hasRealArtist
|
||||||
|
}
|
||||||
|
|
||||||
@Published var animations: DynamicIslandAnimations = .init()
|
@Published var animations: DynamicIslandAnimations = .init()
|
||||||
@Published var avgColor: NSColor = .white
|
@Published var avgColor: NSColor = .white
|
||||||
@Published var bundleIdentifier: String? = nil
|
@Published var bundleIdentifier: String? = nil
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue