import SwiftUI import FlixyDesign /// Der Inhalt der Seitenleiste. /// /// Kennt keine Navigation — sie meldet nur, was ausgewählt wurde. Dadurch /// lässt sie sich auf dem iPhone in den Drawer und auf dem iPad in die /// `NavigationSplitView` stecken, ohne etwas zu ändern. struct SidebarView: View { let libraries: [SidebarDestination] /// Ob der aktive Server Live-TV anbietet. /// /// Ein Eintrag, der zu einem leeren Bildschirm führt, ist schlimmer als /// keiner — und bei Jellyfin führt er dorthin, bis der Programmführer /// umgestellt ist. var showsLiveTV: Bool = true @Binding var selection: SidebarDestination /// Auf dem iPad trägt die Navigationsleiste den Namen bereits — dort wäre /// eine eigene Überschrift die dritte Nennung auf demselben Bildschirm. var showsHeader: Bool = true let onSelect: () -> Void var body: some View { ScrollView { VStack(alignment: .leading, spacing: FlixySpacing.section) { if showsHeader { header } section(title: nil, items: [.home]) if !libraries.isEmpty { section(title: String(localized: "sidebar.libraries"), items: libraries) } section( title: String(localized: "sidebar.more"), items: showsLiveTV ? [.liveTV, .downloads, .settings] : [.downloads, .settings] ) } .padding(.horizontal, FlixySpacing.regular) .padding(.vertical, FlixySpacing.section) } .scrollBounceBehavior(.basedOnSize) .background(Color.flixySurface) } private var header: some View { Text(Branding.appName) .font(.flixyTitle) .foregroundStyle(Color.flixyTextPrimary) .padding(.horizontal, FlixySpacing.snug) } @ViewBuilder private func section(title: String?, items: [SidebarDestination]) -> some View { VStack(alignment: .leading, spacing: FlixySpacing.tight) { if let title { Text(title) .font(.flixyCaption) .foregroundStyle(Color.flixyTextMuted) .textCase(.uppercase) .padding(.horizontal, FlixySpacing.snug) .padding(.bottom, FlixySpacing.tight) } ForEach(items, id: \.self) { item in SidebarRow(destination: item, isSelected: item == selection) { selection = item onSelect() } } } } } private struct SidebarRow: View { let destination: SidebarDestination let isSelected: Bool let action: () -> Void @Environment(\.flixyAccent) private var accent var body: some View { Button(action: action) { Label { Text(destination.sidebarLabel).font(.flixyBody) } icon: { Image(systemName: destination.symbol) // Feste Breite, damit die Beschriftungen auf einer Linie // stehen — SF-Symbole sind unterschiedlich breit. .frame(width: 22) } .foregroundStyle(isSelected ? accent : Color.flixyTextSecondary) .padding(.horizontal, FlixySpacing.snug) .padding(.vertical, FlixySpacing.regular) .frame(maxWidth: .infinity, alignment: .leading) .background { if isSelected { RoundedRectangle(cornerRadius: FlixyRadius.control) .fill(accent.opacity(0.14)) } } } .buttonStyle(.plain) .accessibilityAddTraits(isSelected ? [.isButton, .isSelected] : .isButton) } }