import SwiftUI import FlixyDesign import MediaKit import PlexKit /// Mit welchem Server gesprochen wird — und was er gerade anbietet. struct ServerSettingsView: View { @Environment(FlixySession.self) private var session @Environment(\.flixyAccent) private var accent var body: some View { Form { Section("settings.server") { LabeledContent("settings.serverName", value: session.serverName) // Welche Art von Server — bei zwei möglichen ist das keine // Nebensache mehr, sondern die erste Frage bei jeder // Rückmeldung. LabeledContent("settings.serverKind", value: kindLabel) if let connection = session.server?.connection { LabeledContent("settings.connection", value: connectionLabel(connection)) } if let address = session.serverAddress { LabeledContent("settings.address", value: address) .font(.flixyCaption) .textSelection(.enabled) } if let accountName = session.accountName { LabeledContent("settings.account", value: accountName) } } Section { ForEach(session.libraries) { library in LibraryRow(library: library) } } header: { Text("settings.libraries") } footer: { // Der Suchlauf stand einmal im Dreipunktmenü der Mediathek — // einen Tipp neben „Filter" und „Ansicht wechseln". Er // beschäftigt den Server minutenlang und gehört nicht in eine // Reihe mit Dingen, die man im Vorbeigehen antippt. if session.backend?.capabilities.contains(.libraryScan) == true { Text("settings.scanExplanation") } } } .tint(accent) .flixyForm() .scrollContentBackground(.hidden) .background(Color.flixyCanvas) .navigationTitle(Text("settings.server")) .flixyInlineTitle() } private var kindLabel: String { switch session.kind { case .plex: "Plex" case .jellyfin: "Jellyfin" case nil: "—" } } /// Zeigt, über welchen Weg die Verbindung läuft — beim Suchen nach /// Ruckeln ist das die erste Frage. private func connectionLabel(_ connection: PlexConnection) -> String { if connection.local { return String(localized: "settings.connectionLocal") } if connection.relay { return String(localized: "settings.connectionRelay") } return String(localized: "settings.connectionRemote") } } /// Eine Mediathek in den Einstellungen, mit dem Suchlauf daneben. struct LibraryRow: View { let library: MediaLibrary @Environment(FlixySession.self) private var session @Environment(\.flixyAccent) private var accent @State private var phase: Phase = .idle private enum Phase { case idle, running, done } var body: some View { LabeledContent { HStack(spacing: FlixySpacing.regular) { // Nur, wenn es nicht dasselbe Wort ist: eine Mediathek namens // „Filme" stand sonst als „Filme — Filme" da. if library.title.localizedCaseInsensitiveCompare(library.kind.label) != .orderedSame { Text(library.kind.label) .foregroundStyle(Color.flixyTextSecondary) } if session.backend?.capabilities.contains(.libraryScan) == true { Button { Task { await scan() } } label: { symbol } .buttonStyle(.borderless) .disabled(phase == .running) .accessibilityLabel(Text("library.scan")) } } } label: { Text(library.title) } } @ViewBuilder private var symbol: some View { switch phase { case .idle: Image(systemName: "arrow.triangle.2.circlepath") case .running: ProgressView().controlSize(.small) case .done: Image(systemName: "checkmark").foregroundStyle(accent) } } /// Stößt einen Suchlauf auf dem Server an. /// /// Der Server meldet nicht, wann er fertig ist. Deshalb steht hier kein /// Fortschritt, sondern nur ein Haken dafür, dass der Auftrag angekommen /// ist — ein Balken, der etwas anderes behauptet, wäre erfunden. private func scan() async { guard let backend = session.backend, phase != .running else { return } phase = .running try? await backend.refreshLibrary(library) phase = .done try? await Task.sleep(for: .seconds(4)) phase = .idle } } extension MediaLibrary.Kind { /// Wie diese Art von Mediathek heißt. /// /// Übersetzt statt durchgereicht: die Server liefern „movies" und /// „tvshows", und das großgeschrieben anzuzeigen war eine englische /// Beschriftung in einer deutschen Oberfläche. var label: String { switch self { case .movie: String(localized: "library.kind.movie") case .show: String(localized: "library.kind.show") case .music: String(localized: "library.kind.music") case .photo: String(localized: "library.kind.photo") case .liveTV: String(localized: "library.kind.liveTV") case .other: String(localized: "library.kind.other") } } }