diff --git a/Yattee/Views/MediaBrowser/MediaSourcesView.swift b/Yattee/Views/MediaBrowser/MediaSourcesView.swift index 1f1c19f1..9737ee3f 100644 --- a/Yattee/Views/MediaBrowser/MediaSourcesView.swift +++ b/Yattee/Views/MediaBrowser/MediaSourcesView.swift @@ -670,11 +670,7 @@ struct MediaSourcesView: View { .font(.caption) .foregroundStyle(.secondary) - if needsCredentials(instance) { - Label(String(localized: "sources.status.authRequired"), systemImage: "key.fill") - .font(.caption2) - .foregroundStyle(.orange) - } + instanceStatusView(for: instance) } Spacer() @@ -688,6 +684,42 @@ struct MediaSourcesView: View { .contentShape(Rectangle()) } + /// What is wrong with this source, said once. + /// + /// A refused login comes first: it names one thing the user has to do, and + /// two warnings stacked on one row say less than either alone. Reading the + /// status here also puts this row inside the observation of + /// `AccountsManager.accountStatuses`, so a verdict arriving from the API + /// layer redraws it. + @ViewBuilder + private func instanceStatusView(for instance: Instance) -> some View { + if hasRejectedAccount(instance) { + Label( + AccountStatusBadge.text(for: .credentialRejected), + systemImage: "person.crop.circle.badge.exclamationmark" + ) + .font(.caption2) + .foregroundStyle(AccountStatusBadge.color(for: .credentialRejected)) + } else if needsCredentials(instance) { + Label(String(localized: "sources.status.authRequired"), systemImage: "key.fill") + .font(.caption2) + .foregroundStyle(.orange) + } + } + + /// Whether one of this instance's logins is being refused by the server. + /// + /// `needsCredentials` only knows whether a secret is *there*, so a + /// credential the server has started refusing looks perfectly healthy to + /// it — and this is the screen the user browses their sources from. + private func hasRejectedAccount(_ instance: Instance) -> Bool { + guard let accountsManager = appEnvironment?.accountsManager else { return false } + + return accountsManager.accounts(forInstance: instance.id).contains { + accountsManager.status(for: $0.id) == .credentialRejected + } + } + private func mediaSourceRow(_ source: MediaSource, needsPassword: Bool) -> some View { #if os(tvOS) let rowSpacing: CGFloat = 24