ios: reorderable tab bar with more tabs, debug log export, AirPlay picker, more room above seeker
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0fe35a8d8
commit
926ad6ee99
10 files changed
+350
-10
No files matched your search
@@ -1,11 +1,32 @@
|
||||
import SwiftUI
|
||||
|
||||
enum Tab: String, CaseIterable, Hashable {
|
||||
case Library, Playlists, Search, Downloads, Settings
|
||||
case Library, Songs, Albums, Artists, Genres, Playlists, Search, Downloads, Settings
|
||||
|
||||
static let defaultLayout: [Tab] = [.Library, .Playlists, .Search, .Downloads, .Settings]
|
||||
/// iOS folds anything past five tabs into "More", so the bar holds at most this many.
|
||||
static let maxVisible = 5
|
||||
|
||||
/// Parses the saved comma list; Settings is always kept so the app can't lock itself out.
|
||||
static func layout(_ raw: String) -> [Tab] {
|
||||
var out: [Tab] = []
|
||||
for part in raw.split(separator: ",") {
|
||||
if let t = Tab(rawValue: String(part)), !out.contains(t) { out.append(t) }
|
||||
}
|
||||
if out.isEmpty { return defaultLayout }
|
||||
if !out.contains(.Settings) { out = Array(out.prefix(maxVisible - 1)) + [.Settings] }
|
||||
return Array(out.prefix(maxVisible))
|
||||
}
|
||||
|
||||
static func encode(_ tabs: [Tab]) -> String { tabs.map(\.rawValue).joined(separator: ",") }
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .Library: "square.stack.fill"
|
||||
case .Songs: "music.note"
|
||||
case .Albums: "square.stack"
|
||||
case .Artists: "music.mic"
|
||||
case .Genres: "guitars.fill"
|
||||
case .Playlists: "music.note.list"
|
||||
case .Search: "magnifyingglass"
|
||||
case .Downloads: "arrow.down.circle.fill"
|
||||
@@ -15,10 +36,10 @@ enum Tab: String, CaseIterable, Hashable {
|
||||
}
|
||||
|
||||
enum Route: Hashable {
|
||||
case Artists, Albums, Songs, Playlists
|
||||
case Artists, Albums, Songs, Playlists, Genres, Downloads, TabBar
|
||||
case Eq, EqFilters(profileId: String), EqFilterPage(profileId: String, filterId: String)
|
||||
case PlexAccount, StemServer, Quality, Storage, Appearance
|
||||
case ArtistPage(Artist), AlbumPage(Album), PlaylistPage(Playlist), StemPlaylistPage(String)
|
||||
case GenrePage(String), ArtistPage(Artist), AlbumPage(Album), PlaylistPage(Playlist), StemPlaylistPage(String)
|
||||
}
|
||||
|
||||
/// Tabs, their stacks and the Now Playing sheet: the things any screen can ask of the shell.
|
||||
@@ -47,13 +68,30 @@ final class Ui {
|
||||
if t == tab { paths[t] = [] } else { tab = t }
|
||||
}
|
||||
|
||||
/// The tabs currently in the bar, in order.
|
||||
var tabs: [Tab] { Tab.layout(AppGraph.shared.settings.tabLayout) }
|
||||
|
||||
/// Where library pages open: Library if it's in the bar, else the first tab that is.
|
||||
private var home: Tab { tabs.contains(.Library) ? .Library : (tabs.first ?? .Settings) }
|
||||
|
||||
/// Downloads as its own tab, or pushed onto the current one when it's hidden.
|
||||
func openDownloads() {
|
||||
if tabs.contains(.Downloads) { selectTab(.Downloads) } else { push(.Downloads) }
|
||||
}
|
||||
|
||||
/// Falls back to the first tab when the selected one was just removed from the bar.
|
||||
func ensureValidTab() {
|
||||
if !tabs.contains(tab) { tab = tabs.first ?? .Settings }
|
||||
}
|
||||
|
||||
func openNowPlaying() { nowPlayingOpen = true }
|
||||
|
||||
/// Switches to the Library tab and pushes [r] there.
|
||||
func showInLibrary(_ r: Route) {
|
||||
nowPlayingOpen = false
|
||||
tab = .Library
|
||||
paths[.Library, default: []].append(r)
|
||||
let t = home
|
||||
tab = t
|
||||
paths[t, default: []].append(r)
|
||||
}
|
||||
|
||||
/// Switches to Settings and opens [r] (the Equalizer) there.
|
||||
|
||||
Reference in new issue
Block a user