import SwiftUI import FlixyDesign import PlexKit /// Der Anmeldebildschirm. /// /// Ein Knopf, mehr nicht. Die Anmeldemaske kommt von Plex und öffnet sich in /// einem Fenster innerhalb der App; das Passwort wird dort eingegeben und /// erreicht Flixy nie. Nach dem Anmelden schließt sich das Fenster von selbst. struct SignInView: View { let phase: FlixySession.Phase /// Der Code der laufenden Anmeldung, sobald er gezeigt werden soll. let manualCode: String? let onSignIn: () -> Void let onCancel: () -> Void @Environment(\.flixyAccent) private var accent private var isAuthenticating: Bool { if case .authenticating = phase { return true } return false } var body: some View { VStack(spacing: FlixySpacing.section) { Spacer() VStack(spacing: FlixySpacing.regular) { Image(systemName: "play.rectangle.on.rectangle") .font(.system(size: 52)) .foregroundStyle(accent) Text(Branding.appName) .font(.flixyHero) .foregroundStyle(Color.flixyTextPrimary) Text("signIn.tagline") .font(.flixyBody) .foregroundStyle(Color.flixyTextSecondary) .multilineTextAlignment(.center) } Spacer() VStack(spacing: FlixySpacing.regular) { Button(action: onSignIn) { Group { if isAuthenticating { ProgressView().tint(Color.flixyCanvas) } else { Text("signIn.action").font(.flixyRowHeader) } } .frame(maxWidth: .infinity) .padding(.vertical, FlixySpacing.regular) } .buttonStyle(.borderedProminent) .tint(accent) .disabled(isAuthenticating) if let manualCode { manualFallback(code: manualCode) } else { Text("signIn.privacy") .font(.flixyCaption) .foregroundStyle(Color.flixyTextMuted) .multilineTextAlignment(.center) } } Spacer().frame(height: FlixySpacing.section) } .padding(.horizontal, FlixySpacing.section) .frame(maxWidth: 420) .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.flixyCanvas) } /// Der Ausweg, wenn die Browser-Seite den Code nicht verknüpft. /// /// Das passiert, wenn man dort schon bei Plex angemeldet ist: die Seite /// springt weiter, ohne zu verknüpfen, und die App wartete vergeblich. /// Der Code gilt weiter — abtippen führt immer zum Ziel. @ViewBuilder private func manualFallback(code: String) -> some View { VStack(spacing: FlixySpacing.regular) { Divider().overlay(Color.flixySeparator) Text("signIn.manualHint") .font(.flixyCaption) .foregroundStyle(Color.flixyTextSecondary) .multilineTextAlignment(.center) Text(code) .font(.system(.title, design: .monospaced, weight: .semibold)) .tracking(6) .foregroundStyle(Color.flixyTextPrimary) .textSelection(.enabled) .padding(.vertical, FlixySpacing.snug) .frame(maxWidth: .infinity) .background(Color.flixyElevated, in: .rect(cornerRadius: FlixyRadius.control)) Button("action.cancel", action: onCancel) .font(.flixyCaption) .foregroundStyle(Color.flixyTextSecondary) } .transition(.opacity) } }