fixed dynamicminiplayer placement
This commit is contained in:
parent
8eaab0bc93
commit
3f3010a8fd
1 changed files with 81 additions and 60 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue