Files
yattee/Yattee/Views/Components/DataContextUnavailableBanner.swift
Arkadiusz Fal 175b98097a Put the account switcher where the data is, and retire the old setting
Switching accounts was buried in Subscriptions settings behind a picker
and a confirmation dialog, and it wrote two stores that could disagree.
One shared switcher now sits in the Subscriptions and Playlists
toolbars, the macOS sidebar footer and the Settings root, and every path
goes through AppEnvironment.switchDataContext(to:) so a switch behaves
the same wherever it is made. Switching is one tap: it is reversible, so
there is nothing to confirm.

Rows come from DataContextValidator.availableContexts, name themselves
through displayName(for:), and carry a status suffix when the account is
not signed in — plus the instance host when two accounts share a name.
tvOS gets the same rows as a full-screen picker.

Subscriptions also explains itself when the selected account cannot
answer: a thin banner names the cause and says the data below is local.
This is the first consumer of the validator's unavailability, and
correctIfContextRemoved() now runs on foreground activation, where
SubscriptionAccountValidator used to auto-correct any selection it could
not immediately verify.

With the switcher reading the context directly, the legacy
subscriptionAccount setting has no readers left: the setting, its
validator, the mirror that kept it in step and the feed cache's
instance-keyed fallbacks all go. The cache invalidation the mirror also
carried stays, under its own name. AccountsManager still reads the raw
legacy key for the one-way context migration.
2026-09-03 23:59:32 +02:00

70 lines
2.3 KiB
Swift

//
// DataContextUnavailableBanner.swift
// Yattee
//
// Says why the selected account is not the one answering right now.
//
import SwiftUI
/// A thin strip shown while the selected account context cannot be used.
///
/// Informational only: the selection is deliberately kept — it comes back on
/// its own when the credential or instance arrives — and reads fall back to
/// local data meanwhile, which is exactly what the user would otherwise have to
/// guess at. The switcher sits in the toolbar beside it for anyone who wants to
/// choose something else.
struct DataContextUnavailableBanner: View {
let reason: DataContextUnavailability
var body: some View {
HStack(spacing: 8) {
Image(systemName: icon)
.foregroundStyle(tint)
Text(message)
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
Spacer(minLength: 0)
}
.padding(.horizontal, 16)
.padding(.vertical, 8)
.frame(maxWidth: .infinity, alignment: .leading)
.background(.thinMaterial)
}
private var icon: String {
switch reason {
case .checking: "clock"
case .instancePending: "clock"
case .instanceDisabled: "server.rack"
case .credentialMissing: "person.badge.key"
case .credentialRejected: "exclamationmark.triangle.fill"
case .unreachable: "wifi.exclamationmark"
}
}
private var tint: Color {
switch reason {
case .checking, .instancePending: .secondary
case .instanceDisabled: .secondary
case .credentialMissing: .secondary
case .credentialRejected: .red
case .unreachable: .orange
}
}
private var message: String {
switch reason {
case .checking: String(localized: "subscriptions.banner.checking")
case .instancePending: String(localized: "subscriptions.banner.instancePending")
case .instanceDisabled: String(localized: "subscriptions.banner.instanceDisabled")
case .credentialMissing: String(localized: "subscriptions.banner.credentialMissing")
case .credentialRejected: String(localized: "subscriptions.banner.credentialRejected")
case .unreachable: String(localized: "subscriptions.banner.unreachable")
}
}
}