Show missing-credentials indicator for remote server sources

After reinstalling the app and importing sources from iCloud, remote
server instances with Keychain-only credentials showed no indication
that login is needed, unlike WebDAV/SMB sources.

- Show the orange key icon for Yattee Server instances without stored
  basic auth credentials (auth is always required for this type)
- Persist usesBasicAuth/usesAccountLogin flags on Instance (synced via
  iCloud) so missing Invidious/Piped/PeerTube credentials are detectable
  too; maintained on credential save/delete, login/logout, and legacy
  migration
- Centralize the check in AppEnvironment.needsCredentials(for:) used by
  SourcesListView, MediaSourcesView, and SourceRow
- Backfill the flags at launch from credentials currently in the
  Keychain so existing setups publish them without a re-login
This commit is contained in:
Arkadiusz Fal
2026-07-23 18:19:38 +02:00
parent c3e39c955d
commit 6994b9353a
9 changed files with 132 additions and 7 deletions

View File

@@ -753,7 +753,8 @@ struct AddRemoteServerView: View {
type: type,
url: url,
name: name.isEmpty ? nil : name,
allowInvalidCertificates: allowInvalidCertificates
allowInvalidCertificates: allowInvalidCertificates,
usesBasicAuth: true
)
appEnvironment.basicAuthCredentialsManager.setCredentials(
@@ -783,14 +784,16 @@ struct AddRemoteServerView: View {
// For other instance types: optionally store HTTP Basic Auth credentials
// (used when the instance is fronted by a reverse proxy that requires basic auth).
let hasBasicAuth = !basicAuthUsername.isEmpty && !basicAuthPassword.isEmpty
let instance = Instance(
type: type,
url: url,
name: name.isEmpty ? nil : name,
allowInvalidCertificates: allowInvalidCertificates
allowInvalidCertificates: allowInvalidCertificates,
usesBasicAuth: hasBasicAuth
)
if !basicAuthUsername.isEmpty, !basicAuthPassword.isEmpty {
if hasBasicAuth {
appEnvironment.basicAuthCredentialsManager.setCredentials(
username: basicAuthUsername,
password: basicAuthPassword,

View File

@@ -345,6 +345,7 @@ private struct EditRemoteServerContent: View {
.fullScreenCover(isPresented: $showLoginSheet) {
InstanceLoginView(instance: instance) { credential in
appEnvironment?.credentialsManager(for: instance)?.setCredential(credential, for: instance)
appEnvironment?.instancesManager.setUsesAccountLogin(true, for: instance)
isLoggedIn = true
}
}
@@ -352,6 +353,7 @@ private struct EditRemoteServerContent: View {
.sheet(isPresented: $showLoginSheet) {
InstanceLoginView(instance: instance) { credential in
appEnvironment?.credentialsManager(for: instance)?.setCredential(credential, for: instance)
appEnvironment?.instancesManager.setUsesAccountLogin(true, for: instance)
isLoggedIn = true
}
}
@@ -409,6 +411,7 @@ private struct EditRemoteServerContent: View {
private func logout() {
appEnvironment?.credentialsManager(for: instance)?.deleteCredential(for: instance)
appEnvironment?.instancesManager.setUsesAccountLogin(false, for: instance)
isLoggedIn = false
}
@@ -445,7 +448,9 @@ private struct EditRemoteServerContent: View {
}
private func performSave() {
var updated = instance
// Start from the manager's latest copy so flags updated while this sheet
// was open (e.g. usesAccountLogin set by the login sheet) are preserved.
var updated = appEnvironment?.instancesManager.instances.first { $0.id == instance.id } ?? instance
updated.name = name.isEmpty ? nil : name
updated.isEnabled = isEnabled
updated.allowInvalidCertificates = allowInvalidCertificates
@@ -458,14 +463,17 @@ private struct EditRemoteServerContent: View {
if hadStoredBasicAuth {
appEnvironment?.basicAuthCredentialsManager.deleteCredentials(for: instance)
}
updated.usesBasicAuth = false
} else if !basicAuthUsername.isEmpty, !basicAuthPassword.isEmpty {
appEnvironment?.basicAuthCredentialsManager.setCredentials(
username: basicAuthUsername,
password: basicAuthPassword,
for: instance
)
updated.usesBasicAuth = true
} else if hadStoredBasicAuth, instance.type != .yatteeServer {
appEnvironment?.basicAuthCredentialsManager.deleteCredentials(for: instance)
updated.usesBasicAuth = false
}
appEnvironment?.instancesManager.update(updated)

View File

@@ -32,6 +32,13 @@ struct SourceRow: View {
return status == .authFailed || status == .authRequired
}
/// Whether a remote server instance requires credentials that are missing from
/// the Keychain (e.g. after reinstalling and importing sources from iCloud).
private var needsCredentials: Bool {
guard case .remoteServer(let instance) = source else { return false }
return appEnvironment?.needsCredentials(for: instance) ?? false
}
var body: some View {
#if os(tvOS)
Button(action: onEdit) {
@@ -92,7 +99,7 @@ struct SourceRow: View {
@ViewBuilder
private var statusView: some View {
if needsPassword {
if needsPassword || needsCredentials {
Label(String(localized: "sources.status.authRequired"), systemImage: "key.fill")
.font(.caption2)
.foregroundStyle(.orange)

View File

@@ -403,9 +403,19 @@ struct SourcesListView: View {
.contentShape(Rectangle())
}
/// Whether a remote server instance requires credentials that are missing from
/// the Keychain (e.g. after reinstalling and importing sources from iCloud).
private func needsCredentials(_ instance: Instance) -> Bool {
appEnvironment?.needsCredentials(for: instance) ?? false
}
@ViewBuilder
private func instanceStatusView(for instance: Instance) -> some View {
if let status = instancesManager?.status(for: instance) {
if needsCredentials(instance) {
Label(String(localized: "sources.status.authRequired"), systemImage: "key.fill")
.font(.caption2)
.foregroundStyle(.orange)
} else if let status = instancesManager?.status(for: instance) {
switch status {
case .authFailed:
Label(String(localized: "sources.status.authFailed"), systemImage: "exclamationmark.triangle.fill")