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
12 changes: 6 additions & 6 deletions example/.metadata
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.

version:
revision: "20f82749394e68bcfbbeee96bad384abaae09c13"
revision: "ff37bef603469fb030f2b72995ab929ccfc227f0"
channel: "stable"

project_type: app
Expand All @@ -13,11 +13,11 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
- platform: linux
create_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
base_revision: 20f82749394e68bcfbbeee96bad384abaae09c13
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
- platform: android
create_revision: ff37bef603469fb030f2b72995ab929ccfc227f0
base_revision: ff37bef603469fb030f2b72995ab929ccfc227f0

# User provided section

Expand Down
28 changes: 28 additions & 0 deletions example/analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.

# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml

linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
17 changes: 15 additions & 2 deletions example/lib/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,28 @@ class App extends StatelessWidget {
useMaterial3: true,
),
home: const SolidLogin(
title: 'SOLID POD DEMONSTRATOR',
// Images generated using Bing Image Creator from Designer, powered by
// DALL-E3.

title: 'SOLID UI DEMONSTRATOR',
appDirectory: 'demopod',
image: AssetImage('assets/images/demopod_image.jpg'),
image: AssetImage('assets/images/demopod_image.png'),
logo: AssetImage('assets/images/demopod_logo.png'),
link: 'https://github.com/anusii/solidpod/blob/main/demopod/README.md',
required: false,
infoButtonStyle: InfoButtonStyle(
tooltip: 'Visit the DemoPod documentation.',
),
clientId:
'https://anushkavidanage.github.io/solidui/example/client-profile.jsonld',
// Use the following schemas depending on the platform
// Web: https://anushkavidanage.github.io/solidpod/example/redirect.html
// Mobile: com.example.demopod://redirect
// Desktop: http://localhost:4400/redirect
// (can use any port as long as it matches with the one in your id document)
redirectUri: 'http://localhost:4400/redirect',
postLogoutRedirectUri: 'http://localhost:4400/redirect',
autoLogin: true,
child: appScaffold,
),
);
Expand Down
7 changes: 7 additions & 0 deletions example/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ dependencies:
universal_io: ^2.3.1
window_manager: ^0.5.1

dependency_overrides:
solidpod:
# path: ../../solidpod
git:
url: https://github.com/anusii/solidui.git
ref: av/40_migrate_oidc_implementation

flutter:
uses-material-design: true
assets:
Expand Down
30 changes: 30 additions & 0 deletions example/test/widget_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.

// import 'package:flutter/material.dart';
// import 'package:flutter_test/flutter_test.dart';

// import 'package:demopod/main.dart';

// void main() {
// testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// // Build our app and trigger a frame.
// await tester.pumpWidget(const MyApp());

// // Verify that our counter starts at 0.
// expect(find.text('0'), findsOneWidget);
// expect(find.text('1'), findsNothing);

// // Tap the '+' icon and trigger a frame.
// await tester.tap(find.byIcon(Icons.add));
// await tester.pump();

// // Verify that our counter has incremented.
// expect(find.text('0'), findsNothing);
// expect(find.text('1'), findsOneWidget);
// });
// }
21 changes: 21 additions & 0 deletions lib/src/handlers/solid_auth_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,18 @@ class SolidAuthConfig {

final VoidCallback? onLogout;

/// URL of the app's client profile JSON-LD document. Required parameter

final String? clientId;

/// Custom URL scheme for the OAuth to redirect to after authentication.

final String? redirectUri;

/// Optional redirect URI for logout.

final String? postLogoutRedirectUri;

const SolidAuthConfig({
this.returnTo,
this.loginPageBuilder,
Expand All @@ -98,6 +110,9 @@ class SolidAuthConfig {
this.loginSuccessWidget,
this.onSecurityKeyReset,
this.onLogout,
this.clientId,
this.redirectUri,
this.postLogoutRedirectUri,
});
}

Expand Down Expand Up @@ -258,6 +273,9 @@ class SolidAuthHandler {
themeConfig: _cachedThemeConfig,
snackbarConfig: _cachedSnackbarConfig,
required: _cachedRequired,
clientId: _config!.clientId!,
redirectUri: _config!.redirectUri!,
postLogoutRedirectUri: _config!.postLogoutRedirectUri!,
);
}

