Give every login a screen, including the ones with nowhere to go

An account whose source is gone was listed nowhere: `orphanedAccounts` had
no readers, and the credential behind it could not be reached at all —
`AppEnvironment.accountCredentialsManager(for:)` resolves the store from
the account's instance, and an orphan has none. The new accounts screen
lists them and removes them by account ID, asking every account-keyed
store because the backend is no longer knowable. The pre-revamp
instance-keyed item is deliberately left behind: identifying it needs the
instance too.

Deleting a source has always signed out and removed the accounts on it.
Both confirmations now say so, and name them.

The sign-in that files a credential against an account is now one
implementation instead of a private method on the source editor, so the
accounts screen and the new optional sign-in step after adding a source
attach a login exactly as the editor does. Extracted verbatim — the
identity-scope guard, the account re-resolve, the write-then-read-back
and the rollback are what keep a half-written login from looking real.

Signing out from the per-instance section now asks first, and both
sign-out paths run one cascade rather than two copies of it.
This commit is contained in:
Arkadiusz Fal
2026-09-04 23:35:51 +02:00
parent 0c5745e4fb
commit 69179c9fe9
15 changed files with 1249 additions and 147 deletions

View File

@@ -68,6 +68,13 @@ struct AddRemoteServerView: View {
@State private var showingYatteeServerWarning = false
@State private var pendingYatteeServerInstance: Instance?
/// The instance whose optional sign-in step is on screen.
///
/// The source is already added by then, so dismissing this sheet — by
/// signing in, by skipping, or by pulling it down — is what finishes
/// adding. Only instances that have accounts at all get this far.
@State private var signInInstance: Instance?
// Closure to dismiss the parent sheet
var dismissSheet: DismissAction?
@@ -131,6 +138,9 @@ struct AddRemoteServerView: View {
Text(String(localized: "sources.yatteeServer.warning.message"))
}
.presentationCompactAdaptation(.sheet)
.fullScreenCover(item: $signInInstance, onDismiss: finishAdding) { instance in
signInStep(for: instance)
}
#else
formContent
.navigationTitle(String(localized: "sources.addRemoteServer"))
@@ -147,20 +157,30 @@ struct AddRemoteServerView: View {
Text(String(localized: "sources.yatteeServer.warning.message"))
}
.presentationCompactAdaptation(.sheet)
.sheet(item: $signInInstance, onDismiss: finishAdding) { instance in
signInStep(for: instance)
}
#endif
}
/// The optional login offered right after a source that supports accounts
/// is added, so the common case does not need a second trip through
/// settings to become useful.
private func signInStep(for instance: Instance) -> some View {
InstanceLoginView(instance: instance, isOptionalStep: true) { credential, username in
guard let appEnvironment else { return false }
return AccountSignInHandler(appEnvironment: appEnvironment, instance: instance)
.storeLogin(credential: credential, username: username, target: .newAccount)
}
}
@ViewBuilder
private var yatteeServerWarningButtons: some View {
Button(String(localized: "sources.yatteeServer.warning.disableOthers"), role: .destructive) {
if let instance = pendingYatteeServerInstance {
appEnvironment?.instancesManager.disableOtherYatteeServerInstances(except: instance.id)
appEnvironment?.instancesManager.add(instance)
if let dismissSheet {
dismissSheet()
} else {
dismiss()
}
finishAdding()
}
}
Button(String(localized: "common.cancel"), role: .cancel) {
@@ -768,11 +788,7 @@ struct AddRemoteServerView: View {
showingYatteeServerWarning = true
} else {
appEnvironment.instancesManager.add(instance)
if let dismissSheet {
dismissSheet()
} else {
dismiss()
}
finishAdding()
}
} else {
credentialValidationError = String(localized: "sources.error.invalidCredentials")
@@ -802,6 +818,20 @@ struct AddRemoteServerView: View {
}
appEnvironment.instancesManager.add(instance)
// A source that can hold accounts is worth offering a login for while
// the user is still here. Everything else is done.
if instance.supportsAuthentication {
signInInstance = instance
} else {
finishAdding()
}
}
/// Closes the add flow. Reached directly for sources with no login step,
/// and as the sign-in sheet's dismissal for the ones that have it —
/// signing in and skipping end the same way.
private func finishAdding() {
if let dismissSheet {
dismissSheet()
} else {