Add iOS app with CarPlay and Siri
SwiftUI port of the Android app under ios/, with CarPlay browse and Now Playing, Siri play-media intents, and a build-ipa.sh script that makes an unsigned IPA for sideloading. README gets an iOS section and the vibecoded disclaimer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
45bc0eefe0
commit
fdf9e970b2
45 files changed
+9822
No files matched your search
@@ -0,0 +1,70 @@
|
||||
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).navigationDestination(for: Route.self) { destination($0) }
|
||||
}
|
||||
.safeAreaInset(edge: .bottom, spacing: 0) {
|
||||
if g.playback.current != nil { MiniPlayer() }
|
||||
}
|
||||
.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user