fix(app): signing out or deleting the account keeps the person's app until it is done

Both went through the launch screen (SessionStateLoading), which closed the
person's app before asking the instance; a failure then opened it again
with no word of why. The person's state stays now, saying what is under
way (`busy`), and a failure keeps them signed in with why (`problem`). An
instance out of reach still signs out on the device (Serverpod's
signOutDevice), as signing out of a borrowed device must work offline, so
only the device's own storage fails a sign-out; a deletion fails whenever
the instance isn't reached or refuses.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-21 19:44:43 +02:00
commit 662c30b117
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -126,28 +126,49 @@ class SessionBloc(final AuthRepository _auth, final SignedOutMark _mark)
Future<void> _onSignOutRequested(
SessionEventSignOutRequested event,
Emitter<SessionState> emit,
) async {
emit(const SessionStateLoading());
try {
// The status change to signed out then asks how to start.
await _auth.signOut();
} on Object catch (error, st) {
addError(error, st);
emit(_stateOf(_auth.status));
}
}
) => _onAccountAction(
SessionAction.signOut,
// The instance out of reach signs out on this device all the same
// (Serverpod's `signOutDevice`), as signing out of a borrowed device
// must work offline; only the device's own storage fails it.
_auth.signOut,
SessionProblem.signOutFailed,
emit,
);
Future<void> _onDeleteRequested(
SessionEventDeleteRequested event,
Emitter<SessionState> emit,
) => _onAccountAction(
SessionAction.delete,
_auth.deleteAccount,
SessionProblem.deleteFailed,
emit,
);
/// Runs [action] for the signed-in person's account, their app in place
/// meanwhile, [busy] saying what is under way: never through the launch
/// screen ([SessionStateLoading]), which would close the app and, should
/// [action] fail, open it again with no word of why. Done, the status
/// change to signed out asks how to start; failed, the person stays,
/// told so by [problem].
Future<void> _onAccountAction(
SessionAction busy,
Future<void> Function() action,
SessionProblem problem,
Emitter<SessionState> emit,
) async {
emit(const SessionStateLoading());
if (_auth.status case final SignedIn person) {
emit(SessionStateActive(person, busy: busy));
}
try {
// The status change to signed out then asks how to start.
await _auth.deleteAccount();
await action();
} on Object catch (error, st) {
addError(error, st);
emit(_stateOf(_auth.status));
emit(switch (_auth.status) {
final SignedIn person => SessionStateActive(person, problem: problem),
SignedOut() => const SessionStateSignedOut(),
});
}
}
@ -219,10 +240,40 @@ final class const SessionStateFailed() extends SessionState {
/// A person is signed in.
final class const SessionStateActive(
/// Who is signed in.
final SignedIn person,
) extends SessionState {
final SignedIn person, {
/// What is under way for the account (signing out, deleting it), while
/// the person's app stays; null for nothing.
final SessionAction? busy,
/// Why the last of those failed, the person still signed in; null for
/// none.
final SessionProblem? problem,
}) extends SessionState {
@override
List<Object?> get props => [person];
List<Object?> get props => [person, busy, problem];
}
/// What the person asked for their account, under way while their app
/// stays.
enum SessionAction() {
/// Signing out.
signOut,
/// Deleting the account.
delete,
}
/// Why signing out or deleting the account failed, the person still signed
/// in.
enum SessionProblem() {
/// Signing out failed on this device: the instance not reached signs out
/// here all the same, so only the device's own storage fails it.
signOutFailed,
/// Deleting the account failed, the instance not reached or refusing:
/// nothing deleted.
deleteFailed,
}
/// [person] signed in with GitHub just now: before their app, a password