Skip to content

Commit 6998418

Browse files
authored
Merge pull request #260 from anusii/tony/259_stay_logged_in
Tony/259 stay logged in
2 parents a0e8c0b + 0b55645 commit 6998418

4 files changed

Lines changed: 119 additions & 18 deletions

File tree

lib/src/widgets/solid_login.dart

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
208208
_infoFocusNode = FocusNode(debugLabel: 'infoButton');
209209
_serverInputFocusNode = FocusNode(debugLabel: 'serverInput');
210210

211+
// Restore the persisted "Stay signed in" preference.
212+
213+
_loadStaySignedInPreference();
214+
211215
// Resolve image assets with fallback logic.
212216

213217
_resolveImageAssets();
@@ -253,6 +257,13 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
253257
if (mounted) setState(() => _assetsResolved = true);
254258
}
255259

260+
/// Loads the persisted "Stay signed in" preference from SharedPreferences.
261+
262+
Future<void> _loadStaySignedInPreference() async {
263+
final value = await SolidLoginAuthHandler.getStaySignedIn();
264+
if (mounted) setState(() => _staySignedIn = value);
265+
}
266+
256267
@override
257268
void dispose() {
258269
WidgetsBinding.instance.removeObserver(this);
@@ -370,6 +381,14 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
370381
// - No cache → start the browser login flow as normal.
371382

372383
Future<void> performLogin() async {
384+
// When the user has opted out of staying signed in, discard any
385+
// existing cached session immediately so browser authentication
386+
// is always required.
387+
388+
if (!_staySignedIn) {
389+
await deleteLogIn();
390+
}
391+
373392
final podServer = webIdController.text.trim().isNotEmpty
374393
? webIdController.text.trim()
375394
: SolidConfig.defaultServerUrl;
@@ -415,6 +434,14 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
415434
// re-login so the setup wizard can re-initialise the POD.
416435

417436
Future<void> performContinue() async {
437+
// When the user has opted out of staying signed in, discard any
438+
// existing cached session immediately so the user proceeds in a
439+
// logged-out state.
440+
441+
if (!_staySignedIn) {
442+
await deleteLogIn();
443+
}
444+
418445
final isLoggedIn = await isUserLoggedIn();
419446

420447
if (isLoggedIn && defaultFolders.isNotEmpty) {
@@ -529,14 +556,21 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
529556
height: 24,
530557
child: Checkbox(
531558
value: _staySignedIn,
532-
onChanged: (value) =>
533-
setState(() => _staySignedIn = value ?? true),
559+
onChanged: (value) {
560+
final newValue = value ?? true;
561+
setState(() => _staySignedIn = newValue);
562+
SolidLoginAuthHandler.setStaySignedIn(newValue);
563+
},
534564
),
535565
),
536566
),
537567
const SizedBox(width: 8),
538568
GestureDetector(
539-
onTap: () => setState(() => _staySignedIn = !_staySignedIn),
569+
onTap: () {
570+
final newValue = !_staySignedIn;
571+
setState(() => _staySignedIn = newValue);
572+
SolidLoginAuthHandler.setStaySignedIn(newValue);
573+
},
540574
child: Text(
541575
'Stay signed in',
542576
style: TextStyle(color: currentTheme.textColor, fontSize: 14),

lib/src/widgets/solid_login_auth_handler.dart

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ class SolidLoginAuthHandler {
5656
5757
static const clearSessionKey = 'solidui_clear_session_on_startup';
5858

59+
/// SharedPreferences key for persisting the "Stay signed in" preference.
60+
61+
static const staySignedInKey = 'solidui_stay_signed_in';
62+
5963
/// Clears the cached login session if a previous session opted out of
6064
/// "Stay signed in". Call this early during login page initialisation.
6165
@@ -68,6 +72,21 @@ class SolidLoginAuthHandler {
6872
}
6973
}
7074

75+
/// Returns the persisted "Stay signed in" preference, defaulting to true.
76+
77+
static Future<bool> getStaySignedIn() async {
78+
final prefs = await SharedPreferences.getInstance();
79+
80+
return prefs.getBool(staySignedInKey) ?? true;
81+
}
82+
83+
/// Persists the "Stay signed in" preference.
84+
85+
static Future<void> setStaySignedIn(bool value) async {
86+
final prefs = await SharedPreferences.getInstance();
87+
await prefs.setBool(staySignedInKey, value);
88+
}
89+
7190
/// Notifies the user that their POD is not initialised, verifies the remote
7291
/// directory structure, and navigates to the appropriate screen (setup wizard
7392
/// or child widget).
@@ -97,6 +116,16 @@ class SolidLoginAuthHandler {
97116

98117
if (!allExists) {
99118
await clearPodStructureInitialised();
119+
120+
// Schedule session clearance for next startup when the user has
121+
// opted out of staying signed in. We must not call deleteLogIn()
122+
// here because InitialSetupScreen still needs valid auth data.
123+
124+
if (!staySignedIn) {
125+
final prefs = await SharedPreferences.getInstance();
126+
await prefs.setBool(clearSessionKey, true);
127+
}
128+
100129
if (!context.mounted) return false;
101130

102131
await pushReplacement(
@@ -109,15 +138,20 @@ class SolidLoginAuthHandler {
109138
);
110139
} else {
111140
await markPodStructureInitialised();
141+
142+
// Schedule session clearance for next startup when the user has
143+
// opted out of staying signed in. deleteLogIn() must come after
144+
// markPodStructureInitialised() which requires valid auth data.
145+
146+
if (!staySignedIn) {
147+
final prefs = await SharedPreferences.getInstance();
148+
await prefs.setBool(clearSessionKey, true);
149+
}
150+
112151
if (!context.mounted) return false;
113152
await pushReplacement(context, childWidget);
114153
}
115154

116-
if (!staySignedIn) {
117-
final prefs = await SharedPreferences.getInstance();
118-
await prefs.setBool(clearSessionKey, true);
119-
}
120-
121155
return true;
122156
}
123157

@@ -285,6 +319,15 @@ class SolidLoginAuthHandler {
285319

286320
await clearPodStructureInitialised();
287321

322+
// Schedule session clearance for next startup when the user has
323+
// opted out of staying signed in. We must not call deleteLogIn()
324+
// here because InitialSetupScreen still needs valid auth data.
325+
326+
if (!staySignedIn) {
327+
final prefs = await SharedPreferences.getInstance();
328+
await prefs.setBool(clearSessionKey, true);
329+
}
330+
288331
if (!context.mounted) return false;
289332

290333
await pushReplacement(
@@ -297,15 +340,20 @@ class SolidLoginAuthHandler {
297340
);
298341
} else {
299342
await markPodStructureInitialised();
343+
344+
// Schedule session clearance for next startup when the user has
345+
// opted out of staying signed in. deleteLogIn() must come after
346+
// markPodStructureInitialised() which requires valid auth data.
347+
348+
if (!staySignedIn) {
349+
final prefs = await SharedPreferences.getInstance();
350+
await prefs.setBool(clearSessionKey, true);
351+
}
352+
300353
if (!context.mounted) return false;
301354
await pushReplacement(context, childWidget);
302355
}
303356

304-
if (!staySignedIn) {
305-
final prefs = await SharedPreferences.getInstance();
306-
await prefs.setBool(clearSessionKey, true);
307-
}
308-
309357
return true;
310358
} else {
311359
// solidAuthenticate() returned null. This can happen when:

lib/src/widgets/solid_login_panel.dart

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -124,11 +124,21 @@ class SolidLoginPanel {
124124
],
125125
),
126126

127-
if (staySignedInCheckbox != null) staySignedInCheckbox,
128-
if (tryAnotherAccountButton != null) ...[
129-
const SizedBox(height: 4.0),
127+
if (staySignedInCheckbox != null) ...[
128+
if (tryAnotherAccountButton != null)
129+
Row(
130+
mainAxisAlignment: MainAxisAlignment.center,
131+
crossAxisAlignment: CrossAxisAlignment.center,
132+
children: [
133+
staySignedInCheckbox,
134+
const SizedBox(width: 16.0),
135+
tryAnotherAccountButton,
136+
],
137+
)
138+
else
139+
staySignedInCheckbox,
140+
] else if (tryAnotherAccountButton != null)
130141
tryAnotherAccountButton,
131-
],
132142

133143
const SizedBox(height: 20.0),
134144

lib/src/widgets/solid_scaffold_state.dart

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,16 @@ class SolidScaffoldState extends State<SolidScaffold> {
114114
super.dispose();
115115
}
116116

117-
void _onPreferencesChanged() => mounted ? setState(() {}) : null;
117+
// Deferred to avoid triggering setState while the framework is building
118+
// widgets (e.g. when initializeIfNeeded updates the notifier during build).
119+
120+
void _onPreferencesChanged() {
121+
if (!mounted) return;
122+
WidgetsBinding.instance.addPostFrameCallback((_) {
123+
if (mounted) setState(() {});
124+
});
125+
}
126+
118127
void _onControllerChanged() => mounted ? setState(() {}) : null;
119128
void _onThemeChanged() => mounted ? setState(() {}) : null;
120129

0 commit comments

Comments
 (0)