Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 1 addition & 24 deletions App/Continuity/Views/LibrarySheetView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import Playback
/// Library page in the vertical shell (above Now Playing): browse/add/delete playlists,
/// with a mini player that jumps back to the home page.
struct LibrarySheetView: View {
@Environment(Player.self) private var player
@Environment(PreparationQueue.self) private var prepQueue
@Environment(MainPagerState.self) private var pagerState
@Environment(\.modelContext) private var modelContext
Expand All @@ -20,6 +19,7 @@ struct LibrarySheetView: View {
var body: some View {
NavigationStack {
LibraryView()
.miniPlayerDock()
.navigationTitle("Continuity")
.toolbar {
// Every action is a primaryAction so nothing collapses into a dead "…"
Expand Down Expand Up @@ -92,29 +92,6 @@ struct LibrarySheetView: View {
isImportingLocal = false
}
}
.safeAreaInset(edge: .bottom) {
if player.currentTrack != nil {
MiniPlayerView()
.onTapGesture { pagerState.goToNowPlaying() }
.padding(.horizontal, 12)
.padding(.bottom, 6)
.accessibilityHint("Opens Now Playing")
} else {
// No mini player when idle — still need a way back to the home page.
Button {
pagerState.goToNowPlaying()
} label: {
Image(systemName: "chevron.compact.down")
.font(.title2.weight(.semibold))
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityLabel("Now Playing")
}
}
}
}

Expand Down
2 changes: 1 addition & 1 deletion App/Continuity/Views/MainPagerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ struct MainPagerView: View {
.padding(.bottom, insets.bottom)
.containerRelativeFrame(.vertical)
.id(MainPagerState.Page.library)
NowPlayingView(mode: .home)
NowPlayingView()
.padding(.top, insets.top)
.padding(.bottom, insets.bottom)
.containerRelativeFrame(.vertical)
Expand Down
57 changes: 56 additions & 1 deletion App/Continuity/Views/MiniPlayerView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,23 @@ import SwiftUI
import Playback

/// Compact Liquid Glass now-playing bar docked above the bottom safe area.
/// The whole bar is one button that jumps the pager home to Now Playing; play/pause and
/// skip stay as inner buttons, which win the tap on their own frames.
struct MiniPlayerView: View {
@Environment(Player.self) private var player
@Environment(MainPagerState.self) private var pagerState

var body: some View {
Button {
pagerState.goToNowPlaying()
} label: {
barContent
}
.buttonStyle(.plain)
.accessibilityHint("Opens Now Playing")
}

private var barContent: some View {
HStack(spacing: 12) {
if let track = player.currentTrack {
RemoteArtworkView(url: track.artworkURL, symbol: track.artworkSymbol, seed: track.gradientSeed, cornerRadius: 8)
Expand Down Expand Up @@ -37,14 +50,56 @@ struct MiniPlayerView: View {
}
.padding(.horizontal, 12)
.padding(.vertical, 8)
.continuityGlass(cornerRadius: 18)
.continuityGlass(cornerRadius: 18, interactive: true)
// The glass shape is the tap target — without this, only the bar's subviews hit-test.
.contentShape(RoundedRectangle(cornerRadius: 18, style: .continuous))
// Thin play-progress line hugging the bottom edge of the glass bar.
.overlay(alignment: .bottomLeading) {
MiniProgressLine()
}
}
}

/// The bottom dock every library screen shares: the mini player while something is staged, or
/// a plain chevron back to Now Playing when idle.
///
/// Attach it PER SCREEN (library root and each pushed destination) via `.miniPlayerDock()`:
/// a `safeAreaInset` added outside the NavigationStack does not reach pushed destinations, so
/// their lists scrolled underneath the bar (the "mini player covers the last row" bug).
struct MiniPlayerDock: View {
@Environment(Player.self) private var player
@Environment(MainPagerState.self) private var pagerState

var body: some View {
if player.currentTrack != nil {
MiniPlayerView()
.padding(.horizontal, 12)
.padding(.bottom, 6)
} else {
Button {
pagerState.goToNowPlaying()
} label: {
Image(systemName: "chevron.compact.down")
.font(.title2.weight(.semibold))
.foregroundStyle(.secondary)
.frame(maxWidth: .infinity)
.padding(.vertical, 10)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityLabel("Now Playing")
}
}
}

extension View {
/// Docks the shared mini player above this screen's bottom edge and insets its scroll
/// content by the dock's height. Apply to each screen inside the library NavigationStack.
func miniPlayerDock() -> some View {
safeAreaInset(edge: .bottom) { MiniPlayerDock() }
}
}

/// Leaf view: the mini player's only `player.position` reader, so the 20 Hz playback ticks
/// re-evaluate just this line — not the whole bar (artwork row included) inside the
/// always-mounted library page.
Expand Down
Loading
Loading