From 5c55654e397ec0f15139dc862009b563aeecb7a5 Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Wed, 25 Mar 2026 21:26:52 +1100 Subject: [PATCH 1/4] Align STAY SIGNED IN and TRY ANOTHER WEBID in a Row() --- lib/src/widgets/solid_login_panel.dart | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/src/widgets/solid_login_panel.dart b/lib/src/widgets/solid_login_panel.dart index 8797986..2395f32 100644 --- a/lib/src/widgets/solid_login_panel.dart +++ b/lib/src/widgets/solid_login_panel.dart @@ -124,11 +124,21 @@ class SolidLoginPanel { ], ), - if (staySignedInCheckbox != null) staySignedInCheckbox, - if (tryAnotherAccountButton != null) ...[ - const SizedBox(height: 4.0), + if (staySignedInCheckbox != null) ...[ + if (tryAnotherAccountButton != null) + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + staySignedInCheckbox, + const SizedBox(width: 16.0), + tryAnotherAccountButton, + ], + ) + else + staySignedInCheckbox, + ] else if (tryAnotherAccountButton != null) tryAnotherAccountButton, - ], const SizedBox(height: 20.0), From ccf0fb1b5bf7b18618ef42547a4870badab6951c Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Wed, 25 Mar 2026 22:28:01 +1100 Subject: [PATCH 2/4] Optimise the workflow --- lib/src/widgets/solid_login.dart | 40 +++++++++++++++-- lib/src/widgets/solid_login_auth_handler.dart | 43 ++++++++++++++----- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/lib/src/widgets/solid_login.dart b/lib/src/widgets/solid_login.dart index c13ab3d..f39496b 100644 --- a/lib/src/widgets/solid_login.dart +++ b/lib/src/widgets/solid_login.dart @@ -208,6 +208,10 @@ class _SolidLoginState extends State with WidgetsBindingObserver { _infoFocusNode = FocusNode(debugLabel: 'infoButton'); _serverInputFocusNode = FocusNode(debugLabel: 'serverInput'); + // Restore the persisted "Stay signed in" preference. + + _loadStaySignedInPreference(); + // Resolve image assets with fallback logic. _resolveImageAssets(); @@ -253,6 +257,13 @@ class _SolidLoginState extends State with WidgetsBindingObserver { if (mounted) setState(() => _assetsResolved = true); } + /// Loads the persisted "Stay signed in" preference from SharedPreferences. + + Future _loadStaySignedInPreference() async { + final value = await SolidLoginAuthHandler.getStaySignedIn(); + if (mounted) setState(() => _staySignedIn = value); + } + @override void dispose() { WidgetsBinding.instance.removeObserver(this); @@ -370,6 +381,14 @@ class _SolidLoginState extends State with WidgetsBindingObserver { // - No cache → start the browser login flow as normal. Future performLogin() async { + // When the user has opted out of staying signed in, discard any + // existing cached session immediately so browser authentication + // is always required. + + if (!_staySignedIn) { + await deleteLogIn(); + } + final podServer = webIdController.text.trim().isNotEmpty ? webIdController.text.trim() : SolidConfig.defaultServerUrl; @@ -415,6 +434,14 @@ class _SolidLoginState extends State with WidgetsBindingObserver { // re-login so the setup wizard can re-initialise the POD. Future performContinue() async { + // When the user has opted out of staying signed in, discard any + // existing cached session immediately so the user proceeds in a + // logged-out state. + + if (!_staySignedIn) { + await deleteLogIn(); + } + final isLoggedIn = await isUserLoggedIn(); if (isLoggedIn && defaultFolders.isNotEmpty) { @@ -529,14 +556,21 @@ class _SolidLoginState extends State with WidgetsBindingObserver { height: 24, child: Checkbox( value: _staySignedIn, - onChanged: (value) => - setState(() => _staySignedIn = value ?? true), + onChanged: (value) { + final newValue = value ?? true; + setState(() => _staySignedIn = newValue); + SolidLoginAuthHandler.setStaySignedIn(newValue); + }, ), ), ), const SizedBox(width: 8), GestureDetector( - onTap: () => setState(() => _staySignedIn = !_staySignedIn), + onTap: () { + final newValue = !_staySignedIn; + setState(() => _staySignedIn = newValue); + SolidLoginAuthHandler.setStaySignedIn(newValue); + }, child: Text( 'Stay signed in', style: TextStyle(color: currentTheme.textColor, fontSize: 14), diff --git a/lib/src/widgets/solid_login_auth_handler.dart b/lib/src/widgets/solid_login_auth_handler.dart index 14194eb..c5de3ea 100644 --- a/lib/src/widgets/solid_login_auth_handler.dart +++ b/lib/src/widgets/solid_login_auth_handler.dart @@ -56,6 +56,10 @@ class SolidLoginAuthHandler { static const clearSessionKey = 'solidui_clear_session_on_startup'; + /// SharedPreferences key for persisting the "Stay signed in" preference. + + static const staySignedInKey = 'solidui_stay_signed_in'; + /// Clears the cached login session if a previous session opted out of /// "Stay signed in". Call this early during login page initialisation. @@ -68,6 +72,21 @@ class SolidLoginAuthHandler { } } + /// Returns the persisted "Stay signed in" preference, defaulting to true. + + static Future getStaySignedIn() async { + final prefs = await SharedPreferences.getInstance(); + + return prefs.getBool(staySignedInKey) ?? true; + } + + /// Persists the "Stay signed in" preference. + + static Future setStaySignedIn(bool value) async { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(staySignedInKey, value); + } + /// Notifies the user that their POD is not initialised, verifies the remote /// directory structure, and navigates to the appropriate screen (setup wizard /// or child widget). @@ -95,6 +114,13 @@ class SolidLoginAuthHandler { if (!context.mounted) return false; + // When the user has opted out of staying signed in, clear the + // session cache immediately rather than deferring to next startup. + + if (!staySignedIn) { + await deleteLogIn(); + } + if (!allExists) { await clearPodStructureInitialised(); if (!context.mounted) return false; @@ -113,11 +139,6 @@ class SolidLoginAuthHandler { await pushReplacement(context, childWidget); } - if (!staySignedIn) { - final prefs = await SharedPreferences.getInstance(); - await prefs.setBool(clearSessionKey, true); - } - return true; } @@ -279,6 +300,13 @@ class SolidLoginAuthHandler { if (!context.mounted) return false; + // When the user has opted out of staying signed in, clear the + // session cache immediately rather than deferring to next startup. + + if (!staySignedIn) { + await deleteLogIn(); + } + if (!allExists) { // Remote structure is incomplete — clear the stale local flag and // launch the setup wizard so the user can re-initialise. @@ -301,11 +329,6 @@ class SolidLoginAuthHandler { await pushReplacement(context, childWidget); } - if (!staySignedIn) { - final prefs = await SharedPreferences.getInstance(); - await prefs.setBool(clearSessionKey, true); - } - return true; } else { // solidAuthenticate() returned null. This can happen when: From 32776534337dc3b5e1695747a4fb968a6d546662 Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Thu, 26 Mar 2026 13:32:51 +1100 Subject: [PATCH 3/4] Fix the NotLoggedInException exception --- lib/src/widgets/solid_login_auth_handler.dart | 53 ++++++++++++++----- lib/src/widgets/solid_scaffold_state.dart | 10 +++- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/lib/src/widgets/solid_login_auth_handler.dart b/lib/src/widgets/solid_login_auth_handler.dart index c5de3ea..341e10c 100644 --- a/lib/src/widgets/solid_login_auth_handler.dart +++ b/lib/src/widgets/solid_login_auth_handler.dart @@ -114,15 +114,18 @@ class SolidLoginAuthHandler { if (!context.mounted) return false; - // When the user has opted out of staying signed in, clear the - // session cache immediately rather than deferring to next startup. - - if (!staySignedIn) { - await deleteLogIn(); - } - if (!allExists) { await clearPodStructureInitialised(); + + // Schedule session clearance for next startup when the user has + // opted out of staying signed in. We must not call deleteLogIn() + // here because InitialSetupScreen still needs valid auth data. + + if (!staySignedIn) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(clearSessionKey, true); + } + if (!context.mounted) return false; await pushReplacement( @@ -135,6 +138,16 @@ class SolidLoginAuthHandler { ); } else { await markPodStructureInitialised(); + + // Schedule session clearance for next startup when the user has + // opted out of staying signed in. deleteLogIn() must come after + // markPodStructureInitialised() which requires valid auth data. + + if (!staySignedIn) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(clearSessionKey, true); + } + if (!context.mounted) return false; await pushReplacement(context, childWidget); } @@ -300,19 +313,21 @@ class SolidLoginAuthHandler { if (!context.mounted) return false; - // When the user has opted out of staying signed in, clear the - // session cache immediately rather than deferring to next startup. - - if (!staySignedIn) { - await deleteLogIn(); - } - if (!allExists) { // Remote structure is incomplete — clear the stale local flag and // launch the setup wizard so the user can re-initialise. await clearPodStructureInitialised(); + // Schedule session clearance for next startup when the user has + // opted out of staying signed in. We must not call deleteLogIn() + // here because InitialSetupScreen still needs valid auth data. + + if (!staySignedIn) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(clearSessionKey, true); + } + if (!context.mounted) return false; await pushReplacement( @@ -325,6 +340,16 @@ class SolidLoginAuthHandler { ); } else { await markPodStructureInitialised(); + + // Schedule session clearance for next startup when the user has + // opted out of staying signed in. deleteLogIn() must come after + // markPodStructureInitialised() which requires valid auth data. + + if (!staySignedIn) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setBool(clearSessionKey, true); + } + if (!context.mounted) return false; await pushReplacement(context, childWidget); } diff --git a/lib/src/widgets/solid_scaffold_state.dart b/lib/src/widgets/solid_scaffold_state.dart index c69323a..60b113b 100644 --- a/lib/src/widgets/solid_scaffold_state.dart +++ b/lib/src/widgets/solid_scaffold_state.dart @@ -114,7 +114,15 @@ class SolidScaffoldState extends State { super.dispose(); } - void _onPreferencesChanged() => mounted ? setState(() {}) : null; + // Deferred to avoid triggering setState while the framework is building + // widgets (e.g. when initializeIfNeeded updates the notifier during build). + + void _onPreferencesChanged() { + if (!mounted) return; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted) setState(() {}); + }); + } void _onControllerChanged() => mounted ? setState(() {}) : null; void _onThemeChanged() => mounted ? setState(() {}) : null; From 0b5564555eefa2e501bf4b8a8d7ac1260547eda8 Mon Sep 17 00:00:00 2001 From: Tony Chen Date: Thu, 26 Mar 2026 13:33:32 +1100 Subject: [PATCH 4/4] Lint --- lib/src/widgets/solid_scaffold_state.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/widgets/solid_scaffold_state.dart b/lib/src/widgets/solid_scaffold_state.dart index 60b113b..1203dd2 100644 --- a/lib/src/widgets/solid_scaffold_state.dart +++ b/lib/src/widgets/solid_scaffold_state.dart @@ -123,6 +123,7 @@ class SolidScaffoldState extends State { if (mounted) setState(() {}); }); } + void _onControllerChanged() => mounted ? setState(() {}) : null; void _onThemeChanged() => mounted ? setState(() {}) : null;