Expand All @@ -273,6 +291,9 @@ class SolidAuthHandler {
appLink: _config?.appLink,
loginSuccessWidget: _config?.loginSuccessWidget,
navigateToRootOnSuccess: _config?.loginSuccessWidget == null,
clientId: _config!.clientId!,
redirectUri: _config!.redirectUri!,
postLogoutRedirectUri: _config!.postLogoutRedirectUri!,
);
}

Expand Down
18 changes: 18 additions & 0 deletions lib/src/widgets/solid_default_login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@ class SolidDefaultLogin extends StatelessWidget {

final bool required;

/// URL of the app's client profile JSON-LD document. Required parameter

final String clientId;

/// Custom URL scheme for the OAuth to redirect to after authentication.

final String redirectUri;

/// Optional redirect URI for logout.

final String? postLogoutRedirectUri;

const SolidDefaultLogin({
super.key,
required this.appTitle,
Expand All @@ -131,6 +143,9 @@ class SolidDefaultLogin extends StatelessWidget {
this.themeConfig,
this.snackbarConfig,
this.required = false,
required this.clientId,
required this.redirectUri,
this.postLogoutRedirectUri,
});

@override
Expand Down Expand Up @@ -168,6 +183,9 @@ class SolidDefaultLogin extends StatelessWidget {
changeKeyButtonStyle ?? const ChangeKeyButtonStyle(),
themeConfig: themeConfig ?? const SolidLoginTheme(),
snackbarConfig: snackbarConfig ?? const SnackbarConfig(),
clientId: clientId,
redirectUri: redirectUri,
postLogoutRedirectUri: postLogoutRedirectUri,
child: successWidget,
),
);
Expand Down
72 changes: 69 additions & 3 deletions lib/src/widgets/solid_login.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ import 'package:solidpod/solidpod.dart'
generateDefaultFolders,
generateDefaultFiles,
generateCustomFolders,
setAppDirName;
setAppDirName,
tryRestoreSession;

import 'package:solidui/src/constants/solid_config.dart';
import 'package:solidui/src/handlers/solid_auth_handler.dart';
Expand Down Expand Up @@ -81,6 +82,10 @@ class SolidLogin extends StatefulWidget {
this.themeConfig = const SolidLoginTheme(),
this.snackbarConfig = const SnackbarConfig(),
this.customFolderPathList = const [],
required this.clientId,
required this.redirectUri,
this.postLogoutRedirectUri,
this.autoLogin = false,
super.key,
});

