Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions lib/src/widgets/solid_login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,10 @@ class _SolidLoginState extends State<SolidLogin> 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();
Expand Down Expand Up @@ -253,6 +257,13 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
if (mounted) setState(() => _assetsResolved = true);
}

/// Loads the persisted "Stay signed in" preference from SharedPreferences.

Future<void> _loadStaySignedInPreference() async {
final value = await SolidLoginAuthHandler.getStaySignedIn();
if (mounted) setState(() => _staySignedIn = value);
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
Expand Down Expand Up @@ -370,6 +381,14 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
// - No cache → start the browser login flow as normal.

Future<void> 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;
Expand Down Expand Up @@ -415,6 +434,14 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
// re-login so the setup wizard can re-initialise the POD.

Future<void> 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) {
Expand Down Expand Up @@ -529,14 +556,21 @@ class _SolidLoginState extends State<SolidLogin> 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),
Expand Down
68 changes: 58 additions & 10 deletions lib/src/widgets/solid_login_auth_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -68,6 +72,21 @@ class SolidLoginAuthHandler {
}
}

/// Returns the persisted "Stay signed in" preference, defaulting to true.

static Future<bool> getStaySignedIn() async {
final prefs = await SharedPreferences.getInstance();

return prefs.getBool(staySignedInKey) ?? true;
}

/// Persists the "Stay signed in" preference.

static Future<void> 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).
Expand Down Expand Up @@ -97,6 +116,16 @@ class SolidLoginAuthHandler {

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(
Expand All @@ -109,15 +138,20 @@ 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);
}

if (!staySignedIn) {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(clearSessionKey, true);
}

return true;
}

Expand Down Expand Up @@ -285,6 +319,15 @@ class SolidLoginAuthHandler {

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(
Expand All @@ -297,15 +340,20 @@ 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);
}

if (!staySignedIn) {
final prefs = await SharedPreferences.getInstance();
await prefs.setBool(clearSessionKey, true);
}

return true;
} else {
// solidAuthenticate() returned null. This can happen when:
Expand Down
18 changes: 14 additions & 4 deletions lib/src/widgets/solid_login_panel.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down
11 changes: 10 additions & 1 deletion lib/src/widgets/solid_scaffold_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,16 @@ class SolidScaffoldState extends State<SolidScaffold> {
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;

Expand Down
Loading