fixed guardian login edge cases and misleading placeholders on failed loads
This commit is contained in:
@@ -54,9 +54,14 @@ class GuardianLoginController extends ChangeNotifier {
|
||||
String? get errorMessage => _errorMessage;
|
||||
String? get errorDetails => _errorDetails;
|
||||
|
||||
bool canResend() {
|
||||
bool canResend() => resendCooldown() == Duration.zero;
|
||||
|
||||
/// Restzeit, bis [resend] wieder erlaubt ist.
|
||||
Duration resendCooldown() {
|
||||
final pending = _pending;
|
||||
return pending != null && !_now().isBefore(pending.resendAvailableAt);
|
||||
if (pending == null) return Duration.zero;
|
||||
final remaining = pending.resendAvailableAt.difference(_now());
|
||||
return remaining.isNegative ? Duration.zero : remaining;
|
||||
}
|
||||
|
||||
/// Picks up a request started before the app was closed.
|
||||
@@ -77,8 +82,12 @@ class GuardianLoginController extends ChangeNotifier {
|
||||
Future<bool> requestCode(String email) async {
|
||||
final normalized = email.trim().toLowerCase();
|
||||
if (DemoMode.matchesGuardian(normalized)) {
|
||||
await _signIn(GuardianSession(email: normalized, isDemo: true));
|
||||
return true;
|
||||
var signedIn = false;
|
||||
await _run(() async {
|
||||
await _signIn(GuardianSession(email: normalized, isDemo: true));
|
||||
signedIn = true;
|
||||
});
|
||||
return signedIn;
|
||||
}
|
||||
await _run(() async {
|
||||
final secret = DeviceBinding.generateSecret();
|
||||
@@ -128,6 +137,17 @@ class GuardianLoginController extends ChangeNotifier {
|
||||
return _complete(linkToken: link.linkToken);
|
||||
}
|
||||
|
||||
/// A mail link that does not belong to the server the app talks to (a live
|
||||
/// link while the app points at beta). Without this the tap would do nothing
|
||||
/// at all.
|
||||
void rejectForeignLink() {
|
||||
_errorMessage =
|
||||
'Dieser Anmeldelink gehört zu einem anderen Server als dem, mit dem '
|
||||
'die App gerade verbunden ist. Bitte gib den Code aus der E-Mail ein.';
|
||||
_errorDetails = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Abandons the running request, e.g. to correct a mistyped address.
|
||||
Future<void> changeEmail() async {
|
||||
await _store.clear();
|
||||
@@ -140,7 +160,15 @@ class GuardianLoginController extends ChangeNotifier {
|
||||
|
||||
Future<bool> _complete({String? code, String? linkToken}) async {
|
||||
final pending = _pending;
|
||||
if (pending == null) return false;
|
||||
if (pending == null) {
|
||||
_errorMessage = GuardianLoginException.messageFor(
|
||||
GuardianLoginError.requestExpired,
|
||||
);
|
||||
_errorDetails = null;
|
||||
_step = GuardianLoginStep.enterEmail;
|
||||
notifyListeners();
|
||||
return false;
|
||||
}
|
||||
var signedIn = false;
|
||||
await _run(() async {
|
||||
await _verify.run(
|
||||
@@ -188,9 +216,16 @@ class GuardianLoginController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
static Future<void> _defaultSignIn(Session session) async {
|
||||
// Drop any widget snapshot of a previous account before the new one loads.
|
||||
await WidgetSync.clear();
|
||||
await WidgetSync.triggerUpdate();
|
||||
// Sign in first: the one-time code is already spent at this point, so a
|
||||
// failing widget reset must not cost the session (the user would have to
|
||||
// request a fresh mail for a login that actually succeeded).
|
||||
await SessionManager().signIn(session);
|
||||
// Drop any widget snapshot of a previous account.
|
||||
try {
|
||||
await WidgetSync.clear();
|
||||
await WidgetSync.triggerUpdate();
|
||||
} on Object catch (e) {
|
||||
log('Guardian login: widget reset failed: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import '../../api/marianumconnect/marianumconnect_endpoint.dart' as mc;
|
||||
import '../../auth_link/guardian_link_listener.dart';
|
||||
import '../../auth_link/guardian_login_link.dart';
|
||||
import '../../background/widget_background_task.dart';
|
||||
import '../../session/session_lifecycle.dart';
|
||||
import '../../state/app/modules/account/bloc/account_bloc.dart';
|
||||
import '../../state/app/modules/account/bloc/account_state.dart';
|
||||
import '../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||
@@ -14,6 +15,7 @@ import '../../storage/dev_tools_settings.dart';
|
||||
import '../../storage/settings.dart' as model;
|
||||
import '../../theming/light_app_theme.dart';
|
||||
import '../../utils/haptics.dart';
|
||||
import '../../widget/info_dialog.dart';
|
||||
import '../pages/settings/widgets/endpoint_picker.dart';
|
||||
import 'guardian_login_controller.dart';
|
||||
import 'login_controller.dart';
|
||||
@@ -54,6 +56,16 @@ class _LoginState extends State<Login> with SingleTickerProviderStateMixin {
|
||||
});
|
||||
GuardianLinkListener.pending.addListener(_consumeGuardianLink);
|
||||
_consumeGuardianLink();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _showSignOutNotice());
|
||||
}
|
||||
|
||||
/// An involuntary sign-out (expired token, rotated password) otherwise just
|
||||
/// drops the user here without a word.
|
||||
void _showSignOutNotice() {
|
||||
final notice = SessionLifecycle.signOutNotice.value;
|
||||
if (notice == null || !mounted) return;
|
||||
SessionLifecycle.signOutNotice.value = null;
|
||||
InfoDialog.show(context, notice, title: 'Erneut anmelden');
|
||||
}
|
||||
|
||||
/// A tapped mail link finishes the guardian login without typing the code.
|
||||
@@ -65,10 +77,13 @@ class _LoginState extends State<Login> with SingleTickerProviderStateMixin {
|
||||
uri,
|
||||
apiBase: Uri.parse(mc.MarianumConnectEndpoint.current()),
|
||||
);
|
||||
if (link == null) return;
|
||||
await _guardianRestored;
|
||||
if (!mounted) return;
|
||||
setState(() => _audience = LoginAudience.guardian);
|
||||
if (link == null) {
|
||||
_guardianController.rejectForeignLink();
|
||||
return;
|
||||
}
|
||||
final signedIn = await _guardianController.submitLink(link);
|
||||
if (signedIn && mounted) _onLoginSuccess();
|
||||
}
|
||||
|
||||
@@ -158,7 +158,7 @@ class _GuardianLoginCardState extends State<GuardianLoginCard> {
|
||||
Widget _buildCodeStep(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final pending = _controller.pending!;
|
||||
final remaining = pending.resendAvailableAt.difference(DateTime.now());
|
||||
final remaining = _controller.resendCooldown();
|
||||
return Form(
|
||||
key: _codeFormKey,
|
||||
child: LoginCardFrame(
|
||||
|
||||
@@ -14,7 +14,7 @@ class LoginAudienceCard extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) => LoginCardFrame(
|
||||
title: 'Anmelden',
|
||||
hint: 'Bite wähle deine Anmeldemethode',
|
||||
hint: 'Bitte wähle deine Anmeldemethode',
|
||||
children: [
|
||||
_AudienceButton(
|
||||
key: const Key('login-audience-school'),
|
||||
|
||||
Reference in New Issue
Block a user