import SwiftUI /// The tab bar, a navigation stack per tab, the mini player and the Now Playing sheet. struct RootView: View { @State private var ui = Ui(overlays: Overlays()) private var g: AppGraph { .shared } var body: some View { let s = g.settings let p = Palette.of(s.theme, s.accent) TabView(selection: Binding(get: { ui.tab }, set: { ui.selectTab($0) })) { ForEach(Tab.allCases, id: \.self) { t in NavigationStack(path: ui.path(t)) { root(t).miniPlayerRoom() .navigationDestination(for: Route.self) { destination($0).miniPlayerRoom() } } // Floats over the screens; each screen leaves room for it so nothing ends up underneath. .overlay(alignment: .bottom) { if g.playback.current != nil { MiniPlayer().transition(.move(edge: .bottom).combined(with: .opacity)) } } .animation(.spring(duration: 0.35), value: g.playback.current != nil) .tabItem { Label(t.rawValue, systemImage: t.icon) } .tag(t) } } .sheet(isPresented: Binding(get: { ui.nowPlayingOpen }, set: { ui.nowPlayingOpen = $0 })) { NowPlayingScreen() .overlayHost(ui.overlays, level: 1) .environment(ui) .environment(\.palette, p) .preferredColorScheme(s.theme == .White ? .light : .dark) .tint(p.accent) } .overlayHost(ui.overlays, level: 0) .onChange(of: g.playback.message) { _, m in if let m { ui.overlays.hud(m.text, "info.circle") } } .environment(ui) .environment(\.palette, p) .preferredColorScheme(s.theme == .White ? .light : .dark) .tint(p.accent) } @ViewBuilder private func root(_ t: Tab) -> some View { switch t { case .Library: LibraryHome() case .Playlists: PlaylistsScreen() case .Search: SearchScreen() case .Downloads: DownloadsScreen() case .Settings: SettingsScreen() } } @ViewBuilder private func destination(_ r: Route) -> some View { switch r { case .Artists: ArtistsScreen() case .Albums: AlbumsScreen() case .Songs: SongsScreen() case .Playlists: PlaylistsScreen() case .Eq: EqScreen() case .EqFilters(let id): EqFiltersScreen(profileId: id) case .EqFilterPage(let id, let f): EqFilterPage(profileId: id, filterId: f) case .PlexAccount: PlexAccountScreen() case .StemServer: StemServerScreen() case .Quality: QualityScreen() case .Storage: StorageScreen() case .Appearance: AppearanceScreen() case .ArtistPage(let a): ArtistScreen(artist: a) case .AlbumPage(let a): AlbumScreen(album: a) case .PlaylistPage(let pl): PlaylistScreen(playlist: pl) } } } private struct MiniPlayerRoom: ViewModifier { private var g: AppGraph { .shared } func body(content: Content) -> some View { content.safeAreaInset(edge: .bottom, spacing: 0) { if g.playback.current != nil { Color.clear.frame(height: 66) } } } } private extension View { func miniPlayerRoom() -> some View { modifier(MiniPlayerRoom()) } }