fixed dynamicminiplayer placement

This commit is contained in:
Dallas Groot 2026-04-11 18:15:10 -07:00
parent 8eaab0bc93
commit 3f3010a8fd

View file

@ -10,6 +10,7 @@ struct MainTabView: View {
@State private var navigateToAlbumId: String?
@State private var navigateToArtistId: String?
@State var showNowPlaying = false
@State private var wasInDynamicIsland = false // restore DI mode on NowPlaying dismiss
// Dynamic Island morph
@Namespace private var playerNamespace
@ -173,6 +174,15 @@ struct MainTabView: View {
.opacity(showNowPlaying ? 0 : 1)
}
}
.onChange(of: showNowPlaying) { _, isShowing in
// If we were in DI mode when NowPlaying opened, restore it on dismiss
if !isShowing && wasInDynamicIsland {
wasInDynamicIsland = false
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
isDynamicIsland = true
}
}
}
.onChange(of: debugLogger.isPiP) { _, newVal in
// DebugConsoleView's PiP button sets logger.isPiP; sync to local state
if newVal { debugPipMode = true }
@ -694,87 +704,98 @@ struct DynamicIslandView: View {
var body: some View {
VStack(spacing: 0) {
Spacer().frame(height: 12) // Sit directly below Dynamic Island
// The pill only this is tappable for NowPlaying
HStack(spacing: 0) {
Group {
if let song = audioPlayer.currentSong {
AsyncCoverArt(coverArtId: song.coverArt, size: 48)
} else {
Color.gray.opacity(0.3)
Spacer().frame(height: 12)
HStack(spacing: 8) {
// Left pill: album art + song info
// Left-aligned, sits directly under the Dynamic Island
HStack(spacing: 8) {
Group {
if let song = audioPlayer.currentSong {
AsyncCoverArt(coverArtId: song.coverArt, size: 44)
} else {
Color.gray.opacity(0.3)
}
}
.frame(width: 30, height: 30)
.clipShape(RoundedRectangle(cornerRadius: 8, style: .continuous))
.matchedGeometryEffect(id: "albumArt", in: namespace)
VStack(alignment: .leading, spacing: 1) {
Text(audioPlayer.currentSong?.title ?? "")
.font(.system(size: 11, weight: .semibold))
.foregroundColor(.white)
.lineLimit(1)
Text(audioPlayer.currentSong?.artist ?? "")
.font(.system(size: 9))
.foregroundColor(.white.opacity(0.55))
.lineLimit(1)
}
.frame(maxWidth: 140, alignment: .leading)
}
.padding(.horizontal, 10)
.frame(height: 44)
.background(
RoundedRectangle(cornerRadius: 22, style: .continuous)
.fill(Color.black.opacity(0.85))
.shadow(color: themeColor.opacity(0.3), radius: 6, y: 2)
)
.contentShape(RoundedRectangle(cornerRadius: 22))
.onTapGesture {
wasInDynamicIsland = true
withAnimation(.easeInOut(duration: 0.35)) {
showNowPlaying = true
isDynamicIsland = false
}
}
.frame(width: 32, height: 32)
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
.matchedGeometryEffect(id: "albumArt", in: namespace)
.padding(.leading, 10)
VStack(alignment: .leading, spacing: 0) {
Text(audioPlayer.currentSong?.title ?? "")
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.white)
.lineLimit(1)
Text(audioPlayer.currentSong?.artist ?? "")
.font(.system(size: 10))
.foregroundColor(.white.opacity(0.6))
.lineLimit(1)
}
.padding(.leading, 8)
.frame(maxWidth: .infinity, alignment: .leading)
if VisualizerSettings.shared.enabled {
// Visualizer fill
if VisualizerSettings.shared.enabled && VisualizerSettings.shared.miniPlayerEnabled {
CompactVisualizerView(
isPlaying: audioPlayer.isPlaying,
isSongLoaded: audioPlayer.currentSong != nil,
accentColor: themeColor,
height: 32
height: 44
)
.matchedGeometryEffect(id: "visWave", in: namespace)
.frame(width: 64, height: 32)
.clipShape(RoundedRectangle(cornerRadius: 10))
} else {
Button(action: { audioPlayer.togglePlayPause() }) {
Image(systemName: audioPlayer.isPlaying ? "pause.fill" : "play.fill")
.font(.system(size: 16))
.foregroundColor(.white)
}
.frame(width: 40, height: 32)
}
Spacer().frame(width: 10)
}
.frame(height: 44)
.background(
RoundedRectangle(cornerRadius: 22, style: .continuous)
.fill(
LinearGradient(
colors: [Color.black, themeColor.opacity(0.25)],
startPoint: .leading,
endPoint: .trailing
)
.frame(maxWidth: .infinity)
.frame(height: 44)
.clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
.background(
RoundedRectangle(cornerRadius: 22, style: .continuous)
.fill(Color.black.opacity(0.7))
)
.shadow(color: themeColor.opacity(0.4), radius: 8, y: 2)
)
.padding(.horizontal, 56)
.contentShape(RoundedRectangle(cornerRadius: 22))
.onTapGesture {
withAnimation(.easeInOut(duration: 0.35)) {
showNowPlaying = true
isDynamicIsland = false
} else {
Spacer()
}
// Play/pause circle button matches debug PiP circle style
Button(action: { audioPlayer.togglePlayPause() }) {
Image(systemName: audioPlayer.isPlaying ? "pause.fill" : "play.fill")
.font(.system(size: 15, weight: .semibold))
.foregroundColor(.white)
.frame(width: 44, height: 44)
.background(
Circle()
.fill(Color.black.opacity(0.85))
.shadow(color: themeColor.opacity(0.3), radius: 6, y: 2)
)
}
}
.padding(.leading, 16)
.padding(.trailing, 16)
.gesture(
DragGesture(minimumDistance: 8)
.onEnded { value in
if value.translation.height > 25 {
wasInDynamicIsland = false
withAnimation(.spring(response: 0.5, dampingFraction: 0.8)) {
isDynamicIsland = false
}
}
}
)
Spacer()
}
// Status bar stays visible no .statusBarHidden