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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 22 additions & 23 deletions lib/src/widgets/solid_login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -178,10 +178,6 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {

AssetImage? _resolvedLogo;

/// Whether asset resolution has completed.

bool _assetsResolved = false;

@override
void initState() {
super.initState();
Expand Down Expand Up @@ -233,7 +229,9 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
widget.logo,
'app_icon',
);
if (mounted) setState(() => _assetsResolved = true);
// Trigger a rebuild so the higher-quality resolved asset replaces the
// initial widget.image/widget.logo fallback.
if (mounted) setState(() {});
}

@override
Expand Down Expand Up @@ -269,18 +267,23 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {

Future<void> _initPackageInfo() async {
if (!mounted) return;
// Start fetching app info immediately — it is independent of appDirName.
final appInfoFuture = getAppNameVersion();
await setAppDirName(widget.appDirectory);
final folders = await generateDefaultFolders();
final files = await generateDefaultFiles();
// Now that appDirName is set, resolve folders/files in parallel with the
// already-running appInfo future.
final results = await Future.wait([
generateDefaultFolders(),
generateDefaultFiles(),
appInfoFuture,
]);
final customFolders = generateCustomFolders(widget.customFolderPathList);
if (!mounted) return;
// Single setState: avoids two separate widget rebuilds.
setState(() {
defaultFolders = folders + customFolders;
defaultFiles = files;
});
final appInfo = await getAppNameVersion();
if (!mounted) return;
setState(() {
defaultFolders = (results[0] as List<String>) + customFolders;
defaultFiles = results[1] as Map<dynamic, dynamic>;
final appInfo = results[2] as ({String name, String version});
appName = appInfo.name;
Comment on lines +270 to 287
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using Future.wait([...]) with heterogeneous futures forces the results into a positional List and requires runtime casts by index (results[0] as ...). This is brittle and can silently break if any return type changes. A more maintainable approach is to start the typed futures (final foldersFuture = ..., etc.), await Future.wait<void>([foldersFuture, filesFuture, appInfoFuture]), and then read the typed results from the original futures/variables.

Copilot uses AI. Check for mistakes.
appVersion = appInfo.version;
});
Expand Down Expand Up @@ -317,21 +320,17 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {

@override
Widget build(BuildContext context) {
// Show a loading indicator whilst assets are being resolved.

if (!_assetsResolved) {
return const Scaffold(body: Center(child: CircularProgressIndicator()));
}

// Use the internal state for theme instead of system brightness.

final currentTheme = isDarkMode
? widget.themeConfig.darkTheme
: widget.themeConfig.lightTheme;

// Use resolved image with fallback support.
// Use resolved image if available, otherwise fall back to the widget's
// own AssetImage immediately — this prevents the UI being blocked by a
// loading spinner while assets are resolving in the background.

final effectiveImage = _resolvedImage ?? SolidConfig.soliduiDefaultImage;
final effectiveImage = _resolvedImage ?? widget.image;

// The login box's default image Widget for the left/background panel
// depending on screen width.
Expand Down Expand Up @@ -388,9 +387,9 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
focusNode: _infoFocusNode,
);

// Use resolved logo with fallback support.
// Use resolved logo if available, otherwise fall back to widget's own logo.

final effectiveLogo = _resolvedLogo ?? SolidConfig.soliduiDefaultLogo;
final effectiveLogo = _resolvedLogo ?? widget.logo;

Comment on lines +390 to 393
Copy link

Copilot AI Mar 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

effectiveLogo also falls back to widget.logo before _resolvedLogo is computed. If widget.logo points to a missing asset, the first build can trigger an image loading exception before the resolver falls back to the solidui defaults. Consider using a guaranteed-existing placeholder until _resolvedLogo is set (or ensure missing assets are handled without throwing during the initial frame).

Copilot uses AI. Check for mistakes.
// Build the login panel content.

Expand Down
9 changes: 8 additions & 1 deletion lib/src/widgets/solid_login_auth_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ class SolidLoginAuthHandler {
}

// Perform the actual authentication by contacting the server.
// Pass the already-computed login status so solidAuthenticate() does NOT
// repeat the same isUserLoggedIn() check (secure storage read + token
// expiry check) that we just performed above.

if (!context.mounted) {
browserMessageTimer?.cancel();
Expand All @@ -114,7 +117,11 @@ class SolidLoginAuthHandler {

List<dynamic>? authResult;
try {
authResult = await solidAuthenticate(podServer, context);
authResult = await solidAuthenticate(
podServer,
context,
wasAlreadyLoggedIn: wasAlreadyLoggedIn,
);
} on Object catch (e) {
// Authentication failed due to a network or server error (e.g. DNS
// lookup failure, socket exception, HTTP 502, or any other
Expand Down
Loading