From 8751446d2841d3167f4f866878eb9fd473c9d2cd Mon Sep 17 00:00:00 2001 From: Williams Date: Tue, 9 Dec 2025 23:02:04 +1100 Subject: [PATCH 01/37] new --- lib/src/handlers/solid_auth_handler.dart | 17 ++++++++++++++++- lib/src/utils/web_reload_stub.dart | 8 ++++++++ lib/src/utils/web_reload_web.dart | 9 +++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 lib/src/utils/web_reload_stub.dart create mode 100644 lib/src/utils/web_reload_web.dart diff --git a/lib/src/handlers/solid_auth_handler.dart b/lib/src/handlers/solid_auth_handler.dart index e63d285..c3f9ca8 100644 --- a/lib/src/handlers/solid_auth_handler.dart +++ b/lib/src/handlers/solid_auth_handler.dart @@ -28,6 +28,7 @@ library; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart' show getWebId; @@ -35,6 +36,9 @@ import 'package:solidpod/solidpod.dart' show getWebId; import 'package:solidui/src/constants/solid_config.dart'; import 'package:solidui/src/widgets/solid_default_login.dart'; import 'package:solidui/src/widgets/solid_logout_dialog.dart' show logoutPopup; +import 'package:solidui/src/utils/web_reload_stub.dart' + if (dart.library.html) 'package:solidui/src/utils/web_reload_web.dart' + as web_reload; /// Configuration for Solid authentication handling. @@ -130,9 +134,20 @@ class SolidAuthHandler { // No additional navigation needed. } - /// Handle login functionality by navigating to login page. + /// Handle login functionality by reloading the page on web platform. + /// On web, this takes the guest user back to homepage to properly authenticate. + /// On mobile/desktop, navigates to the login page. Future handleLogin(BuildContext context) async { + // On web platform, reload page to reset state and return to homepage + if (kIsWeb) { + debugPrint('SolidAuthHandler: Guest user requesting login, reloading page...'); + await Future.delayed(const Duration(milliseconds: 100)); + web_reload.reloadPage(); + return; + } + + // On mobile/desktop, navigate to login page Navigator.pushReplacement( context, MaterialPageRoute( diff --git a/lib/src/utils/web_reload_stub.dart b/lib/src/utils/web_reload_stub.dart new file mode 100644 index 0000000..849a15b --- /dev/null +++ b/lib/src/utils/web_reload_stub.dart @@ -0,0 +1,8 @@ +/// Stub implementation for non-web platforms. +/// Does nothing on mobile/desktop platforms. + +library; + +void reloadPage() { + // No-op on non-web platforms +} diff --git a/lib/src/utils/web_reload_web.dart b/lib/src/utils/web_reload_web.dart new file mode 100644 index 0000000..7263ae8 --- /dev/null +++ b/lib/src/utils/web_reload_web.dart @@ -0,0 +1,9 @@ +/// Web-specific implementation to reload the page. + +library; + +import 'package:web/web.dart' as web; + +void reloadPage() { + web.window.location.reload(); +} From 70a947e09d9eb95f0e4d1d1f6709d4cd3c8b6cb3 Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 10 Dec 2025 00:00:21 +1100 Subject: [PATCH 02/37] new --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index e4a4fef..3267c85 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,6 +31,7 @@ dependencies: solidpod: ^0.8.6 url_launcher: ^6.3.1 version_widget: ^1.0.6 + web: ^1.1.0 dev_dependencies: flutter_lints: ^6.0.0 From 8b40173fe1c2464731334560a551cf36138fbac1 Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 10 Dec 2025 12:37:51 +1100 Subject: [PATCH 03/37] new --- .../widgets/solid_dynamic_login_status.dart | 15 ++-- lib/src/widgets/solid_login_auth_handler.dart | 69 +++++++++++++------ lib/src/widgets/solid_logout_dialog.dart | 5 +- 3 files changed, 62 insertions(+), 27 deletions(-) diff --git a/lib/src/widgets/solid_dynamic_login_status.dart b/lib/src/widgets/solid_dynamic_login_status.dart index 4881f45..4d7a578 100644 --- a/lib/src/widgets/solid_dynamic_login_status.dart +++ b/lib/src/widgets/solid_dynamic_login_status.dart @@ -135,24 +135,27 @@ class _SolidDynamicLoginStatusState extends State { _currentWebId != null && _currentWebId!.isNotEmpty; if (isCurrentlyLoggedIn) { + // Logout scenario - don't recheck status as page will reload if (widget.onTap != null) { widget.onTap!.call(); } else { SolidAuthHandler.instance.handleLogout(context); } } else { + // Login scenario - can delay status check if (widget.onLogin != null) { widget.onLogin!.call(); } else { SolidAuthHandler.instance.handleLogin(context); } + + // Only refresh status for login scenario + Future.delayed(const Duration(milliseconds: 500), () { + if (mounted) { + _checkLoginStatus(); + } + }); } - - Future.delayed(const Duration(milliseconds: 500), () { - if (mounted) { - _checkLoginStatus(); - } - }); } @override diff --git a/lib/src/widgets/solid_login_auth_handler.dart b/lib/src/widgets/solid_login_auth_handler.dart index 39e5bac..74c9d3a 100644 --- a/lib/src/widgets/solid_login_auth_handler.dart +++ b/lib/src/widgets/solid_login_auth_handler.dart @@ -31,12 +31,13 @@ library; // ignore_for_file: public_member_api_docs +import 'dart:async' show unawaited; + import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart' show checkLoggedIn, solidAuthenticate, initialStructureTest; -import 'package:solidui/src/screens/initial_setup_screen.dart'; import 'package:solidui/src/widgets/solid_animation_dialog.dart'; import 'package:solidui/src/widgets/solid_login_helper.dart'; @@ -136,28 +137,21 @@ class SolidLoginAuthHandler { await Future.delayed(const Duration(milliseconds: 300)); } - // Navigate to the appropriate screen based on structure test. + // Navigate to main app immediately after successful authentication + // This provides instant user feedback and better UX + if (!context.mounted) return false; + + debugPrint('SolidLoginAuthHandler: Authentication successful, navigating to app immediately...'); + await pushReplacement(context, childWidget); - final resCheckList = await initialStructureTest( + // Check initial structure in background (non-blocking) + // If setup is needed, user can access it from the app later + unawaited(_checkInitialStructureInBackground( defaultFolders, defaultFiles, - ); - final allExists = resCheckList.first as bool; - - if (!context.mounted) return false; - - if (!allExists) { - await pushReplacement( - context, - InitialSetupScreen( - resCheckList: resCheckList, - originalLogin: originalLoginWidget, - child: childWidget, - ), - ); - } else { - await pushReplacement(context, childWidget); - } + originalLoginWidget, + childWidget, + ),); return true; } else { @@ -178,4 +172,39 @@ class SolidLoginAuthHandler { return false; } } + + /// Checks initial POD structure in the background without blocking navigation. + /// + /// This allows the user to access the app immediately while structure + /// verification happens asynchronously. If setup is needed, it can be + /// triggered later from within the app. + static Future _checkInitialStructureInBackground( + List defaultFolders, + Map defaultFiles, + dynamic originalLoginWidget, + Widget childWidget, + ) async { + try { + debugPrint('SolidLoginAuthHandler: Checking initial structure in background...'); + + final resCheckList = await initialStructureTest( + defaultFolders, + defaultFiles, + ); + + final allExists = resCheckList.first as bool; + + if (allExists) { + debugPrint('SolidLoginAuthHandler: Initial structure verified successfully'); + } else { + debugPrint('SolidLoginAuthHandler: Initial structure incomplete - user may need to run setup'); + // In the future, we could show a notification or prompt here + // For now, we just log it and let the user discover setup options in the app + } + } catch (e) { + debugPrint('SolidLoginAuthHandler: Background structure check failed: $e'); + // Non-critical error - user can still use the app + } + } } + diff --git a/lib/src/widgets/solid_logout_dialog.dart b/lib/src/widgets/solid_logout_dialog.dart index 1be7e7c..518bef0 100644 --- a/lib/src/widgets/solid_logout_dialog.dart +++ b/lib/src/widgets/solid_logout_dialog.dart @@ -26,6 +26,7 @@ library; +import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart' show getAppNameVersion, logoutPod; @@ -55,7 +56,9 @@ class _LogoutDialogState extends State { child: const Text('OK'), onPressed: () async { if (await logoutPod()) { - if (context.mounted) { + // On web, logoutPod() reloads the page, so no navigation needed + // On mobile/desktop, navigate to login page + if (!kIsWeb && context.mounted) { await Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => widget.child), From 8a2e815d57498efd2cf833d9d4355ab907e18d48 Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 10 Dec 2025 13:32:27 +1100 Subject: [PATCH 04/37] new --- lib/src/widgets/solid_scaffold.dart | 1 + .../solid_scaffold_layout_builder.dart | 22 ++++++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/solid_scaffold.dart b/lib/src/widgets/solid_scaffold.dart index 376ae74..e37a720 100644 --- a/lib/src/widgets/solid_scaffold.dart +++ b/lib/src/widgets/solid_scaffold.dart @@ -509,6 +509,7 @@ class SolidScaffoldState extends State { ), _onMenuSelected, widget.onShowAlert, + widget.menu, // Pass menu items for IndexedStack ); return NotificationListener( onNotification: (notification) { diff --git a/lib/src/widgets/solid_scaffold_layout_builder.dart b/lib/src/widgets/solid_scaffold_layout_builder.dart index 2947ee2..381c4a0 100644 --- a/lib/src/widgets/solid_scaffold_layout_builder.dart +++ b/lib/src/widgets/solid_scaffold_layout_builder.dart @@ -36,13 +36,15 @@ import 'package:solidui/src/constants/navigation.dart'; import 'package:solidui/src/widgets/solid_dynamic_login_status.dart'; import 'package:solidui/src/widgets/solid_nav_bar.dart'; import 'package:solidui/src/widgets/solid_nav_models.dart'; +import 'package:solidui/src/widgets/solid_scaffold_models.dart'; import 'package:solidui/src/widgets/solid_status_bar.dart'; import 'package:solidui/src/widgets/solid_status_bar_models.dart'; /// Builder class for creating Scaffold layouts. class SolidScaffoldLayoutBuilder { - /// Builds the main body content. + /// Builds the main body content using IndexedStack to preserve widget state. + /// This prevents unnecessary widget rebuilds when switching between tabs. static Widget buildBody( BuildContext context, @@ -52,6 +54,7 @@ class SolidScaffoldLayoutBuilder { Widget? effectiveChild, Function(int) onTabSelected, Function(BuildContext, String, String?)? onShowAlert, + List? menuItems, ) { final theme = Theme.of(context); @@ -59,6 +62,19 @@ class SolidScaffoldLayoutBuilder { return const SizedBox.shrink(); } + // Build IndexedStack with all menu item children to preserve state + Widget contentArea; + if (menuItems != null && menuItems.isNotEmpty) { + contentArea = IndexedStack( + index: selectedIndex.clamp(0, menuItems.length - 1), + sizing: StackFit.expand, + children: menuItems.map((item) => item.child ?? const SizedBox.shrink()).toList(), + ); + } else { + // Fallback to single child if no menu items + contentArea = effectiveChild; + } + if (isWideScreen) { // Wide screen: show navigation bar + content. @@ -75,7 +91,7 @@ class SolidScaffoldLayoutBuilder { onShowAlert: onShowAlert, ), VerticalDivider(width: 1, color: theme.dividerColor), - Expanded(child: effectiveChild), + Expanded(child: contentArea), ], ), ), @@ -87,7 +103,7 @@ class SolidScaffoldLayoutBuilder { return Column( children: [ Divider(height: 1, color: theme.dividerColor), - Expanded(child: effectiveChild), + Expanded(child: contentArea), ], ); } From 03ab4d50085c63a534e9212bbe05d8fdac3ac4cc Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 10 Dec 2025 21:36:59 +1100 Subject: [PATCH 05/37] new --- lib/src/widgets/solid_login_helper.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/src/widgets/solid_login_helper.dart b/lib/src/widgets/solid_login_helper.dart index 5ba71c5..5ae48f5 100644 --- a/lib/src/widgets/solid_login_helper.dart +++ b/lib/src/widgets/solid_login_helper.dart @@ -353,17 +353,17 @@ MarkdownTooltip getThemeToggleTooltip( ); /// Utility function for navigation +/// Modified to preserve widget state by using simple replacement instead of clearing entire stack Future pushReplacement( BuildContext context, Widget destinationWidget, ) async { - Navigator.pushAndRemoveUntil( - context, + // Use simple pushReplacement instead of pushAndRemoveUntil + // This preserves the navigation history and doesn't destroy all widgets + await Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (context) => destinationWidget, ), - (Route route) => - false, // This predicate ensures all previous routes are removed ); } From fda79577c69f8e3a380ad9e60ae51ebf85fec922 Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 10 Dec 2025 23:57:37 +1100 Subject: [PATCH 06/37] new --- lib/src/handlers/solid_auth_handler.dart | 26 +++++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/src/handlers/solid_auth_handler.dart b/lib/src/handlers/solid_auth_handler.dart index c3f9ca8..b1cc092 100644 --- a/lib/src/handlers/solid_auth_handler.dart +++ b/lib/src/handlers/solid_auth_handler.dart @@ -34,7 +34,7 @@ import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart' show getWebId; import 'package:solidui/src/constants/solid_config.dart'; -import 'package:solidui/src/widgets/solid_default_login.dart'; +import 'package:solidui/src/widgets/solid_login.dart'; import 'package:solidui/src/widgets/solid_logout_dialog.dart' show logoutPopup; import 'package:solidui/src/utils/web_reload_stub.dart' if (dart.library.html) 'package:solidui/src/utils/web_reload_web.dart' @@ -157,23 +157,29 @@ class SolidAuthHandler { } /// Build the login page widget. + /// + /// Returns the actual login input page (SolidLogin), not the success page. + /// This is used when guest users want to authenticate, or after logout. Widget _buildLoginPage(BuildContext context) { if (_config?.loginPageBuilder != null) { return _config!.loginPageBuilder!(context); } - // Use default login page. + // Use the login input page, not the success page + // The loginSuccessWidget (child) will be shown after successful authentication + final mainAppWidget = _config?.loginSuccessWidget ?? + const Center(child: Text('Authentication required')); - return SolidDefaultLogin( - appTitle: _config?.appTitle ?? 'Solid App', + return SolidLogin( appDirectory: _config?.appDirectory ?? 'solid_app', - defaultServerUrl: - _config?.defaultServerUrl ?? SolidConfig.defaultServerUrl, - appImage: _config?.appImage, - appLogo: _config?.appLogo, - appLink: _config?.appLink, - loginSuccessWidget: _config?.loginSuccessWidget, + webID: _config?.defaultServerUrl ?? SolidConfig.defaultServerUrl, + // Use provided images or fallback to SolidLogin's defaults from solidpod package + image: _config?.appImage ?? + const AssetImage('assets/images/default_image.jpg', package: 'solidpod'), + logo: _config?.appLogo ?? + const AssetImage('assets/images/default_logo.png', package: 'solidpod'), + child: mainAppWidget, ); } From 67a3939678bc01c26511e668737a9e9d3e1f55b6 Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 11 Dec 2025 03:55:38 +1100 Subject: [PATCH 07/37] new --- .../solid_security_key_operations.dart | 5 +- lib/src/widgets/solid_status_bar.dart | 50 ++++++++++++++++++- 2 files changed, 50 insertions(+), 5 deletions(-) diff --git a/lib/src/widgets/solid_security_key_operations.dart b/lib/src/widgets/solid_security_key_operations.dart index 7272f98..361000a 100644 --- a/lib/src/widgets/solid_security_key_operations.dart +++ b/lib/src/widgets/solid_security_key_operations.dart @@ -71,9 +71,8 @@ class SecurityKeyOperations { try { // Use initPodKeys() to re-initialise the security key. - - // await KeyManager.initPodKeys(key); - // debugPrint('Security key successfully initialised.'); + await KeyManager.initPodKeys(key); + debugPrint('Security key successfully initialised and saved.'); return true; } catch (e) { debugPrint('Error setting security key: $e'); diff --git a/lib/src/widgets/solid_status_bar.dart b/lib/src/widgets/solid_status_bar.dart index f300cb0..6b13f15 100644 --- a/lib/src/widgets/solid_status_bar.dart +++ b/lib/src/widgets/solid_status_bar.dart @@ -32,6 +32,8 @@ library; import 'package:flutter/material.dart'; +import 'package:solidpod/solidpod.dart' show getWebId; + import 'package:gap/gap.dart'; import 'package:markdown_tooltip/markdown_tooltip.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -184,10 +186,54 @@ class SolidStatusBar extends StatelessWidget { /// Shows the built-in security key manager dialogue. - void _showSecurityKeyManager( + Future _showSecurityKeyManager( BuildContext context, SolidSecurityKeyStatus config, - ) { + ) async { + // Import at top: import 'package:solidpod/solidpod.dart' show getWebId; + // Check if user is logged in first + try { + final webId = await getWebId(); + + if (webId == null || webId.isEmpty) { + // Show friendly login prompt + if (!context.mounted) return; + await showDialog( + context: context, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + title: const Row( + children: [ + Icon(Icons.info_outline, color: Colors.blue), + SizedBox(width: 8), + Text('Login Required'), + ], + ), + content: const Text( + 'You must be logged in to manage your security key.\n\n' + 'Security keys are used to encrypt your sensitive data stored in your Solid Pod.\n\n' + 'Please log in first, then you can set up your security key.', + style: TextStyle(fontSize: 14), + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context), + child: const Text('OK'), + ), + ], + ), + ); + return; + } + } catch (e) { + debugPrint('Error checking login status: $e'); + // Continue to show dialog anyway + } + + if (!context.mounted) return; + showDialog( context: context, barrierColor: Colors.black.withValues(alpha: 0.5), From d2ec70204428566197ad1a9d12eda7ca9ba13b0d Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 11 Dec 2025 18:58:10 +1100 Subject: [PATCH 08/37] new --- lib/src/handlers/solid_auth_handler.dart | 3 ++ lib/src/widgets/solid_login.dart | 48 +++++++++++++++++++----- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/lib/src/handlers/solid_auth_handler.dart b/lib/src/handlers/solid_auth_handler.dart index b1cc092..6e88dfd 100644 --- a/lib/src/handlers/solid_auth_handler.dart +++ b/lib/src/handlers/solid_auth_handler.dart @@ -171,7 +171,10 @@ class SolidAuthHandler { final mainAppWidget = _config?.loginSuccessWidget ?? const Center(child: Text('Authentication required')); + // Use ValueKey to identify this as a fresh login page instance + // This works with didUpdateWidget() to reset state when needed return SolidLogin( + key: const ValueKey('login_page'), appDirectory: _config?.appDirectory ?? 'solid_app', webID: _config?.defaultServerUrl ?? SolidConfig.defaultServerUrl, // Use provided images or fallback to SolidLogin's defaults from solidpod package diff --git a/lib/src/widgets/solid_login.dart b/lib/src/widgets/solid_login.dart index df2414c..70978ad 100644 --- a/lib/src/widgets/solid_login.dart +++ b/lib/src/widgets/solid_login.dart @@ -182,14 +182,47 @@ class _SolidLoginState extends State { bool isDarkMode = false; + // Text controller for the URI of the solid server - should be managed in state + late TextEditingController _webIdController; + @override void initState() { super.initState(); + // Initialize the controller with the widget's webID + _webIdController = TextEditingController(text: widget.webID); + // dc 20251022: please explain why calling an async without await. _initPackageInfo(); } + @override + void didUpdateWidget(SolidLogin oldWidget) { + super.didUpdateWidget(oldWidget); + + // Always reset the controller text to widget.webID when widget updates + // This ensures fresh state when returning from guest mode, even if the user + // had manually modified the URL field before leaving + // Only skip reset if the current text already matches the intended value + if (_webIdController.text != widget.webID) { + _webIdController.text = widget.webID; + } + + // CRITICAL: Reset appDirName if appDirectory changed + // This fixes the double-slash bug when returning from guest mode + // Without this, appDirName stays empty causing paths like //data/places.json + if (oldWidget.appDirectory != widget.appDirectory) { + setAppDirName(widget.appDirectory); + } + } + + @override + void dispose() { + // Clean up the controller when the widget is disposed + _webIdController.dispose(); + super.dispose(); + } + // Fetch the package information. Future _initPackageInfo() async { @@ -311,19 +344,14 @@ class _SolidLoginState extends State { image: DecorationImage(image: widget.image, fit: BoxFit.cover), ); - // Text controller for the URI of the solid server to which an authenticate - // request is sent. - - final webIdController = TextEditingController()..text = widget.webID; - // Build all buttons using the button builder. // User input from text field will override the default server URL. final registerButton = SolidLoginButtons.buildRegisterButton( style: widget.registerButtonStyle, onPressed: () { - final webId = webIdController.text.trim().isNotEmpty - ? webIdController.text.trim() + final webId = _webIdController.text.trim().isNotEmpty + ? _webIdController.text.trim() : SolidConfig.defaultServerUrl; launchUrl(Uri.parse('$webId/.account/login/password/register/')); }, @@ -332,8 +360,8 @@ class _SolidLoginState extends State { final loginButton = SolidLoginButtons.buildLoginButton( style: widget.loginButtonStyle, onPressed: () async { - final podServer = webIdController.text.trim().isNotEmpty - ? webIdController.text.trim() + final podServer = _webIdController.text.trim().isNotEmpty + ? _webIdController.text.trim() : SolidConfig.defaultServerUrl; isDialogCanceled = false; @@ -368,7 +396,7 @@ class _SolidLoginState extends State { logo: widget.logo, title: widget.title, appVersion: appVersion, - webIdController: webIdController, + webIdController: _webIdController, loginButton: loginButton, registerButton: registerButton, continueButton: continueButton, From 4b135a630ac3c927e78bcbf105f2e8ecc6fbecee Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 11 Dec 2025 19:05:19 +1100 Subject: [PATCH 09/37] new --- lib/src/widgets/solid_login_auth_handler.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/widgets/solid_login_auth_handler.dart b/lib/src/widgets/solid_login_auth_handler.dart index 74c9d3a..c9c16d9 100644 --- a/lib/src/widgets/solid_login_auth_handler.dart +++ b/lib/src/widgets/solid_login_auth_handler.dart @@ -141,7 +141,6 @@ class SolidLoginAuthHandler { // This provides instant user feedback and better UX if (!context.mounted) return false; - debugPrint('SolidLoginAuthHandler: Authentication successful, navigating to app immediately...'); await pushReplacement(context, childWidget); // Check initial structure in background (non-blocking) From 44fd9b7fa5a642a99d29b6bb0aa1298daf94a58e Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 11 Dec 2025 20:37:44 +1100 Subject: [PATCH 10/37] new --- lib/src/handlers/solid_auth_handler.dart | 20 ++++---------------- lib/src/utils/web_reload_stub.dart | 8 -------- lib/src/utils/web_reload_web.dart | 9 --------- lib/src/widgets/solid_logout_dialog.dart | 7 +++---- 4 files changed, 7 insertions(+), 37 deletions(-) delete mode 100644 lib/src/utils/web_reload_stub.dart delete mode 100644 lib/src/utils/web_reload_web.dart diff --git a/lib/src/handlers/solid_auth_handler.dart b/lib/src/handlers/solid_auth_handler.dart index 6e88dfd..a2b44f3 100644 --- a/lib/src/handlers/solid_auth_handler.dart +++ b/lib/src/handlers/solid_auth_handler.dart @@ -28,7 +28,6 @@ library; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart' show getWebId; @@ -36,9 +35,6 @@ import 'package:solidpod/solidpod.dart' show getWebId; import 'package:solidui/src/constants/solid_config.dart'; import 'package:solidui/src/widgets/solid_login.dart'; import 'package:solidui/src/widgets/solid_logout_dialog.dart' show logoutPopup; -import 'package:solidui/src/utils/web_reload_stub.dart' - if (dart.library.html) 'package:solidui/src/utils/web_reload_web.dart' - as web_reload; /// Configuration for Solid authentication handling. @@ -134,20 +130,12 @@ class SolidAuthHandler { // No additional navigation needed. } - /// Handle login functionality by reloading the page on web platform. - /// On web, this takes the guest user back to homepage to properly authenticate. - /// On mobile/desktop, navigates to the login page. + /// Handle login functionality - navigates to login page. + /// Works consistently across all platforms (web, mobile, desktop). Future handleLogin(BuildContext context) async { - // On web platform, reload page to reset state and return to homepage - if (kIsWeb) { - debugPrint('SolidAuthHandler: Guest user requesting login, reloading page...'); - await Future.delayed(const Duration(milliseconds: 100)); - web_reload.reloadPage(); - return; - } - - // On mobile/desktop, navigate to login page + // Navigate to login page using standard Flutter navigation + // This works across all platforms and maintains proper widget lifecycle Navigator.pushReplacement( context, MaterialPageRoute( diff --git a/lib/src/utils/web_reload_stub.dart b/lib/src/utils/web_reload_stub.dart deleted file mode 100644 index 849a15b..0000000 --- a/lib/src/utils/web_reload_stub.dart +++ /dev/null @@ -1,8 +0,0 @@ -/// Stub implementation for non-web platforms. -/// Does nothing on mobile/desktop platforms. - -library; - -void reloadPage() { - // No-op on non-web platforms -} diff --git a/lib/src/utils/web_reload_web.dart b/lib/src/utils/web_reload_web.dart deleted file mode 100644 index 7263ae8..0000000 --- a/lib/src/utils/web_reload_web.dart +++ /dev/null @@ -1,9 +0,0 @@ -/// Web-specific implementation to reload the page. - -library; - -import 'package:web/web.dart' as web; - -void reloadPage() { - web.window.location.reload(); -} diff --git a/lib/src/widgets/solid_logout_dialog.dart b/lib/src/widgets/solid_logout_dialog.dart index 518bef0..f0c1f5f 100644 --- a/lib/src/widgets/solid_logout_dialog.dart +++ b/lib/src/widgets/solid_logout_dialog.dart @@ -26,7 +26,6 @@ library; -import 'package:flutter/foundation.dart' show kIsWeb; import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart' show getAppNameVersion, logoutPod; @@ -56,9 +55,9 @@ class _LogoutDialogState extends State { child: const Text('OK'), onPressed: () async { if (await logoutPod()) { - // On web, logoutPod() reloads the page, so no navigation needed - // On mobile/desktop, navigate to login page - if (!kIsWeb && context.mounted) { + // Navigate to login page after successful logout + // Works consistently across all platforms + if (context.mounted) { await Navigator.pushReplacement( context, MaterialPageRoute(builder: (context) => widget.child), From 617de91b153e5927d17c5fa1174276c03e8d8137 Mon Sep 17 00:00:00 2001 From: Williams Date: Fri, 12 Dec 2025 12:45:27 +1100 Subject: [PATCH 11/37] new --- devtools_options.yaml | 3 + lib/src/widgets/solid_login_auth_handler.dart | 65 ++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 devtools_options.yaml diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/src/widgets/solid_login_auth_handler.dart b/lib/src/widgets/solid_login_auth_handler.dart index c9c16d9..132bc4a 100644 --- a/lib/src/widgets/solid_login_auth_handler.dart +++ b/lib/src/widgets/solid_login_auth_handler.dart @@ -100,7 +100,70 @@ class SolidLoginAuthHandler { // Perform the actual authentication by contacting the server. if (!context.mounted) return false; - final authResult = await solidAuthenticate(podServer, context); + + List? authResult; + try { + authResult = await solidAuthenticate(podServer, context); + } catch (e) { + // Authentication error - likely server unavailable or network issue + debugPrint('SolidLoginAuthHandler: Authentication error: $e'); + + if (!context.mounted) return false; + + // Close the animation dialog + if (!wasAlreadyLoggedIn) { + Navigator.of(context, rootNavigator: true).pop(); + } + + // Show error dialog with server availability check + await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + title: const Row( + children: [ + Icon(Icons.error_outline, color: Colors.red), + SizedBox(width: 8), + Text('Connection Failed'), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Unable to connect to the Solid server.', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 12), + Text('Server: $podServer'), + const SizedBox(height: 12), + const Text('Possible causes:'), + const SizedBox(height: 4), + const Text('• Server is temporarily unavailable'), + const Text('• Network connection issue'), + const Text('• Invalid server URL'), + const SizedBox(height: 12), + Text( + 'Error: ${e.toString()}', + style: const TextStyle(fontSize: 11, color: Colors.grey), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('OK'), + ), + ], + ), + ); + + // Navigate back to login screen + if (!context.mounted) return false; + await pushReplacement(context, originalLoginWidget); + return false; + } // If authentication succeeded and the user was already logged in, // it means they are using a cached session. From c8abf7feb5a20a97f11c1d8b673b839d01e060d6 Mon Sep 17 00:00:00 2001 From: Williams Date: Sat, 13 Dec 2025 12:59:57 +1100 Subject: [PATCH 12/37] new --- lib/src/utils/file_operations.dart | 10 +++++----- lib/src/widgets/solid_file_browser.dart | 4 ++-- lib/src/widgets/solid_security_key_manager.dart | 4 +++- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/src/utils/file_operations.dart b/lib/src/utils/file_operations.dart index 0638ea5..8576180 100644 --- a/lib/src/utils/file_operations.dart +++ b/lib/src/utils/file_operations.dart @@ -110,9 +110,9 @@ class FileOperations { final fileName = extractResourceName(fileUrl); - // Skip non-TTL files. Include both .enc.ttl and .ttl files. - - if (!fileName.endsWith('.enc.ttl') && !fileName.endsWith('.ttl')) { + // Skip ACL files and metadata files, but include all other file types + // (TTL, JSON, CSV, TXT, etc.) + if (fileName.endsWith('.acl') || fileName.endsWith('.meta')) { continue; } @@ -180,12 +180,12 @@ class FileOperations { static Future getDirectoryFileCount(String dirPath) async { try { - // Get directory contents and count files. + // Get directory contents and count files (exclude ACL and metadata files). final dirUrl = await getDirUrl(dirPath); final resources = await getResourcesInContainer(dirUrl); return resources.files - .where((f) => f.endsWith('.enc.ttl') || f.endsWith('.ttl')) + .where((f) => !f.endsWith('.acl') && !f.endsWith('.meta')) .length; } catch (e) { debugPrint('Error counting files in directory: $e'); diff --git a/lib/src/widgets/solid_file_browser.dart b/lib/src/widgets/solid_file_browser.dart index ae159df..ad782a8 100644 --- a/lib/src/widgets/solid_file_browser.dart +++ b/lib/src/widgets/solid_file_browser.dart @@ -232,10 +232,10 @@ class SolidFileBrowserState extends State { currentDirDirectoryCount = directories.length; }); - // Count files in current directory. + // Count files in current directory (exclude ACL and metadata files). currentDirFileCount = resources.files - .where((f) => f.endsWith('.enc.ttl') || f.endsWith('.ttl')) + .where((f) => !f.endsWith('.acl') && !f.endsWith('.meta')) .length; // Get file counts for all subdirectories. diff --git a/lib/src/widgets/solid_security_key_manager.dart b/lib/src/widgets/solid_security_key_manager.dart index df37d81..f4bdee9 100644 --- a/lib/src/widgets/solid_security_key_manager.dart +++ b/lib/src/widgets/solid_security_key_manager.dart @@ -290,9 +290,11 @@ class SolidSecurityKeyManagerState extends State await KeyManager.forgetSecurityKey(); // Delete the key file from POD. + // IMPORTANT: Use isKey: true to skip permission revocation + // because encryption files are NOT in the data directory final encKeyPath = await getEncKeyPath(); - await deleteFile(encKeyPath); + await deleteFile(encKeyPath, isKey: true); success = true; msg = 'Successfully forgot local security key.'; From 6d227215f6a53d03396cd3444e128e782034f8ce Mon Sep 17 00:00:00 2001 From: Williams Date: Sun, 14 Dec 2025 10:58:31 +1100 Subject: [PATCH 13/37] new --- lib/src/handlers/solid_auth_handler.dart | 14 +++--- .../widgets/solid_dynamic_login_status.dart | 2 +- lib/src/widgets/solid_login.dart | 4 +- lib/src/widgets/solid_login_auth_handler.dart | 47 ++++++++++--------- lib/src/widgets/solid_scaffold.dart | 2 +- .../solid_scaffold_layout_builder.dart | 4 +- lib/src/widgets/solid_status_bar.dart | 4 +- 7 files changed, 43 insertions(+), 34 deletions(-) diff --git a/lib/src/handlers/solid_auth_handler.dart b/lib/src/handlers/solid_auth_handler.dart index a2b44f3..864eae7 100644 --- a/lib/src/handlers/solid_auth_handler.dart +++ b/lib/src/handlers/solid_auth_handler.dart @@ -145,7 +145,7 @@ class SolidAuthHandler { } /// Build the login page widget. - /// + /// /// Returns the actual login input page (SolidLogin), not the success page. /// This is used when guest users want to authenticate, or after logout. @@ -156,7 +156,7 @@ class SolidAuthHandler { // Use the login input page, not the success page // The loginSuccessWidget (child) will be shown after successful authentication - final mainAppWidget = _config?.loginSuccessWidget ?? + final mainAppWidget = _config?.loginSuccessWidget ?? const Center(child: Text('Authentication required')); // Use ValueKey to identify this as a fresh login page instance @@ -166,10 +166,12 @@ class SolidAuthHandler { appDirectory: _config?.appDirectory ?? 'solid_app', webID: _config?.defaultServerUrl ?? SolidConfig.defaultServerUrl, // Use provided images or fallback to SolidLogin's defaults from solidpod package - image: _config?.appImage ?? - const AssetImage('assets/images/default_image.jpg', package: 'solidpod'), - logo: _config?.appLogo ?? - const AssetImage('assets/images/default_logo.png', package: 'solidpod'), + image: _config?.appImage ?? + const AssetImage('assets/images/default_image.jpg', + package: 'solidpod'), + logo: _config?.appLogo ?? + const AssetImage('assets/images/default_logo.png', + package: 'solidpod'), child: mainAppWidget, ); } diff --git a/lib/src/widgets/solid_dynamic_login_status.dart b/lib/src/widgets/solid_dynamic_login_status.dart index 4d7a578..9fff2cc 100644 --- a/lib/src/widgets/solid_dynamic_login_status.dart +++ b/lib/src/widgets/solid_dynamic_login_status.dart @@ -148,7 +148,7 @@ class _SolidDynamicLoginStatusState extends State { } else { SolidAuthHandler.instance.handleLogin(context); } - + // Only refresh status for login scenario Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { diff --git a/lib/src/widgets/solid_login.dart b/lib/src/widgets/solid_login.dart index 70978ad..07302b0 100644 --- a/lib/src/widgets/solid_login.dart +++ b/lib/src/widgets/solid_login.dart @@ -199,7 +199,7 @@ class _SolidLoginState extends State { @override void didUpdateWidget(SolidLogin oldWidget) { super.didUpdateWidget(oldWidget); - + // Always reset the controller text to widget.webID when widget updates // This ensures fresh state when returning from guest mode, even if the user // had manually modified the URL field before leaving @@ -207,7 +207,7 @@ class _SolidLoginState extends State { if (_webIdController.text != widget.webID) { _webIdController.text = widget.webID; } - + // CRITICAL: Reset appDirName if appDirectory changed // This fixes the double-slash bug when returning from guest mode // Without this, appDirName stays empty causing paths like //data/places.json diff --git a/lib/src/widgets/solid_login_auth_handler.dart b/lib/src/widgets/solid_login_auth_handler.dart index 132bc4a..948a0a5 100644 --- a/lib/src/widgets/solid_login_auth_handler.dart +++ b/lib/src/widgets/solid_login_auth_handler.dart @@ -100,21 +100,21 @@ class SolidLoginAuthHandler { // Perform the actual authentication by contacting the server. if (!context.mounted) return false; - + List? authResult; try { authResult = await solidAuthenticate(podServer, context); } catch (e) { // Authentication error - likely server unavailable or network issue debugPrint('SolidLoginAuthHandler: Authentication error: $e'); - + if (!context.mounted) return false; - + // Close the animation dialog if (!wasAlreadyLoggedIn) { Navigator.of(context, rootNavigator: true).pop(); } - + // Show error dialog with server availability check await showDialog( context: context, @@ -158,7 +158,7 @@ class SolidLoginAuthHandler { ], ), ); - + // Navigate back to login screen if (!context.mounted) return false; await pushReplacement(context, originalLoginWidget); @@ -203,17 +203,19 @@ class SolidLoginAuthHandler { // Navigate to main app immediately after successful authentication // This provides instant user feedback and better UX if (!context.mounted) return false; - + await pushReplacement(context, childWidget); // Check initial structure in background (non-blocking) // If setup is needed, user can access it from the app later - unawaited(_checkInitialStructureInBackground( - defaultFolders, - defaultFiles, - originalLoginWidget, - childWidget, - ),); + unawaited( + _checkInitialStructureInBackground( + defaultFolders, + defaultFiles, + originalLoginWidget, + childWidget, + ), + ); return true; } else { @@ -236,7 +238,7 @@ class SolidLoginAuthHandler { } /// Checks initial POD structure in the background without blocking navigation. - /// + /// /// This allows the user to access the app immediately while structure /// verification happens asynchronously. If setup is needed, it can be /// triggered later from within the app. @@ -247,26 +249,29 @@ class SolidLoginAuthHandler { Widget childWidget, ) async { try { - debugPrint('SolidLoginAuthHandler: Checking initial structure in background...'); - + debugPrint( + 'SolidLoginAuthHandler: Checking initial structure in background...'); + final resCheckList = await initialStructureTest( defaultFolders, defaultFiles, ); - + final allExists = resCheckList.first as bool; - + if (allExists) { - debugPrint('SolidLoginAuthHandler: Initial structure verified successfully'); + debugPrint( + 'SolidLoginAuthHandler: Initial structure verified successfully'); } else { - debugPrint('SolidLoginAuthHandler: Initial structure incomplete - user may need to run setup'); + debugPrint( + 'SolidLoginAuthHandler: Initial structure incomplete - user may need to run setup'); // In the future, we could show a notification or prompt here // For now, we just log it and let the user discover setup options in the app } } catch (e) { - debugPrint('SolidLoginAuthHandler: Background structure check failed: $e'); + debugPrint( + 'SolidLoginAuthHandler: Background structure check failed: $e'); // Non-critical error - user can still use the app } } } - diff --git a/lib/src/widgets/solid_scaffold.dart b/lib/src/widgets/solid_scaffold.dart index e37a720..8752597 100644 --- a/lib/src/widgets/solid_scaffold.dart +++ b/lib/src/widgets/solid_scaffold.dart @@ -509,7 +509,7 @@ class SolidScaffoldState extends State { ), _onMenuSelected, widget.onShowAlert, - widget.menu, // Pass menu items for IndexedStack + widget.menu, // Pass menu items for IndexedStack ); return NotificationListener( onNotification: (notification) { diff --git a/lib/src/widgets/solid_scaffold_layout_builder.dart b/lib/src/widgets/solid_scaffold_layout_builder.dart index 381c4a0..63edcfe 100644 --- a/lib/src/widgets/solid_scaffold_layout_builder.dart +++ b/lib/src/widgets/solid_scaffold_layout_builder.dart @@ -68,7 +68,9 @@ class SolidScaffoldLayoutBuilder { contentArea = IndexedStack( index: selectedIndex.clamp(0, menuItems.length - 1), sizing: StackFit.expand, - children: menuItems.map((item) => item.child ?? const SizedBox.shrink()).toList(), + children: menuItems + .map((item) => item.child ?? const SizedBox.shrink()) + .toList(), ); } else { // Fallback to single child if no menu items diff --git a/lib/src/widgets/solid_status_bar.dart b/lib/src/widgets/solid_status_bar.dart index 6b13f15..ac457ad 100644 --- a/lib/src/widgets/solid_status_bar.dart +++ b/lib/src/widgets/solid_status_bar.dart @@ -194,7 +194,7 @@ class SolidStatusBar extends StatelessWidget { // Check if user is logged in first try { final webId = await getWebId(); - + if (webId == null || webId.isEmpty) { // Show friendly login prompt if (!context.mounted) return; @@ -233,7 +233,7 @@ class SolidStatusBar extends StatelessWidget { } if (!context.mounted) return; - + showDialog( context: context, barrierColor: Colors.black.withValues(alpha: 0.5), From d936549b5158ecbe7050c26978d6fdd7a266933a Mon Sep 17 00:00:00 2001 From: Williams Date: Mon, 15 Dec 2025 13:01:02 +1100 Subject: [PATCH 14/37] new --- lib/src/handlers/solid_auth_handler.dart | 4 ++-- lib/src/widgets/solid_login_auth_handler.dart | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/src/handlers/solid_auth_handler.dart b/lib/src/handlers/solid_auth_handler.dart index 864eae7..e777ab0 100644 --- a/lib/src/handlers/solid_auth_handler.dart +++ b/lib/src/handlers/solid_auth_handler.dart @@ -168,10 +168,10 @@ class SolidAuthHandler { // Use provided images or fallback to SolidLogin's defaults from solidpod package image: _config?.appImage ?? const AssetImage('assets/images/default_image.jpg', - package: 'solidpod'), + package: 'solidpod',), logo: _config?.appLogo ?? const AssetImage('assets/images/default_logo.png', - package: 'solidpod'), + package: 'solidpod',), child: mainAppWidget, ); } diff --git a/lib/src/widgets/solid_login_auth_handler.dart b/lib/src/widgets/solid_login_auth_handler.dart index 948a0a5..b5d76d8 100644 --- a/lib/src/widgets/solid_login_auth_handler.dart +++ b/lib/src/widgets/solid_login_auth_handler.dart @@ -250,7 +250,7 @@ class SolidLoginAuthHandler { ) async { try { debugPrint( - 'SolidLoginAuthHandler: Checking initial structure in background...'); + 'SolidLoginAuthHandler: Checking initial structure in background...',); final resCheckList = await initialStructureTest( defaultFolders, @@ -261,16 +261,16 @@ class SolidLoginAuthHandler { if (allExists) { debugPrint( - 'SolidLoginAuthHandler: Initial structure verified successfully'); + 'SolidLoginAuthHandler: Initial structure verified successfully',); } else { debugPrint( - 'SolidLoginAuthHandler: Initial structure incomplete - user may need to run setup'); + 'SolidLoginAuthHandler: Initial structure incomplete - user may need to run setup',); // In the future, we could show a notification or prompt here // For now, we just log it and let the user discover setup options in the app } } catch (e) { debugPrint( - 'SolidLoginAuthHandler: Background structure check failed: $e'); + 'SolidLoginAuthHandler: Background structure check failed: $e',); // Non-critical error - user can still use the app } } From e830ee41be2ae8e1ffddd6aa77938ed5f4eba2cb Mon Sep 17 00:00:00 2001 From: Williams Date: Mon, 15 Dec 2025 20:54:56 +1100 Subject: [PATCH 15/37] new --- lib/src/handlers/solid_auth_handler.dart | 12 ++++++++---- lib/src/widgets/solid_login_auth_handler.dart | 12 ++++++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/src/handlers/solid_auth_handler.dart b/lib/src/handlers/solid_auth_handler.dart index e777ab0..1e45562 100644 --- a/lib/src/handlers/solid_auth_handler.dart +++ b/lib/src/handlers/solid_auth_handler.dart @@ -167,11 +167,15 @@ class SolidAuthHandler { webID: _config?.defaultServerUrl ?? SolidConfig.defaultServerUrl, // Use provided images or fallback to SolidLogin's defaults from solidpod package image: _config?.appImage ?? - const AssetImage('assets/images/default_image.jpg', - package: 'solidpod',), + const AssetImage( + 'assets/images/default_image.jpg', + package: 'solidpod', + ), logo: _config?.appLogo ?? - const AssetImage('assets/images/default_logo.png', - package: 'solidpod',), + const AssetImage( + 'assets/images/default_logo.png', + package: 'solidpod', + ), child: mainAppWidget, ); } diff --git a/lib/src/widgets/solid_login_auth_handler.dart b/lib/src/widgets/solid_login_auth_handler.dart index b5d76d8..00b8621 100644 --- a/lib/src/widgets/solid_login_auth_handler.dart +++ b/lib/src/widgets/solid_login_auth_handler.dart @@ -250,7 +250,8 @@ class SolidLoginAuthHandler { ) async { try { debugPrint( - 'SolidLoginAuthHandler: Checking initial structure in background...',); + 'SolidLoginAuthHandler: Checking initial structure in background...', + ); final resCheckList = await initialStructureTest( defaultFolders, @@ -261,16 +262,19 @@ class SolidLoginAuthHandler { if (allExists) { debugPrint( - 'SolidLoginAuthHandler: Initial structure verified successfully',); + 'SolidLoginAuthHandler: Initial structure verified successfully', + ); } else { debugPrint( - 'SolidLoginAuthHandler: Initial structure incomplete - user may need to run setup',); + 'SolidLoginAuthHandler: Initial structure incomplete - user may need to run setup', + ); // In the future, we could show a notification or prompt here // For now, we just log it and let the user discover setup options in the app } } catch (e) { debugPrint( - 'SolidLoginAuthHandler: Background structure check failed: $e',); + 'SolidLoginAuthHandler: Background structure check failed: $e', + ); // Non-critical error - user can still use the app } } From 2f96bbb6b85b6412a5bfa29cde5ea0bb09085930 Mon Sep 17 00:00:00 2001 From: Williams Date: Tue, 16 Dec 2025 16:12:59 +1100 Subject: [PATCH 16/37] new --- lib/src/widgets/solid_scaffold.dart | 6 +++++ .../widgets/solid_scaffold_build_helper.dart | 2 ++ .../solid_scaffold_layout_builder.dart | 8 +++++- .../solid_scaffold_widget_builder.dart | 2 ++ lib/src/widgets/solid_status_bar.dart | 26 +++++++++++++++++++ lib/src/widgets/solid_status_bar_models.dart | 14 +++++++++- 6 files changed, 56 insertions(+), 2 deletions(-) diff --git a/lib/src/widgets/solid_scaffold.dart b/lib/src/widgets/solid_scaffold.dart index 8752597..1cdf680 100644 --- a/lib/src/widgets/solid_scaffold.dart +++ b/lib/src/widgets/solid_scaffold.dart @@ -292,6 +292,7 @@ class SolidScaffoldState extends State { final GlobalKey _scaffoldKey = GlobalKey(); SolidSecurityKeyService? _securityKeyService; bool _isKeySaved = false; + bool _isLoadingSecurityKeyStatus = false; bool _isUpdatingSecurityKeyStatus = false; String? _appVersion; bool _isVersionLoaded = false; @@ -380,6 +381,7 @@ class SolidScaffoldState extends State { Future _updateSecurityKeyStatusFromService() async { if (_isUpdatingSecurityKeyStatus) return; _isUpdatingSecurityKeyStatus = true; + if (mounted) setState(() => _isLoadingSecurityKeyStatus = true); try { final isKeySaved = await SolidScaffoldInitHelpers.updateSecurityKeyStatusFromService( @@ -388,6 +390,7 @@ class SolidScaffoldState extends State { ); if (mounted) setState(() => _isKeySaved = isKeySaved); } finally { + if (mounted) setState(() => _isLoadingSecurityKeyStatus = false); _isUpdatingSecurityKeyStatus = false; } } @@ -395,6 +398,7 @@ class SolidScaffoldState extends State { Future _loadSecurityKeyStatus() async { if (_isUpdatingSecurityKeyStatus) return; _isUpdatingSecurityKeyStatus = true; + if (mounted) setState(() => _isLoadingSecurityKeyStatus = true); try { final hasKeyInMemory = await SolidScaffoldInitHelpers.loadSecurityKeyStatus( @@ -412,6 +416,7 @@ class SolidScaffoldState extends State { } catch (e) { if (mounted) setState(() => _isKeySaved = false); } finally { + if (mounted) setState(() => _isLoadingSecurityKeyStatus = false); _isUpdatingSecurityKeyStatus = false; } } @@ -529,6 +534,7 @@ class SolidScaffoldState extends State { isCompatibilityMode: isCompatibilityMode, bodyContent: bodyContent, isKeySaved: _isKeySaved, + isLoadingSecurityKey: _isLoadingSecurityKeyStatus, currentSelectedIndex: _currentSelectedIndex, onMenuSelected: _onMenuSelected, getUsesInternalManagement: _getUsesInternalManagement, diff --git a/lib/src/widgets/solid_scaffold_build_helper.dart b/lib/src/widgets/solid_scaffold_build_helper.dart index 8f4a03f..8a4029b 100644 --- a/lib/src/widgets/solid_scaffold_build_helper.dart +++ b/lib/src/widgets/solid_scaffold_build_helper.dart @@ -124,6 +124,7 @@ class SolidScaffoldBuildHelper { required bool isCompatibilityMode, required Widget? bodyContent, required bool isKeySaved, + bool isLoadingSecurityKey = false, required int currentSelectedIndex, required void Function(int) onMenuSelected, required bool Function() getUsesInternalManagement, @@ -184,6 +185,7 @@ class SolidScaffoldBuildHelper { : SolidScaffoldLayoutBuilder.buildStatusBar( config.statusBar, isKeySaved, + isLoading: isLoadingSecurityKey, ), bottomSheet: config.bottomSheet, persistentFooterButtons: config.persistentFooterButtons, diff --git a/lib/src/widgets/solid_scaffold_layout_builder.dart b/lib/src/widgets/solid_scaffold_layout_builder.dart index 63edcfe..42d1f5b 100644 --- a/lib/src/widgets/solid_scaffold_layout_builder.dart +++ b/lib/src/widgets/solid_scaffold_layout_builder.dart @@ -113,7 +113,11 @@ class SolidScaffoldLayoutBuilder { /// Builds the status bar. - static Widget? buildStatusBar(SolidStatusBarConfig? config, bool isKeySaved) { + static Widget? buildStatusBar( + SolidStatusBarConfig? config, + bool isKeySaved, { + bool isLoading = false, + }) { if (config == null) return null; // Create a modified config with updated security key status. @@ -124,12 +128,14 @@ class SolidScaffoldLayoutBuilder { final originalStatus = config.securityKeyStatus!; final updatedStatus = SolidSecurityKeyStatus( isKeySaved: isKeySaved, + isLoading: isLoading, onTap: originalStatus.onTap, onKeyStatusChanged: originalStatus.onKeyStatusChanged, title: originalStatus.title, appWidget: originalStatus.appWidget, keySavedText: originalStatus.keySavedText, keyNotSavedText: originalStatus.keyNotSavedText, + loadingText: originalStatus.loadingText, tooltip: originalStatus.tooltip, ); diff --git a/lib/src/widgets/solid_scaffold_widget_builder.dart b/lib/src/widgets/solid_scaffold_widget_builder.dart index 3aad1c7..3ca90a8 100644 --- a/lib/src/widgets/solid_scaffold_widget_builder.dart +++ b/lib/src/widgets/solid_scaffold_widget_builder.dart @@ -51,6 +51,7 @@ class SolidScaffoldWidgetBuilder { required bool isCompatibilityMode, required Widget? bodyContent, required bool isKeySaved, + required bool isLoadingSecurityKey, required int currentSelectedIndex, required void Function(int) onMenuSelected, required bool Function() getUsesInternalManagement, @@ -111,6 +112,7 @@ class SolidScaffoldWidgetBuilder { : SolidScaffoldLayoutBuilder.buildStatusBar( widget.statusBar, isKeySaved, + isLoading: isLoadingSecurityKey, ), bottomSheet: widget.bottomSheet, persistentFooterButtons: widget.persistentFooterButtons, diff --git a/lib/src/widgets/solid_status_bar.dart b/lib/src/widgets/solid_status_bar.dart index ac457ad..00290ef 100644 --- a/lib/src/widgets/solid_status_bar.dart +++ b/lib/src/widgets/solid_status_bar.dart @@ -161,6 +161,32 @@ class SolidStatusBar extends StatelessWidget { final theme = Theme.of(context); + // Show loading indicator if status is being loaded + if (securityKeyStatus.isLoading) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: 14, + height: 14, + child: CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation( + theme.colorScheme.primary, + ), + ), + ), + const SizedBox(width: 8), + Text( + securityKeyStatus.displayText, + style: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.primary, + ), + ), + ], + ); + } + // Determine the onTap handler. VoidCallback? onTap = securityKeyStatus.onTap; diff --git a/lib/src/widgets/solid_status_bar_models.dart b/lib/src/widgets/solid_status_bar_models.dart index 16fc554..cad6819 100644 --- a/lib/src/widgets/solid_status_bar_models.dart +++ b/lib/src/widgets/solid_status_bar_models.dart @@ -322,6 +322,10 @@ class SolidSecurityKeyStatus { final bool? isKeySaved; + /// Whether the security key status is currently loading. + + final bool isLoading; + /// Optional callback when security key management is tapped. /// If null, SolidScaffold will handle security key management automatically. @@ -349,25 +353,33 @@ class SolidSecurityKeyStatus { final String? keyNotSavedText; + /// Custom text for loading state (if null, uses default). + + final String? loadingText; + /// Tooltip message for the security key status. final String? tooltip; const SolidSecurityKeyStatus({ this.isKeySaved, + this.isLoading = false, this.onTap, this.onKeyStatusChanged, this.title, this.appWidget, this.keySavedText, this.keyNotSavedText, + this.loadingText, this.tooltip, }); /// Get the display text based on key status. String get displayText { - if (isKeySaved == true) { + if (isLoading) { + return loadingText ?? 'Security Key: Loading...'; + } else if (isKeySaved == true) { return keySavedText ?? 'Security Key: Saved'; } else { return keyNotSavedText ?? 'Security Key: Not Saved'; From ec478aa4af8c72dff8ef14c97dd8c40822cd527f Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 17 Dec 2025 02:36:09 +1100 Subject: [PATCH 17/37] new --- example/pubspec.yaml | 2 ++ lib/src/handlers/solid_auth_handler.dart | 26 ++++++++++++++++++++++++ lib/src/widgets/solid_login.dart | 23 +++++++++++++++++++++ pubspec.yaml | 14 +++++++++++++ 4 files changed, 65 insertions(+) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 734d94d..f2b5c2b 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -19,6 +19,8 @@ dependencies: dev_dependencies: flutter_lints: ^3.0.0 + flutter_test: + sdk: flutter flutter: uses-material-design: true diff --git a/lib/src/handlers/solid_auth_handler.dart b/lib/src/handlers/solid_auth_handler.dart index 1e45562..e3716ae 100644 --- a/lib/src/handlers/solid_auth_handler.dart +++ b/lib/src/handlers/solid_auth_handler.dart @@ -114,6 +114,32 @@ class SolidAuthHandler { _config = config; } + /// Configure default values without overwriting existing configuration. + /// This is used by SolidLogin to provide fallback values while preserving + /// app-specific settings like onSecurityKeyReset. + + void configureDefaults(SolidAuthConfig defaults) { + if (_config == null) { + // No existing config, use defaults + _config = defaults; + } else { + // Merge: keep existing non-null values, fill in missing ones from defaults + _config = SolidAuthConfig( + returnTo: _config!.returnTo ?? defaults.returnTo, + loginPageBuilder: _config!.loginPageBuilder ?? defaults.loginPageBuilder, + defaultServerUrl: _config!.defaultServerUrl ?? defaults.defaultServerUrl, + appTitle: _config!.appTitle ?? defaults.appTitle, + appDirectory: _config!.appDirectory ?? defaults.appDirectory, + appImage: _config!.appImage ?? defaults.appImage, + appLogo: _config!.appLogo ?? defaults.appLogo, + appLink: _config!.appLink ?? defaults.appLink, + loginSuccessWidget: _config!.loginSuccessWidget ?? defaults.loginSuccessWidget, + // IMPORTANT: Preserve app's security key reset callback + onSecurityKeyReset: _config!.onSecurityKeyReset ?? defaults.onSecurityKeyReset, + ); + } + } + /// Handle logout functionality with confirmation popup. Future handleLogout(BuildContext context) async { diff --git a/lib/src/widgets/solid_login.dart b/lib/src/widgets/solid_login.dart index 07302b0..c967955 100644 --- a/lib/src/widgets/solid_login.dart +++ b/lib/src/widgets/solid_login.dart @@ -42,6 +42,7 @@ import 'package:solidpod/solidpod.dart' import 'package:url_launcher/url_launcher.dart'; import 'package:solidui/src/constants/solid_config.dart'; +import 'package:solidui/src/handlers/solid_auth_handler.dart'; import 'package:solidui/src/models/snackbar_config.dart'; import 'package:solidui/src/widgets/solid_login_auth_handler.dart'; import 'package:solidui/src/widgets/solid_login_buttons.dart'; @@ -192,10 +193,32 @@ class _SolidLoginState extends State { // Initialize the controller with the widget's webID _webIdController = TextEditingController(text: widget.webID); + // Auto-configure SolidAuthHandler with this widget's settings + // This ensures the handler works even if the app didn't explicitly configure it + // Apps can override this by calling configure() in main.dart before runApp() + _autoConfigureSolidAuthHandler(); + // dc 20251022: please explain why calling an async without await. _initPackageInfo(); } + // Auto-configure SolidAuthHandler if not already configured by the app. + void _autoConfigureSolidAuthHandler() { + // Use configureDefaults instead of configure to preserve app settings + // This provides working defaults while keeping important app-specific + // configurations like onSecurityKeyReset callback + SolidAuthHandler.instance.configureDefaults( + SolidAuthConfig( + appDirectory: widget.appDirectory, + defaultServerUrl: widget.webID, + appImage: widget.image, + appLogo: widget.logo, + appLink: widget.link, + loginSuccessWidget: widget.child, + ), + ); + } + @override void didUpdateWidget(SolidLogin oldWidget) { super.didUpdateWidget(oldWidget); diff --git a/pubspec.yaml b/pubspec.yaml index 3267c85..1c13c80 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -39,3 +39,17 @@ dev_dependencies: flutter: uses-material-design: true + +dependency_overrides: + solidpod: + git: + url: https://github.com/lambadajester50-byte/solidpod.git + ref: dev + solid_auth: + git: + url: https://github.com/lambadajester50-byte/solid_auth.git + ref: dev + solidui: + git: + url: https://github.com/lambadajester50-byte/solidui.git + ref: dev \ No newline at end of file From 293c5dcd86dda2c1e18a3866ba93d4eca4091573 Mon Sep 17 00:00:00 2001 From: Williams Date: Fri, 19 Dec 2025 21:38:40 +1100 Subject: [PATCH 18/37] all_fixed --- pubspec.yaml | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/pubspec.yaml b/pubspec.yaml index 03bbc9c..220a28c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,18 +38,4 @@ dev_dependencies: window_manager: ^0.5.1 flutter: - uses-material-design: true - -dependency_overrides: - solidpod: - git: - url: https://github.com/lambadajester50-byte/solidpod.git - ref: dev - solid_auth: - git: - url: https://github.com/lambadajester50-byte/solid_auth.git - ref: dev - solidui: - git: - url: https://github.com/lambadajester50-byte/solidui.git - ref: dev \ No newline at end of file + uses-material-design: true \ No newline at end of file From e7db7d6241e46506367562a16fa23f72bcdd1384 Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 25 Dec 2025 13:53:48 +1100 Subject: [PATCH 19/37] new --- .../services/solid_security_key_service.dart | 35 ++++++++++++------- .../solid_security_key_operations.dart | 7 ++-- 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/lib/src/services/solid_security_key_service.dart b/lib/src/services/solid_security_key_service.dart index 95555d7..6cd7104 100644 --- a/lib/src/services/solid_security_key_service.dart +++ b/lib/src/services/solid_security_key_service.dart @@ -47,14 +47,18 @@ class SolidSecurityKeyService extends ChangeNotifier { bool? _lastKnownKeyStatus; - /// Checks if a security key is currently saved. + /// Checks if a security key is currently saved locally. /// - /// Returns true if a security key exists in memory, false otherwise. + /// Returns true if a security key exists in local storage, false otherwise. + /// This method performs a quick local check without server validation. /// This method handles exceptions gracefully and logs errors for debugging. + /// + /// Note: This checks local storage only and doesn't verify against server. + /// For full validation, the key will be verified when actually used. Future isKeySaved() async { try { - return await KeyManager.hasSecurityKey(); + return await KeyManager.hasSecurityKeyLocally(); } catch (e) { debugPrint('Error checking security key status: $e'); return false; @@ -74,9 +78,9 @@ class SolidSecurityKeyService extends ChangeNotifier { Future fetchKeySavedStatus([Function(bool)? onKeyStatusChanged]) async { try { - // Check if the security key exists in memory. + // Check if the security key exists locally (fast, no network required). - final hasKey = await KeyManager.hasSecurityKey(); + final hasKey = await KeyManager.hasSecurityKeyLocally(); // Call the callback if provided. @@ -135,15 +139,22 @@ class SolidSecurityKeyService extends ChangeNotifier { Future isSecurityKeyNeeded() async { try { - // If key already exists, it's not needed to be set. + // First check locally - if key exists, it's not needed to be set. - final hasKey = await KeyManager.hasSecurityKey(); - if (hasKey) return false; + final hasKeyLocally = await KeyManager.hasSecurityKeyLocally(); + if (hasKeyLocally) return false; - // Check if a verification key exists (indicating encryption is used). + // Check if a verification key exists on server (indicating encryption is used). + // This requires network access. - final verificationKey = await KeyManager.getVerificationKey(); - return verificationKey.isNotEmpty; + try { + final verificationKey = await KeyManager.getVerificationKey(); + return verificationKey.isNotEmpty; + } catch (e) { + // If verification key can't be fetched, assume encryption not set up yet + debugPrint('Could not fetch verification key: $e'); + return false; + } } catch (e) { debugPrint('Error checking if security key is needed: $e'); return false; @@ -157,7 +168,7 @@ class SolidSecurityKeyService extends ChangeNotifier { Future validateSecurityKeySetup() async { try { - return await KeyManager.hasSecurityKey(); + return await KeyManager.hasSecurityKeyLocally(); } catch (e) { debugPrint('Error validating security key setup: $e'); return false; diff --git a/lib/src/widgets/solid_security_key_operations.dart b/lib/src/widgets/solid_security_key_operations.dart index 361000a..a804a23 100644 --- a/lib/src/widgets/solid_security_key_operations.dart +++ b/lib/src/widgets/solid_security_key_operations.dart @@ -35,11 +35,14 @@ import 'package:solidpod/solidpod.dart' show KeyManager; /// Helper class for Security Key operations. class SecurityKeyOperations { - /// Checks if a security key exists and is valid. + /// Checks if a security key exists locally. + /// + /// This performs a fast local check without server validation. + /// The key will be validated against the server when actually used. static Future checkKeyStatus() async { try { - final hasKey = await KeyManager.hasSecurityKey(); + final hasKey = await KeyManager.hasSecurityKeyLocally(); debugPrint( 'Security key status check: ${hasKey ? "exists" : "not found"}', ); From bc35243400a01b588251d185ce6b9b351ce55993 Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 25 Dec 2025 14:49:51 +1100 Subject: [PATCH 20/37] new --- .../services/solid_security_key_service.dart | 35 +++++++------------ .../solid_security_key_operations.dart | 7 ++-- 2 files changed, 14 insertions(+), 28 deletions(-) diff --git a/lib/src/services/solid_security_key_service.dart b/lib/src/services/solid_security_key_service.dart index 6cd7104..95555d7 100644 --- a/lib/src/services/solid_security_key_service.dart +++ b/lib/src/services/solid_security_key_service.dart @@ -47,18 +47,14 @@ class SolidSecurityKeyService extends ChangeNotifier { bool? _lastKnownKeyStatus; - /// Checks if a security key is currently saved locally. + /// Checks if a security key is currently saved. /// - /// Returns true if a security key exists in local storage, false otherwise. - /// This method performs a quick local check without server validation. + /// Returns true if a security key exists in memory, false otherwise. /// This method handles exceptions gracefully and logs errors for debugging. - /// - /// Note: This checks local storage only and doesn't verify against server. - /// For full validation, the key will be verified when actually used. Future isKeySaved() async { try { - return await KeyManager.hasSecurityKeyLocally(); + return await KeyManager.hasSecurityKey(); } catch (e) { debugPrint('Error checking security key status: $e'); return false; @@ -78,9 +74,9 @@ class SolidSecurityKeyService extends ChangeNotifier { Future fetchKeySavedStatus([Function(bool)? onKeyStatusChanged]) async { try { - // Check if the security key exists locally (fast, no network required). + // Check if the security key exists in memory. - final hasKey = await KeyManager.hasSecurityKeyLocally(); + final hasKey = await KeyManager.hasSecurityKey(); // Call the callback if provided. @@ -139,22 +135,15 @@ class SolidSecurityKeyService extends ChangeNotifier { Future isSecurityKeyNeeded() async { try { - // First check locally - if key exists, it's not needed to be set. + // If key already exists, it's not needed to be set. - final hasKeyLocally = await KeyManager.hasSecurityKeyLocally(); - if (hasKeyLocally) return false; + final hasKey = await KeyManager.hasSecurityKey(); + if (hasKey) return false; - // Check if a verification key exists on server (indicating encryption is used). - // This requires network access. + // Check if a verification key exists (indicating encryption is used). - try { - final verificationKey = await KeyManager.getVerificationKey(); - return verificationKey.isNotEmpty; - } catch (e) { - // If verification key can't be fetched, assume encryption not set up yet - debugPrint('Could not fetch verification key: $e'); - return false; - } + final verificationKey = await KeyManager.getVerificationKey(); + return verificationKey.isNotEmpty; } catch (e) { debugPrint('Error checking if security key is needed: $e'); return false; @@ -168,7 +157,7 @@ class SolidSecurityKeyService extends ChangeNotifier { Future validateSecurityKeySetup() async { try { - return await KeyManager.hasSecurityKeyLocally(); + return await KeyManager.hasSecurityKey(); } catch (e) { debugPrint('Error validating security key setup: $e'); return false; diff --git a/lib/src/widgets/solid_security_key_operations.dart b/lib/src/widgets/solid_security_key_operations.dart index a804a23..361000a 100644 --- a/lib/src/widgets/solid_security_key_operations.dart +++ b/lib/src/widgets/solid_security_key_operations.dart @@ -35,14 +35,11 @@ import 'package:solidpod/solidpod.dart' show KeyManager; /// Helper class for Security Key operations. class SecurityKeyOperations { - /// Checks if a security key exists locally. - /// - /// This performs a fast local check without server validation. - /// The key will be validated against the server when actually used. + /// Checks if a security key exists and is valid. static Future checkKeyStatus() async { try { - final hasKey = await KeyManager.hasSecurityKeyLocally(); + final hasKey = await KeyManager.hasSecurityKey(); debugPrint( 'Security key status check: ${hasKey ? "exists" : "not found"}', ); From 1b4861e8b1f64eb41b4e68c1fed33c73bece6f27 Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 25 Dec 2025 15:39:21 +1100 Subject: [PATCH 21/37] new --- lib/src/utils/file_operations.dart | 36 +++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/lib/src/utils/file_operations.dart b/lib/src/utils/file_operations.dart index 8576180..09fb2b9 100644 --- a/lib/src/utils/file_operations.dart +++ b/lib/src/utils/file_operations.dart @@ -124,20 +124,36 @@ class FileOperations { if (!context.mounted) continue; - // Read file metadata using relative path from pod root. + // Try to read file metadata using relative path from pod root. // Note: currentPath already contains the full path from pod root // (e.g., "healthpod/data/pathology"), so we use relativeToPod to avoid // path duplication. + + // If reading metadata fails (e.g., permission issues or file format), + // we still want to show the file in the browser, so we catch exceptions. + + try { + final metadata = await readPod( + relativePath, + pathType: PathType.relativeToPod, + ); - final metadata = await readPod( - relativePath, - pathType: PathType.relativeToPod, - ); - - // Add valid files to the processed list. - - if (metadata != SolidFunctionCallStatus.fail.toString() && - metadata != SolidFunctionCallStatus.notLoggedIn.toString()) { + // Add files with successful metadata read. + if (metadata != SolidFunctionCallStatus.fail.toString() && + metadata != SolidFunctionCallStatus.notLoggedIn.toString()) { + processedFiles.add( + FileItem( + name: fileName, + path: relativePath, + dateModified: DateTime.now(), + ), + ); + } + } catch (e) { + // If metadata read fails, still show the file. + // This ensures files like places.json are visible even if + // there are permission or format issues. + debugPrint('Failed to read metadata for $fileName: $e'); processedFiles.add( FileItem( name: fileName, From ec42754661f872f6af99b3ccfea548bdcc69f4fa Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 25 Dec 2025 15:43:54 +1100 Subject: [PATCH 22/37] new --- lib/src/utils/file_operations.dart | 36 +++++++++--------------------- 1 file changed, 10 insertions(+), 26 deletions(-) diff --git a/lib/src/utils/file_operations.dart b/lib/src/utils/file_operations.dart index 09fb2b9..8576180 100644 --- a/lib/src/utils/file_operations.dart +++ b/lib/src/utils/file_operations.dart @@ -124,36 +124,20 @@ class FileOperations { if (!context.mounted) continue; - // Try to read file metadata using relative path from pod root. + // Read file metadata using relative path from pod root. // Note: currentPath already contains the full path from pod root // (e.g., "healthpod/data/pathology"), so we use relativeToPod to avoid // path duplication. - - // If reading metadata fails (e.g., permission issues or file format), - // we still want to show the file in the browser, so we catch exceptions. - - try { - final metadata = await readPod( - relativePath, - pathType: PathType.relativeToPod, - ); - // Add files with successful metadata read. - if (metadata != SolidFunctionCallStatus.fail.toString() && - metadata != SolidFunctionCallStatus.notLoggedIn.toString()) { - processedFiles.add( - FileItem( - name: fileName, - path: relativePath, - dateModified: DateTime.now(), - ), - ); - } - } catch (e) { - // If metadata read fails, still show the file. - // This ensures files like places.json are visible even if - // there are permission or format issues. - debugPrint('Failed to read metadata for $fileName: $e'); + final metadata = await readPod( + relativePath, + pathType: PathType.relativeToPod, + ); + + // Add valid files to the processed list. + + if (metadata != SolidFunctionCallStatus.fail.toString() && + metadata != SolidFunctionCallStatus.notLoggedIn.toString()) { processedFiles.add( FileItem( name: fileName, From 7e32c939cc64d77f8a8fcaa4ca5e292fdd54c5c3 Mon Sep 17 00:00:00 2001 From: Williams Date: Fri, 2 Jan 2026 18:12:43 +1100 Subject: [PATCH 23/37] new --- .../widgets/solid_security_key_manager.dart | 52 ++++++++- .../solid_security_key_manager_dialogs.dart | 107 ++++++++++++++++++ .../solid_security_key_operations.dart | 48 ++++++++ 3 files changed, 206 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/solid_security_key_manager.dart b/lib/src/widgets/solid_security_key_manager.dart index f4bdee9..beaa07c 100644 --- a/lib/src/widgets/solid_security_key_manager.dart +++ b/lib/src/widgets/solid_security_key_manager.dart @@ -31,7 +31,13 @@ library; import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart' - show KeyManager, deleteFile, getEncKeyPath; + show + KeyManager, + deleteFile, + getEncKeyPath, + getFileUrl, + checkResourceStatus, + ResourceStatus; import 'package:solidui/src/services/solid_security_key_notifier.dart'; import 'package:solidui/src/widgets/solid_security_key_manager_dialogs.dart'; @@ -197,9 +203,53 @@ class SolidSecurityKeyManagerState extends State ); return; } + + // CRITICAL FIX: Check if server has enc-keys.ttl before showing new key dialog. + // If server has keys but local doesn't, user needs to RESTORE key, not create new. + // Creating new would overwrite server keys and cause permanent data loss! + + try { + final encKeyPath = await getEncKeyPath(); + final encKeyUrl = await getFileUrl(encKeyPath); + final status = await checkResourceStatus(encKeyUrl, isFile: true); + + if (status == ResourceStatus.exist) { + // Server has keys - show restore key dialog instead of new key dialog. + if (!context.mounted) return; + await _showRestoreKeyDialog(context); + return; + } + } catch (e) { + debugPrint('Error checking server key status: $e'); + // On error, fall through to new key dialog (safer default for new users). + } + + if (!context.mounted) return; return _showNewKeyDialog(context); } + /// Shows dialog to restore/verify an existing security key. + /// Used when server has enc-keys.ttl but local storage doesn't have the key. + Future _showRestoreKeyDialog(BuildContext context) async { + await SolidSecurityKeyManagerDialogs.showRestoreKeyDialog( + context, + _keyController, + () async { + _updateKeyStatusAfterSet(); + }, + (key) async { + return await SecurityKeyOperations.handleRestoreKey( + key, + (message) { + if (mounted) { + SecurityKeyUIHelpers.showErrorSnackBar(this.context, message); + } + }, + ); + }, + ); + } + Future _showNewKeyDialog(BuildContext context) async { await SolidSecurityKeyManagerDialogs.showNewKeyDialog( context, diff --git a/lib/src/widgets/solid_security_key_manager_dialogs.dart b/lib/src/widgets/solid_security_key_manager_dialogs.dart index d67ad35..2788ffd 100644 --- a/lib/src/widgets/solid_security_key_manager_dialogs.dart +++ b/lib/src/widgets/solid_security_key_manager_dialogs.dart @@ -94,6 +94,113 @@ class SolidSecurityKeyManagerDialogs { ); } + /// Shows the restore key dialog for verifying an existing security key. + /// Used when server has enc-keys.ttl but local storage doesn't have the key. + + static Future showRestoreKeyDialog( + BuildContext context, + TextEditingController keyController, + Future Function() onKeyRestored, + Future Function(String key) handleRestoreFunction, + ) async { + keyController.clear(); + bool isLoading = false; + bool obscureText = true; + + return showDialog( + context: context, + barrierDismissible: true, + builder: (context) => StatefulBuilder( + builder: (context, setState) => AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + title: const Row( + children: [ + Icon(Icons.key, color: Colors.orange), + SizedBox(width: 8), + Expanded( + child: Text( + 'Restore Security Key', + style: TextStyle(fontSize: 18), + ), + ), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Your encrypted data was found on the server.', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Please enter the security key you originally set to restore ' + 'access to your encrypted data.', + style: TextStyle(fontSize: 13), + ), + const SizedBox(height: 8), + const Text( + '⚠️ If you forgot your key, your encrypted data cannot be recovered.', + style: TextStyle( + fontSize: 12, + color: Colors.red, + fontStyle: FontStyle.italic, + ), + ), + const SizedBox(height: 16), + TextField( + controller: keyController, + obscureText: obscureText, + decoration: InputDecoration( + labelText: 'Security Key', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon( + obscureText ? Icons.visibility_off : Icons.visibility, + ), + onPressed: () => setState(() => obscureText = !obscureText), + ), + ), + enabled: !isLoading, + ), + ], + ), + actions: [ + TextButton( + onPressed: isLoading ? null : () => Navigator.pop(context), + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: isLoading + ? null + : () async { + setState(() => isLoading = true); + final success = + await handleRestoreFunction(keyController.text); + if (success) { + if (context.mounted) Navigator.pop(context); + await onKeyRestored(); + } else { + setState(() => isLoading = false); + } + }, + child: isLoading + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Restore'), + ), + ], + ), + ), + ); + } + /// Shows the key file not found dialogue. static Future showKeyFileNotFoundDialog( diff --git a/lib/src/widgets/solid_security_key_operations.dart b/lib/src/widgets/solid_security_key_operations.dart index 361000a..13cbddb 100644 --- a/lib/src/widgets/solid_security_key_operations.dart +++ b/lib/src/widgets/solid_security_key_operations.dart @@ -106,4 +106,52 @@ class SecurityKeyOperations { return false; } } + + /// Handles restoring/verifying an existing security key. + /// This is used when the server has enc-keys.ttl but local storage doesn't. + /// Unlike handleKeySubmission, this uses setSecurityKey which VERIFIES + /// against the server's verification key instead of overwriting it. + + static Future handleRestoreKey( + String key, + void Function(String message) showErrorFunction, + ) async { + if (key.isEmpty) { + showErrorFunction('Please enter your security key'); + return false; + } + + try { + // Use setSecurityKey() which verifies against server's verification key. + // This will throw if the key doesn't match. + await KeyManager.setSecurityKey(key); + debugPrint('Security key successfully restored and verified.'); + return true; + } catch (e) { + debugPrint('Error restoring security key: $e'); + String errorMessage = 'Incorrect security key.'; + + final errorStr = e.toString().toLowerCase(); + + if (errorStr.contains('not logged in') || + errorStr.contains('authentication')) { + errorMessage = 'You must be logged in to restore your security key.'; + } else if (errorStr.contains('network') || + errorStr.contains('connection')) { + errorMessage = + 'Network error. Please check your connection and try again.'; + } else if (errorStr.contains('verify')) { + errorMessage = + 'Incorrect security key. Please enter the key you originally set.'; + } + + try { + showErrorFunction(errorMessage); + } catch (displayError) { + debugPrint('Error displaying error message: $displayError'); + } + + return false; + } + } } From ea6be28cc8551f033ce31b1e2a5c232d6322545f Mon Sep 17 00:00:00 2001 From: Williams Date: Fri, 2 Jan 2026 18:34:49 +1100 Subject: [PATCH 24/37] new --- .../widgets/solid_security_key_manager.dart | 35 ++++++ .../solid_security_key_manager_dialogs.dart | 101 ++++++++++++++++-- 2 files changed, 125 insertions(+), 11 deletions(-) diff --git a/lib/src/widgets/solid_security_key_manager.dart b/lib/src/widgets/solid_security_key_manager.dart index beaa07c..1d30eb5 100644 --- a/lib/src/widgets/solid_security_key_manager.dart +++ b/lib/src/widgets/solid_security_key_manager.dart @@ -242,14 +242,49 @@ class SolidSecurityKeyManagerState extends State key, (message) { if (mounted) { + // ignore: use_build_context_synchronously SecurityKeyUIHelpers.showErrorSnackBar(this.context, message); } }, ); }, + onForgotKey: () async { + await _handleForgotKeyReset(); + }, ); } + /// Handles the forgot key reset - deletes all encryption keys and shows new key dialog. + Future _handleForgotKeyReset() async { + try { + // Delete encryption key files from server + final encKeyPath = await getEncKeyPath(); + await deleteFile(encKeyPath, isKey: true); + + // Clear any local key state + await KeyManager.forgetSecurityKey(); + + // Update status + if (mounted) { + setState(() => _hasExistingKey = false); + securityKeyNotifier.updateStatus(false); + widget.onKeyStatusChanged(false); + } + + // Show new key dialog + if (mounted) { + await _showNewKeyDialog(context); + } + } catch (e) { + if (mounted) { + SecurityKeyUIHelpers.showErrorSnackBar( + context, + 'Failed to reset security key: $e', + ); + } + } + } + Future _showNewKeyDialog(BuildContext context) async { await SolidSecurityKeyManagerDialogs.showNewKeyDialog( context, diff --git a/lib/src/widgets/solid_security_key_manager_dialogs.dart b/lib/src/widgets/solid_security_key_manager_dialogs.dart index 2788ffd..fec2c4a 100644 --- a/lib/src/widgets/solid_security_key_manager_dialogs.dart +++ b/lib/src/widgets/solid_security_key_manager_dialogs.dart @@ -101,8 +101,9 @@ class SolidSecurityKeyManagerDialogs { BuildContext context, TextEditingController keyController, Future Function() onKeyRestored, - Future Function(String key) handleRestoreFunction, - ) async { + Future Function(String key) handleRestoreFunction, { + Future Function()? onForgotKey, + }) async { keyController.clear(); bool isLoading = false; bool obscureText = true; @@ -141,15 +142,6 @@ class SolidSecurityKeyManagerDialogs { 'access to your encrypted data.', style: TextStyle(fontSize: 13), ), - const SizedBox(height: 8), - const Text( - '⚠️ If you forgot your key, your encrypted data cannot be recovered.', - style: TextStyle( - fontSize: 12, - color: Colors.red, - fontStyle: FontStyle.italic, - ), - ), const SizedBox(height: 16), TextField( controller: keyController, @@ -166,6 +158,30 @@ class SolidSecurityKeyManagerDialogs { ), enabled: !isLoading, ), + if (onForgotKey != null) ...[ + const SizedBox(height: 12), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: isLoading + ? null + : () async { + Navigator.pop(context); + await _showForgotKeyConfirmation( + context, + onForgotKey, + ); + }, + child: const Text( + 'Forgot Key? Reset All', + style: TextStyle( + color: Colors.red, + fontSize: 12, + ), + ), + ), + ), + ], ], ), actions: [ @@ -201,6 +217,69 @@ class SolidSecurityKeyManagerDialogs { ); } + /// Shows confirmation dialog for forgetting/resetting security key. + static Future _showForgotKeyConfirmation( + BuildContext context, + Future Function() onConfirmReset, + ) async { + final confirmed = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) => AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: const Row( + children: [ + Icon(Icons.warning, color: Colors.red), + SizedBox(width: 8), + Text('Reset Security Key?', style: TextStyle(fontSize: 18)), + ], + ), + content: const Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '⚠️ This will permanently delete ALL your encrypted data!', + style: TextStyle( + fontWeight: FontWeight.bold, + color: Colors.red, + ), + ), + SizedBox(height: 12), + Text( + 'This action cannot be undone. All files encrypted with your ' + 'old security key will become inaccessible forever.', + ), + SizedBox(height: 12), + Text( + 'Only proceed if you are sure you want to start fresh with a ' + 'new security key.', + style: TextStyle(fontSize: 13, color: Colors.grey), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('Cancel'), + ), + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + ), + onPressed: () => Navigator.pop(context, true), + child: const Text('Delete All & Reset'), + ), + ], + ), + ); + + if (confirmed == true && context.mounted) { + await onConfirmReset(); + } + } + /// Shows the key file not found dialogue. static Future showKeyFileNotFoundDialog( From 35b0a3d396a3ac7d6f6e507b6d878abbeb5ffd20 Mon Sep 17 00:00:00 2001 From: Williams Date: Fri, 2 Jan 2026 18:47:40 +1100 Subject: [PATCH 25/37] new --- .../widgets/solid_security_key_manager.dart | 16 +- .../solid_security_key_operations.dart | 153 ++++++++++++++++++ 2 files changed, 165 insertions(+), 4 deletions(-) diff --git a/lib/src/widgets/solid_security_key_manager.dart b/lib/src/widgets/solid_security_key_manager.dart index 1d30eb5..e0b3568 100644 --- a/lib/src/widgets/solid_security_key_manager.dart +++ b/lib/src/widgets/solid_security_key_manager.dart @@ -257,9 +257,12 @@ class SolidSecurityKeyManagerState extends State /// Handles the forgot key reset - deletes all encryption keys and shows new key dialog. Future _handleForgotKeyReset() async { try { - // Delete encryption key files from server + // Delete the main encryption key file from server + // Other key files (ind-keys, pub-key) will be overwritten by initPodKeys final encKeyPath = await getEncKeyPath(); - await deleteFile(encKeyPath, isKey: true); + try { + await deleteFile(encKeyPath, isKey: true); + } catch (_) {} // Clear any local key state await KeyManager.forgetSecurityKey(); @@ -271,9 +274,14 @@ class SolidSecurityKeyManagerState extends State widget.onKeyStatusChanged(false); } - // Show new key dialog + // Initialize new keys - this will create fresh encryption keys if (mounted) { - await _showNewKeyDialog(context); + await SecurityKeyOperations.handleNewKeyInit( + context, + _keyController, + _confirmKeyController, + () async => _updateKeyStatusAfterSet(), + ); } } catch (e) { if (mounted) { diff --git a/lib/src/widgets/solid_security_key_operations.dart b/lib/src/widgets/solid_security_key_operations.dart index 13cbddb..320903f 100644 --- a/lib/src/widgets/solid_security_key_operations.dart +++ b/lib/src/widgets/solid_security_key_operations.dart @@ -154,4 +154,157 @@ class SecurityKeyOperations { return false; } } + + /// Handles initializing new encryption keys after a full reset. + /// Shows the new key dialog and calls initPodKeys to create fresh keys. + static Future handleNewKeyInit( + BuildContext context, + TextEditingController keyController, + TextEditingController confirmKeyController, + Future Function() onKeySet, + ) async { + keyController.clear(); + confirmKeyController.clear(); + + await showDialog( + context: context, + barrierDismissible: false, + builder: (dialogContext) => _NewKeyInitDialog( + keyController: keyController, + confirmKeyController: confirmKeyController, + onKeySet: onKeySet, + ), + ); + } +} + +/// Dialog for setting a new security key after reset. +class _NewKeyInitDialog extends StatefulWidget { + const _NewKeyInitDialog({ + required this.keyController, + required this.confirmKeyController, + required this.onKeySet, + }); + + final TextEditingController keyController; + final TextEditingController confirmKeyController; + final Future Function() onKeySet; + + @override + State<_NewKeyInitDialog> createState() => _NewKeyInitDialogState(); +} + +class _NewKeyInitDialogState extends State<_NewKeyInitDialog> { + bool _isLoading = false; + bool _obscureKey = true; + bool _obscureConfirm = true; + String? _errorMessage; + + @override + Widget build(BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + title: const Row( + children: [ + Icon(Icons.key, color: Colors.green), + SizedBox(width: 8), + Text('Set New Security Key', style: TextStyle(fontSize: 18)), + ], + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Your old encryption keys have been deleted.', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + const Text( + 'Please set a new security key to encrypt your data.', + style: TextStyle(fontSize: 13), + ), + const SizedBox(height: 16), + TextField( + controller: widget.keyController, + obscureText: _obscureKey, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: 'New Security Key', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon(_obscureKey ? Icons.visibility_off : Icons.visibility), + onPressed: () => setState(() => _obscureKey = !_obscureKey), + ), + ), + ), + const SizedBox(height: 12), + TextField( + controller: widget.confirmKeyController, + obscureText: _obscureConfirm, + enabled: !_isLoading, + decoration: InputDecoration( + labelText: 'Confirm Security Key', + border: const OutlineInputBorder(), + suffixIcon: IconButton( + icon: Icon(_obscureConfirm ? Icons.visibility_off : Icons.visibility), + onPressed: () => setState(() => _obscureConfirm = !_obscureConfirm), + ), + ), + ), + if (_errorMessage != null) ...[ + const SizedBox(height: 12), + Text( + _errorMessage!, + style: const TextStyle(color: Colors.red, fontSize: 13), + ), + ], + ], + ), + actions: [ + ElevatedButton( + onPressed: _isLoading ? null : _handleSubmit, + child: _isLoading + ? const SizedBox( + width: 16, + height: 16, + child: CircularProgressIndicator(strokeWidth: 2), + ) + : const Text('Set Key'), + ), + ], + ); + } + + Future _handleSubmit() async { + final key = widget.keyController.text; + final confirmKey = widget.confirmKeyController.text; + + if (key.isEmpty || confirmKey.isEmpty) { + setState(() => _errorMessage = 'Please enter both fields'); + return; + } + + if (key != confirmKey) { + setState(() => _errorMessage = 'Keys do not match'); + return; + } + + setState(() { + _isLoading = true; + _errorMessage = null; + }); + + try { + await KeyManager.initPodKeys(key); + if (!mounted) return; + Navigator.of(context).pop(); + await widget.onKeySet(); + } catch (e) { + setState(() { + _isLoading = false; + _errorMessage = 'Failed to set key: ${e.toString().split('\n').first}'; + }); + } + } } From 3cf1ac0a9ffac6592f9c407739ed9ca829fa8dd1 Mon Sep 17 00:00:00 2001 From: Williams Date: Fri, 2 Jan 2026 20:00:55 +1100 Subject: [PATCH 26/37] new --- .../widgets/solid_security_key_manager.dart | 43 ----- .../solid_security_key_manager_dialogs.dart | 101 ++---------- .../solid_security_key_operations.dart | 153 ------------------ 3 files changed, 11 insertions(+), 286 deletions(-) diff --git a/lib/src/widgets/solid_security_key_manager.dart b/lib/src/widgets/solid_security_key_manager.dart index e0b3568..beaa07c 100644 --- a/lib/src/widgets/solid_security_key_manager.dart +++ b/lib/src/widgets/solid_security_key_manager.dart @@ -242,57 +242,14 @@ class SolidSecurityKeyManagerState extends State key, (message) { if (mounted) { - // ignore: use_build_context_synchronously SecurityKeyUIHelpers.showErrorSnackBar(this.context, message); } }, ); }, - onForgotKey: () async { - await _handleForgotKeyReset(); - }, ); } - /// Handles the forgot key reset - deletes all encryption keys and shows new key dialog. - Future _handleForgotKeyReset() async { - try { - // Delete the main encryption key file from server - // Other key files (ind-keys, pub-key) will be overwritten by initPodKeys - final encKeyPath = await getEncKeyPath(); - try { - await deleteFile(encKeyPath, isKey: true); - } catch (_) {} - - // Clear any local key state - await KeyManager.forgetSecurityKey(); - - // Update status - if (mounted) { - setState(() => _hasExistingKey = false); - securityKeyNotifier.updateStatus(false); - widget.onKeyStatusChanged(false); - } - - // Initialize new keys - this will create fresh encryption keys - if (mounted) { - await SecurityKeyOperations.handleNewKeyInit( - context, - _keyController, - _confirmKeyController, - () async => _updateKeyStatusAfterSet(), - ); - } - } catch (e) { - if (mounted) { - SecurityKeyUIHelpers.showErrorSnackBar( - context, - 'Failed to reset security key: $e', - ); - } - } - } - Future _showNewKeyDialog(BuildContext context) async { await SolidSecurityKeyManagerDialogs.showNewKeyDialog( context, diff --git a/lib/src/widgets/solid_security_key_manager_dialogs.dart b/lib/src/widgets/solid_security_key_manager_dialogs.dart index fec2c4a..2788ffd 100644 --- a/lib/src/widgets/solid_security_key_manager_dialogs.dart +++ b/lib/src/widgets/solid_security_key_manager_dialogs.dart @@ -101,9 +101,8 @@ class SolidSecurityKeyManagerDialogs { BuildContext context, TextEditingController keyController, Future Function() onKeyRestored, - Future Function(String key) handleRestoreFunction, { - Future Function()? onForgotKey, - }) async { + Future Function(String key) handleRestoreFunction, + ) async { keyController.clear(); bool isLoading = false; bool obscureText = true; @@ -142,6 +141,15 @@ class SolidSecurityKeyManagerDialogs { 'access to your encrypted data.', style: TextStyle(fontSize: 13), ), + const SizedBox(height: 8), + const Text( + '⚠️ If you forgot your key, your encrypted data cannot be recovered.', + style: TextStyle( + fontSize: 12, + color: Colors.red, + fontStyle: FontStyle.italic, + ), + ), const SizedBox(height: 16), TextField( controller: keyController, @@ -158,30 +166,6 @@ class SolidSecurityKeyManagerDialogs { ), enabled: !isLoading, ), - if (onForgotKey != null) ...[ - const SizedBox(height: 12), - Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: isLoading - ? null - : () async { - Navigator.pop(context); - await _showForgotKeyConfirmation( - context, - onForgotKey, - ); - }, - child: const Text( - 'Forgot Key? Reset All', - style: TextStyle( - color: Colors.red, - fontSize: 12, - ), - ), - ), - ), - ], ], ), actions: [ @@ -217,69 +201,6 @@ class SolidSecurityKeyManagerDialogs { ); } - /// Shows confirmation dialog for forgetting/resetting security key. - static Future _showForgotKeyConfirmation( - BuildContext context, - Future Function() onConfirmReset, - ) async { - final confirmed = await showDialog( - context: context, - barrierDismissible: false, - builder: (context) => AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Row( - children: [ - Icon(Icons.warning, color: Colors.red), - SizedBox(width: 8), - Text('Reset Security Key?', style: TextStyle(fontSize: 18)), - ], - ), - content: const Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - '⚠️ This will permanently delete ALL your encrypted data!', - style: TextStyle( - fontWeight: FontWeight.bold, - color: Colors.red, - ), - ), - SizedBox(height: 12), - Text( - 'This action cannot be undone. All files encrypted with your ' - 'old security key will become inaccessible forever.', - ), - SizedBox(height: 12), - Text( - 'Only proceed if you are sure you want to start fresh with a ' - 'new security key.', - style: TextStyle(fontSize: 13, color: Colors.grey), - ), - ], - ), - actions: [ - TextButton( - onPressed: () => Navigator.pop(context, false), - child: const Text('Cancel'), - ), - ElevatedButton( - style: ElevatedButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, - ), - onPressed: () => Navigator.pop(context, true), - child: const Text('Delete All & Reset'), - ), - ], - ), - ); - - if (confirmed == true && context.mounted) { - await onConfirmReset(); - } - } - /// Shows the key file not found dialogue. static Future showKeyFileNotFoundDialog( diff --git a/lib/src/widgets/solid_security_key_operations.dart b/lib/src/widgets/solid_security_key_operations.dart index 320903f..13cbddb 100644 --- a/lib/src/widgets/solid_security_key_operations.dart +++ b/lib/src/widgets/solid_security_key_operations.dart @@ -154,157 +154,4 @@ class SecurityKeyOperations { return false; } } - - /// Handles initializing new encryption keys after a full reset. - /// Shows the new key dialog and calls initPodKeys to create fresh keys. - static Future handleNewKeyInit( - BuildContext context, - TextEditingController keyController, - TextEditingController confirmKeyController, - Future Function() onKeySet, - ) async { - keyController.clear(); - confirmKeyController.clear(); - - await showDialog( - context: context, - barrierDismissible: false, - builder: (dialogContext) => _NewKeyInitDialog( - keyController: keyController, - confirmKeyController: confirmKeyController, - onKeySet: onKeySet, - ), - ); - } -} - -/// Dialog for setting a new security key after reset. -class _NewKeyInitDialog extends StatefulWidget { - const _NewKeyInitDialog({ - required this.keyController, - required this.confirmKeyController, - required this.onKeySet, - }); - - final TextEditingController keyController; - final TextEditingController confirmKeyController; - final Future Function() onKeySet; - - @override - State<_NewKeyInitDialog> createState() => _NewKeyInitDialogState(); -} - -class _NewKeyInitDialogState extends State<_NewKeyInitDialog> { - bool _isLoading = false; - bool _obscureKey = true; - bool _obscureConfirm = true; - String? _errorMessage; - - @override - Widget build(BuildContext context) { - return AlertDialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - title: const Row( - children: [ - Icon(Icons.key, color: Colors.green), - SizedBox(width: 8), - Text('Set New Security Key', style: TextStyle(fontSize: 18)), - ], - ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Your old encryption keys have been deleted.', - style: TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - const Text( - 'Please set a new security key to encrypt your data.', - style: TextStyle(fontSize: 13), - ), - const SizedBox(height: 16), - TextField( - controller: widget.keyController, - obscureText: _obscureKey, - enabled: !_isLoading, - decoration: InputDecoration( - labelText: 'New Security Key', - border: const OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon(_obscureKey ? Icons.visibility_off : Icons.visibility), - onPressed: () => setState(() => _obscureKey = !_obscureKey), - ), - ), - ), - const SizedBox(height: 12), - TextField( - controller: widget.confirmKeyController, - obscureText: _obscureConfirm, - enabled: !_isLoading, - decoration: InputDecoration( - labelText: 'Confirm Security Key', - border: const OutlineInputBorder(), - suffixIcon: IconButton( - icon: Icon(_obscureConfirm ? Icons.visibility_off : Icons.visibility), - onPressed: () => setState(() => _obscureConfirm = !_obscureConfirm), - ), - ), - ), - if (_errorMessage != null) ...[ - const SizedBox(height: 12), - Text( - _errorMessage!, - style: const TextStyle(color: Colors.red, fontSize: 13), - ), - ], - ], - ), - actions: [ - ElevatedButton( - onPressed: _isLoading ? null : _handleSubmit, - child: _isLoading - ? const SizedBox( - width: 16, - height: 16, - child: CircularProgressIndicator(strokeWidth: 2), - ) - : const Text('Set Key'), - ), - ], - ); - } - - Future _handleSubmit() async { - final key = widget.keyController.text; - final confirmKey = widget.confirmKeyController.text; - - if (key.isEmpty || confirmKey.isEmpty) { - setState(() => _errorMessage = 'Please enter both fields'); - return; - } - - if (key != confirmKey) { - setState(() => _errorMessage = 'Keys do not match'); - return; - } - - setState(() { - _isLoading = true; - _errorMessage = null; - }); - - try { - await KeyManager.initPodKeys(key); - if (!mounted) return; - Navigator.of(context).pop(); - await widget.onKeySet(); - } catch (e) { - setState(() { - _isLoading = false; - _errorMessage = 'Failed to set key: ${e.toString().split('\n').first}'; - }); - } - } } From 1d8bb14ed61d5c503276a10a4be28d58074d4551 Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 7 Jan 2026 02:33:53 +1100 Subject: [PATCH 27/37] new --- lib/solidui.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/solidui.dart b/lib/solidui.dart index a09d456..ee63a79 100644 --- a/lib/solidui.dart +++ b/lib/solidui.dart @@ -95,6 +95,7 @@ export 'src/utils/solid_file_operations.dart'; export 'src/utils/solid_alert.dart'; export 'src/utils/solid_pod_helpers.dart' show loginIfRequired, getKeyFromUserIfRequired; +export 'src/utils/solid_notifications.dart'; export 'src/widgets/solid_format_info_card.dart'; From e0b92926e02d4f54d3c9280cee4f04e6c2acb380 Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 7 Jan 2026 22:12:19 +1100 Subject: [PATCH 28/37] fix_issue_overflow --- .../solid_scaffold_appbar_overflow.dart | 24 +++++++++---------- lib/src/widgets/solid_scaffold_helpers.dart | 6 +++-- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/src/widgets/solid_scaffold_appbar_overflow.dart b/lib/src/widgets/solid_scaffold_appbar_overflow.dart index 4965933..b03c8a4 100644 --- a/lib/src/widgets/solid_scaffold_appbar_overflow.dart +++ b/lib/src/widgets/solid_scaffold_appbar_overflow.dart @@ -113,10 +113,10 @@ class SolidAppBarOverflowHandler { final isInOverflow = actionConfig?.showInOverflow ?? false; - // On narrow screens, only show in overflow if showInOverflow = true. - // Buttons marked as "add to appbar" (showInOverflow = false) stay in AppBar. + // On narrow screens (forceOverflow = true), always show visible buttons + // in overflow menu regardless of showInOverflow setting. - if (forceOverflow) return isInOverflow; + if (forceOverflow) return true; return isInOverflow; } @@ -135,10 +135,10 @@ class SolidAppBarOverflowHandler { final isInOverflow = actionConfig?.showInOverflow ?? false; - // On narrow screens, only show in overflow if showInOverflow = true. - // Buttons marked as "add to appbar" (showInOverflow = false) stay in AppBar. + // On narrow screens (forceOverflow = true), always show visible buttons + // in overflow menu regardless of showInOverflow setting. - if (forceOverflow) return isInOverflow; + if (forceOverflow) return true; return isInOverflow; } @@ -156,10 +156,10 @@ class SolidAppBarOverflowHandler { final isInOverflow = actionConfig?.showInOverflow ?? false; - // On narrow screens, only show in overflow if showInOverflow = true. - // Buttons marked as "add to appbar" (showInOverflow = false) stay in AppBar. + // On narrow screens (forceOverflow = true), always show visible buttons + // in overflow menu regardless of showInOverflow setting. - if (forceOverflow) return isInOverflow; + if (forceOverflow) return true; return isInOverflow; } @@ -177,10 +177,10 @@ class SolidAppBarOverflowHandler { final isInOverflow = actionConfig?.showInOverflow ?? false; - // On narrow screens, only show in overflow if showInOverflow = true. - // Buttons marked as "add to appbar" (showInOverflow = false) stay in AppBar. + // On narrow screens (forceOverflow = true), always show visible buttons + // in overflow menu regardless of showInOverflow setting. - if (forceOverflow) return isInOverflow; + if (forceOverflow) return true; return isInOverflow; } diff --git a/lib/src/widgets/solid_scaffold_helpers.dart b/lib/src/widgets/solid_scaffold_helpers.dart index f8d9e88..2db78a9 100644 --- a/lib/src/widgets/solid_scaffold_helpers.dart +++ b/lib/src/widgets/solid_scaffold_helpers.dart @@ -155,9 +155,11 @@ class SolidScaffoldHelpers { ..sort((a, b) => a.order.compareTo(b.order)); for (final actionItem in allActions) { - // Skip if not visible or not marked for overflow. + // Skip if not visible. + // Note: showInOverflow check is handled by hasXxxInOverflow parameters, + // which already account for narrow screen behavior. - if (!actionItem.isVisible || !actionItem.showInOverflow) continue; + if (!actionItem.isVisible) continue; // Handle each action type. From 2eca185e77179b62062f398b4af8c8efeac3632b Mon Sep 17 00:00:00 2001 From: Williams Date: Mon, 12 Jan 2026 20:47:45 +0800 Subject: [PATCH 29/37] fix_issue --- .../widgets/solid_security_key_manager.dart | 18 +++++++++--------- lib/src/widgets/solid_status_bar.dart | 3 +-- pubspec.yaml | 3 ++- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/src/widgets/solid_security_key_manager.dart b/lib/src/widgets/solid_security_key_manager.dart index beaa07c..a6123ca 100644 --- a/lib/src/widgets/solid_security_key_manager.dart +++ b/lib/src/widgets/solid_security_key_manager.dart @@ -30,7 +30,7 @@ library; import 'package:flutter/material.dart'; -import 'package:solidpod/solidpod.dart' +import 'package:solidpod/solidpod.dart' as solidpod show KeyManager, deleteFile, @@ -209,11 +209,11 @@ class SolidSecurityKeyManagerState extends State // Creating new would overwrite server keys and cause permanent data loss! try { - final encKeyPath = await getEncKeyPath(); - final encKeyUrl = await getFileUrl(encKeyPath); - final status = await checkResourceStatus(encKeyUrl, isFile: true); + final encKeyPath = await solidpod.getEncKeyPath(); + final encKeyUrl = await solidpod.getFileUrl(encKeyPath); + final status = await solidpod.checkResourceStatus(encKeyUrl, isFile: true); - if (status == ResourceStatus.exist) { + if (status == solidpod.ResourceStatus.exist) { // Server has keys - show restore key dialog instead of new key dialog. if (!context.mounted) return; await _showRestoreKeyDialog(context); @@ -303,7 +303,7 @@ class SolidSecurityKeyManagerState extends State 'The security key file could not be found. ' 'Would you like to set a new security key?', ); - await KeyManager.forgetSecurityKey(); + await solidpod.KeyManager.forgetSecurityKey(); await _checkKeyStatus(); if (context.mounted) { await _showKeyInputDialog(context); @@ -337,14 +337,14 @@ class SolidSecurityKeyManagerState extends State try { // Clear the key from memory. - await KeyManager.forgetSecurityKey(); + await solidpod.KeyManager.forgetSecurityKey(); // Delete the key file from POD. // IMPORTANT: Use isKey: true to skip permission revocation // because encryption files are NOT in the data directory - final encKeyPath = await getEncKeyPath(); - await deleteFile(encKeyPath, isKey: true); + final encKeyPath = await solidpod.getEncKeyPath(); + await solidpod.deleteFile(encKeyPath, isKey: true); success = true; msg = 'Successfully forgot local security key.'; diff --git a/lib/src/widgets/solid_status_bar.dart b/lib/src/widgets/solid_status_bar.dart index 00290ef..5be139d 100644 --- a/lib/src/widgets/solid_status_bar.dart +++ b/lib/src/widgets/solid_status_bar.dart @@ -32,10 +32,9 @@ library; import 'package:flutter/material.dart'; -import 'package:solidpod/solidpod.dart' show getWebId; - import 'package:gap/gap.dart'; import 'package:markdown_tooltip/markdown_tooltip.dart'; +import 'package:solidpod/solidpod.dart' show getWebId; import 'package:url_launcher/url_launcher.dart'; import 'package:solidui/src/constants/navigation.dart'; diff --git a/pubspec.yaml b/pubspec.yaml index 4432400..0da019d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -31,10 +31,11 @@ dependencies: solidpod: ^0.9.0 url_launcher: ^6.3.2 version_widget: ^1.0.6 - web: ^1.1.0 dev_dependencies: flutter_lints: ^6.0.0 + flutter_test: + sdk: flutter window_manager: ^0.5.1 flutter: From 96550196d07f4d4f63c0ae2c0c22c0bdac0cc458 Mon Sep 17 00:00:00 2001 From: Williams Date: Mon, 12 Jan 2026 20:47:45 +0800 Subject: [PATCH 30/37] add_support_example_login --- example/lib/main.dart | 22 ++++++++++++++++++++++ example/web/callback.html | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 example/web/callback.html diff --git a/example/lib/main.dart b/example/lib/main.dart index 8d79912..062cf75 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,9 +30,12 @@ library; import 'package:flutter/material.dart'; +import 'package:solidpod/solidpod.dart' show KeyManager, setAppDirName; +import 'package:solidui/solidui.dart'; import 'package:window_manager/window_manager.dart'; import 'app.dart'; +import 'app_scaffold.dart'; import 'constants/app.dart'; import 'utils/is_desktop.dart'; @@ -41,6 +44,25 @@ import 'utils/is_desktop.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); + // CRITICAL: Set app directory name BEFORE any Pod operations + await setAppDirName('myapp'); + + // Configure SolidAuthHandler with app-specific settings + SolidAuthHandler.instance.configure( + SolidAuthConfig( + appTitle: appTitle, + appDirectory: 'myapp', + defaultServerUrl: 'https://pods.solidcommunity.au', + appImage: const AssetImage('assets/images/app_image.jpg'), + appLogo: const AssetImage('assets/images/app_icon.jpg'), + loginSuccessWidget: appScaffold, + onSecurityKeyReset: () async { + await KeyManager.clear(); + debugPrint('MyApp: Security key cleared on logout'); + }, + ), + ); + // Set window options for desktop platforms (Windows, Linux, macOS). if (isDesktop) { diff --git a/example/web/callback.html b/example/web/callback.html new file mode 100644 index 0000000..bb7c891 --- /dev/null +++ b/example/web/callback.html @@ -0,0 +1,37 @@ + + + + + Authentication Callback + + + +

Authentication complete. You can close this window.

+

If this window does not close automatically, please close it manually.

+ + From 8721568fca6e2b29df2f94f88d9047fd81f84034 Mon Sep 17 00:00:00 2001 From: Williams Date: Mon, 12 Jan 2026 21:09:42 +0800 Subject: [PATCH 31/37] format_issue --- lib/src/widgets/solid_security_key_manager.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/solid_security_key_manager.dart b/lib/src/widgets/solid_security_key_manager.dart index a6123ca..cd2da73 100644 --- a/lib/src/widgets/solid_security_key_manager.dart +++ b/lib/src/widgets/solid_security_key_manager.dart @@ -211,7 +211,8 @@ class SolidSecurityKeyManagerState extends State try { final encKeyPath = await solidpod.getEncKeyPath(); final encKeyUrl = await solidpod.getFileUrl(encKeyPath); - final status = await solidpod.checkResourceStatus(encKeyUrl, isFile: true); + final status = + await solidpod.checkResourceStatus(encKeyUrl, isFile: true); if (status == solidpod.ResourceStatus.exist) { // Server has keys - show restore key dialog instead of new key dialog. From 8b94958475394df26e6585cb9e221f8faad0a954 Mon Sep 17 00:00:00 2001 From: Williams Date: Wed, 14 Jan 2026 01:34:40 +0800 Subject: [PATCH 32/37] resolve import order --- example/lib/main.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 062cf75..be05d1a 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -31,9 +31,10 @@ library; import 'package:flutter/material.dart'; import 'package:solidpod/solidpod.dart' show KeyManager, setAppDirName; -import 'package:solidui/solidui.dart'; import 'package:window_manager/window_manager.dart'; +import 'package:solidui/solidui.dart'; + import 'app.dart'; import 'app_scaffold.dart'; import 'constants/app.dart'; From 25c4cac7fc779ff3dae9d15719b2f8a2ceda4ede Mon Sep 17 00:00:00 2001 From: Tony Chen <128760989+tonypioneer@users.noreply.github.com> Date: Wed, 14 Jan 2026 23:36:20 +1100 Subject: [PATCH 33/37] Lint --- example/lib/main.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index be05d1a..5dc56a7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -45,10 +45,12 @@ import 'utils/is_desktop.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); - // CRITICAL: Set app directory name BEFORE any Pod operations + // CRITICAL: Set app directory name BEFORE any Pod operations. + await setAppDirName('myapp'); - // Configure SolidAuthHandler with app-specific settings + // Configure SolidAuthHandler with app-specific settings. + SolidAuthHandler.instance.configure( SolidAuthConfig( appTitle: appTitle, From 4c63276b45ba9bcc9d5623bb0ddc33bb06323543 Mon Sep 17 00:00:00 2001 From: Tony Chen <128760989+tonypioneer@users.noreply.github.com> Date: Wed, 14 Jan 2026 23:38:00 +1100 Subject: [PATCH 34/37] Lint --- lib/src/widgets/solid_login.dart | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/src/widgets/solid_login.dart b/lib/src/widgets/solid_login.dart index 2951a3c..2d48f79 100644 --- a/lib/src/widgets/solid_login.dart +++ b/lib/src/widgets/solid_login.dart @@ -216,7 +216,8 @@ class _SolidLoginState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); solidThemeNotifier.addListener(_onThemeChanged); - // Initialize the controller with the widget's webID + // Initialise the controller with the widget's webID. + _webIdController = TextEditingController(text: widget.webID); // Auto-configure SolidAuthHandler with this widget's settings @@ -238,10 +239,12 @@ class _SolidLoginState extends State with WidgetsBindingObserver { } // Auto-configure SolidAuthHandler if not already configured by the app. + void _autoConfigureSolidAuthHandler() { // Use configureDefaults instead of configure to preserve app settings // This provides working defaults while keeping important app-specific - // configurations like onSecurityKeyReset callback + // configurations like onSecurityKeyReset callback. + SolidAuthHandler.instance.configureDefaults( SolidAuthConfig( appDirectory: widget.appDirectory, @@ -261,14 +264,16 @@ class _SolidLoginState extends State with WidgetsBindingObserver { // Always reset the controller text to widget.webID when widget updates // This ensures fresh state when returning from guest mode, even if the user // had manually modified the URL field before leaving - // Only skip reset if the current text already matches the intended value + // Only skip reset if the current text already matches the intended value. + if (_webIdController.text != widget.webID) { _webIdController.text = widget.webID; } // CRITICAL: Reset appDirName if appDirectory changed // This fixes the double-slash bug when returning from guest mode - // Without this, appDirName stays empty causing paths like //data/places.json + // Without this, appDirName stays empty causing paths like //data/places.json. + if (oldWidget.appDirectory != widget.appDirectory) { setAppDirName(widget.appDirectory); } From c732c7b0e7414114edc28cf4fe67a19d763718e4 Mon Sep 17 00:00:00 2001 From: Tony Chen <128760989+tonypioneer@users.noreply.github.com> Date: Wed, 14 Jan 2026 23:39:15 +1100 Subject: [PATCH 35/37] Lint --- lib/src/widgets/solid_login.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/src/widgets/solid_login.dart b/lib/src/widgets/solid_login.dart index 2d48f79..27a1517 100644 --- a/lib/src/widgets/solid_login.dart +++ b/lib/src/widgets/solid_login.dart @@ -222,7 +222,8 @@ class _SolidLoginState extends State with WidgetsBindingObserver { // Auto-configure SolidAuthHandler with this widget's settings // This ensures the handler works even if the app didn't explicitly configure it - // Apps can override this by calling configure() in main.dart before runApp() + // Apps can override this by calling configure() in main.dart before runApp(). + _autoConfigureSolidAuthHandler(); // Initialise focus nodes for keyboard navigation. From 48fbe4251dadc3186e8e577bbd93a4e724f92330 Mon Sep 17 00:00:00 2001 From: Tony Chen <128760989+tonypioneer@users.noreply.github.com> Date: Wed, 14 Jan 2026 23:40:07 +1100 Subject: [PATCH 36/37] Lint --- lib/src/widgets/solid_status_bar.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/src/widgets/solid_status_bar.dart b/lib/src/widgets/solid_status_bar.dart index 5be139d..e6e3b7e 100644 --- a/lib/src/widgets/solid_status_bar.dart +++ b/lib/src/widgets/solid_status_bar.dart @@ -160,7 +160,8 @@ class SolidStatusBar extends StatelessWidget { final theme = Theme.of(context); - // Show loading indicator if status is being loaded + // Show loading indicator if status is being loaded. + if (securityKeyStatus.isLoading) { return Row( mainAxisSize: MainAxisSize.min, @@ -216,12 +217,14 @@ class SolidStatusBar extends StatelessWidget { SolidSecurityKeyStatus config, ) async { // Import at top: import 'package:solidpod/solidpod.dart' show getWebId; - // Check if user is logged in first + // Check if user is logged in first. + try { final webId = await getWebId(); if (webId == null || webId.isEmpty) { - // Show friendly login prompt + // Show friendly login prompt. + if (!context.mounted) return; await showDialog( context: context, From fb712d82e227ad4d1b648ae0defce11d80919b6b Mon Sep 17 00:00:00 2001 From: Williams Date: Thu, 15 Jan 2026 00:17:10 +0800 Subject: [PATCH 37/37] add_new_lines --- .../initial_setup_widgets/res_create_form_submission.dart | 1 + lib/src/utils/file_operations.dart | 1 + lib/src/widgets/build_message_container.dart | 3 +++ lib/src/widgets/solid_dynamic_login_status.dart | 3 +++ lib/src/widgets/solid_format_info_card.dart | 6 ++++++ lib/src/widgets/solid_login.dart | 8 +++++++- lib/src/widgets/solid_login_helper.dart | 1 + lib/src/widgets/solid_logout_dialog.dart | 1 + lib/src/widgets/solid_status_bar.dart | 1 + 9 files changed, 24 insertions(+), 1 deletion(-) diff --git a/lib/src/screens/initial_setup_widgets/res_create_form_submission.dart b/lib/src/screens/initial_setup_widgets/res_create_form_submission.dart index c85e6e6..81c00eb 100644 --- a/lib/src/screens/initial_setup_widgets/res_create_form_submission.dart +++ b/lib/src/screens/initial_setup_widgets/res_create_form_submission.dart @@ -57,6 +57,7 @@ ElevatedButton resCreateFormSubmission( Widget child, ) { // Use MediaQuery to determine the screen width and adjust the font size accordingly. + final screenWidth = MediaQuery.of(context).size.width; final isSmallDevice = screenWidth < 360; // A threshold for small devices, can be adjusted. diff --git a/lib/src/utils/file_operations.dart b/lib/src/utils/file_operations.dart index 9d9acdf..02af115 100644 --- a/lib/src/utils/file_operations.dart +++ b/lib/src/utils/file_operations.dart @@ -112,6 +112,7 @@ class FileOperations { // Skip ACL files and metadata files, but include all other file types // (TTL, JSON, CSV, TXT, etc.) + if (fileName.endsWith('.acl') || fileName.endsWith('.meta')) { continue; } diff --git a/lib/src/widgets/build_message_container.dart b/lib/src/widgets/build_message_container.dart index fcab854..534e0a4 100644 --- a/lib/src/widgets/build_message_container.dart +++ b/lib/src/widgets/build_message_container.dart @@ -111,6 +111,7 @@ Container buildMsgBox( String msg, ) { // Zheyuan might need to use isRTL in the future + // ignore: unused_local_variable var isRTL = false; @@ -127,10 +128,12 @@ Container buildMsgBox( } // Determine device type for layout adjustments + final isMobile = size.width <= 730; final isTablet = size.width > 730 && size.width <= 1050; // Minimal horizontal padding for all devices + final horizontalPadding = size.width * 0.01; // Adjust this value to increase or decrease padding diff --git a/lib/src/widgets/solid_dynamic_login_status.dart b/lib/src/widgets/solid_dynamic_login_status.dart index 80e7c86..f5b6741 100644 --- a/lib/src/widgets/solid_dynamic_login_status.dart +++ b/lib/src/widgets/solid_dynamic_login_status.dart @@ -136,6 +136,7 @@ class _SolidDynamicLoginStatusState extends State { if (isCurrentlyLoggedIn) { // Logout scenario - don't recheck status as page will reload + if (widget.onTap != null) { widget.onTap!.call(); } else { @@ -143,6 +144,7 @@ class _SolidDynamicLoginStatusState extends State { } } else { // Login scenario - can delay status check + if (widget.onLogin != null) { widget.onLogin!.call(); } else { @@ -150,6 +152,7 @@ class _SolidDynamicLoginStatusState extends State { } // Only refresh status for login scenario + Future.delayed(const Duration(milliseconds: 500), () { if (mounted) { _checkLoginStatus(); diff --git a/lib/src/widgets/solid_format_info_card.dart b/lib/src/widgets/solid_format_info_card.dart index 1ac1130..7c845f3 100644 --- a/lib/src/widgets/solid_format_info_card.dart +++ b/lib/src/widgets/solid_format_info_card.dart @@ -77,6 +77,7 @@ class SolidFormatInfoCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header with icon and title. + Row( children: [ Icon( @@ -98,6 +99,7 @@ class SolidFormatInfoCard extends StatelessWidget { ), // Description if provided. + if (config.description != null) ...[ const SizedBox(height: 8), Text( @@ -111,6 +113,7 @@ class SolidFormatInfoCard extends StatelessWidget { const SizedBox(height: 12), // Required fields section. + Text( 'Required Fields:', style: TextStyle( @@ -137,6 +140,7 @@ class SolidFormatInfoCard extends StatelessWidget { ), // Optional fields section. + if (config.optionalFields.isNotEmpty) ...[ const SizedBox(height: 12), Text( @@ -166,6 +170,7 @@ class SolidFormatInfoCard extends StatelessWidget { ], // Format type indicator. + const SizedBox(height: 12), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), @@ -188,6 +193,7 @@ class SolidFormatInfoCard extends StatelessWidget { ), // Note text. + const SizedBox(height: 8), Text( config.isJson diff --git a/lib/src/widgets/solid_login.dart b/lib/src/widgets/solid_login.dart index 2951a3c..53992ca 100644 --- a/lib/src/widgets/solid_login.dart +++ b/lib/src/widgets/solid_login.dart @@ -62,6 +62,7 @@ class SolidLogin extends StatefulWidget { const SolidLogin({ // Include the literals here so that they are exposed through the docs. + required this.child, this.required = false, this.appDirectory = '', @@ -216,12 +217,14 @@ class _SolidLoginState extends State with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); solidThemeNotifier.addListener(_onThemeChanged); - // Initialize the controller with the widget's webID + // Initialise the controller with the widget's WebID. + _webIdController = TextEditingController(text: widget.webID); // Auto-configure SolidAuthHandler with this widget's settings // This ensures the handler works even if the app didn't explicitly configure it // Apps can override this by calling configure() in main.dart before runApp() + _autoConfigureSolidAuthHandler(); // Initialise focus nodes for keyboard navigation. @@ -238,6 +241,7 @@ class _SolidLoginState extends State with WidgetsBindingObserver { } // Auto-configure SolidAuthHandler if not already configured by the app. + void _autoConfigureSolidAuthHandler() { // Use configureDefaults instead of configure to preserve app settings // This provides working defaults while keeping important app-specific @@ -262,6 +266,7 @@ class _SolidLoginState extends State with WidgetsBindingObserver { // This ensures fresh state when returning from guest mode, even if the user // had manually modified the URL field before leaving // Only skip reset if the current text already matches the intended value + if (_webIdController.text != widget.webID) { _webIdController.text = widget.webID; } @@ -269,6 +274,7 @@ class _SolidLoginState extends State with WidgetsBindingObserver { // CRITICAL: Reset appDirName if appDirectory changed // This fixes the double-slash bug when returning from guest mode // Without this, appDirName stays empty causing paths like //data/places.json + if (oldWidget.appDirectory != widget.appDirectory) { setAppDirName(widget.appDirectory); } diff --git a/lib/src/widgets/solid_login_helper.dart b/lib/src/widgets/solid_login_helper.dart index 990b3a4..95879ac 100644 --- a/lib/src/widgets/solid_login_helper.dart +++ b/lib/src/widgets/solid_login_helper.dart @@ -376,6 +376,7 @@ Future pushReplacement( ) async { // Use simple pushReplacement instead of pushAndRemoveUntil // This preserves the navigation history and doesn't destroy all widgets + await Navigator.of(context).pushReplacement( MaterialPageRoute( builder: (context) => destinationWidget, diff --git a/lib/src/widgets/solid_logout_dialog.dart b/lib/src/widgets/solid_logout_dialog.dart index f0c1f5f..e55afeb 100644 --- a/lib/src/widgets/solid_logout_dialog.dart +++ b/lib/src/widgets/solid_logout_dialog.dart @@ -57,6 +57,7 @@ class _LogoutDialogState extends State { if (await logoutPod()) { // Navigate to login page after successful logout // Works consistently across all platforms + if (context.mounted) { await Navigator.pushReplacement( context, diff --git a/lib/src/widgets/solid_status_bar.dart b/lib/src/widgets/solid_status_bar.dart index 5be139d..5dbb04b 100644 --- a/lib/src/widgets/solid_status_bar.dart +++ b/lib/src/widgets/solid_status_bar.dart @@ -217,6 +217,7 @@ class SolidStatusBar extends StatelessWidget { ) async { // Import at top: import 'package:solidpod/solidpod.dart' show getWebId; // Check if user is logged in first + try { final webId = await getWebId();