Expand Down Expand Up @@ -144,6 +149,26 @@ class SolidLogin extends StatefulWidget {

final List customFolderPathList;

/// URL of the app's client profile JSON-LD document. Required parameter

final String clientId;

/// Custom URL scheme for the OAuth to redirect to after authentication.

final String redirectUri;

/// Optional redirect URI for logout.

final String? postLogoutRedirectUri;

/// When true, automatically restores a saved session on startup and navigates
/// directly to [child] without showing the login page.
///
/// Falls back to showing the login page if no valid session is found or if
/// the user has opted out of "Stay signed in".

final bool autoLogin;

@override
State<SolidLogin> createState() => _SolidLoginState();
}
Expand Down Expand Up @@ -184,6 +209,10 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {

bool _assetsResolved = false;

/// Whether an auto-login check is in progress (shows a loading screen).

bool _checkingAutoLogin = false;

/// Whether the user wishes to persist the login session across app restarts.

bool _staySignedIn = true;
Expand Down Expand Up @@ -217,6 +246,13 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {

SolidLoginAuthHandler.clearSessionIfRequired();

// If autoLogin is requested, attempt silent session restoration after the
// first frame so that context is available for navigation.

if (widget.autoLogin) {
WidgetsBinding.instance.addPostFrameCallback((_) => _checkAutoLogin());
}

// dc 20251022: please explain why calling an async without await.

_initPackageInfo();
Expand Down Expand Up @@ -271,6 +307,32 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
if (mounted) setState(() => _staySignedIn = value);
}

/// Attempts silent session restoration. On success navigates directly to
/// [widget.child]; on failure shows the login page as normal.

Future<void> _checkAutoLogin() async {
if (!mounted) return;

// Honour the "Stay signed in" opt-out — if disabled, skip auto-login.
final staySignedIn = await SolidLoginAuthHandler.getStaySignedIn();
if (!staySignedIn || !mounted) return;

if (mounted) setState(() => _checkingAutoLogin = true);

final session = await tryRestoreSession();

if (!mounted) return;
if (session == null) {
setState(() => _checkingAutoLogin = false);
return;
}

// Session restored — navigate directly to the child widget.
await Navigator.of(context).pushReplacement(
MaterialPageRoute<void>(builder: (_) => widget.child),
);
}

@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
Expand Down Expand Up @@ -366,9 +428,10 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {

@override
Widget build(BuildContext context) {
// Show a loading indicator whilst assets are being resolved.
// Show a loading indicator whilst assets are being resolved or an
// auto-login check is in progress.

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

Expand Down Expand Up @@ -408,6 +471,9 @@ class _SolidLoginState extends State<SolidLogin> with WidgetsBindingObserver {
updateDialogCanceledState: updateState,
showSnackbar: _showSnackbar,
staySignedIn: _staySignedIn,
clientId: widget.clientId,
redirectUri: widget.redirectUri,
postLogoutRedirectUri: widget.postLogoutRedirectUri,
);
}

Expand Down
6 changes: 6 additions & 0 deletions lib/src/widgets/solid_login_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class SolidLoginActions {
required VoidCallback updateDialogCanceledState,
required LoginSnackbar showSnackbar,
required bool staySignedIn,
required final String clientId,
required final String redirectUri,
final String? postLogoutRedirectUri,
}) async {
// When the user has opted out of staying signed in, discard any existing
// cached session immediately so browser authentication is always
Expand Down Expand Up @@ -132,6 +135,9 @@ class SolidLoginActions {
updateDialogCanceledState: updateDialogCanceledState,
showSnackbar: showSnackbar,
staySignedIn: staySignedIn,
clientId: clientId,
redirectUri: redirectUri,
postLogoutRedirectUri: postLogoutRedirectUri,
);
}

Expand Down
11 changes: 10 additions & 1 deletion lib/src/widgets/solid_login_auth_handler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -250,11 +250,14 @@ class SolidLoginAuthHandler {
required List<String> defaultFolders,
required Map<dynamic, dynamic> defaultFiles,
required dynamic originalLoginWidget,
required final String clientId,
required final String redirectUri,
required Widget childWidget,
required ValueGetter<bool> isDialogCanceled,
required VoidCallback updateDialogCanceledState,
required Function(String message, {Duration? duration, bool showAction})
showSnackbar,
final String? postLogoutRedirectUri,
bool staySignedIn = true,
}) async {
// Method to show busy animation requiring BuildContext.
Expand Down Expand Up @@ -305,7 +308,13 @@ class SolidLoginAuthHandler {

List<dynamic>? authResult;
try {
authResult = await solidAuthenticate(podServer, context);
authResult = await solidAuthenticate(
podServer,
context,
clientId: clientId,
redirectUri: redirectUri,
postLogoutRedirectUri: postLogoutRedirectUri,
);
} on Object catch (e) {
// Check whether auth data was persisted before the failure (i.e. POD
// not initialised) vs a genuine server/network error.
Expand Down
Loading
Loading