diff --git a/packages/app/android/app/src/main/AndroidManifest.xml b/packages/app/android/app/src/main/AndroidManifest.xml
index 86bb0637..197488d0 100644
--- a/packages/app/android/app/src/main/AndroidManifest.xml
+++ b/packages/app/android/app/src/main/AndroidManifest.xml
@@ -28,6 +28,13 @@
+
+
+
+
+
+
+
diff --git a/packages/neon/neon/lib/src/pages/login_qrcode.dart b/packages/neon/neon/lib/src/pages/login_qrcode.dart
index e1810175..08797c14 100644
--- a/packages/neon/neon/lib/src/pages/login_qrcode.dart
+++ b/packages/neon/neon/lib/src/pages/login_qrcode.dart
@@ -1,7 +1,10 @@
+import 'dart:async';
+
import 'package:flutter/material.dart';
import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/utils/exceptions.dart';
+import 'package:neon/src/utils/login_qrcode.dart';
import 'package:neon/src/widgets/exception.dart';
class LoginQrcodePage extends StatefulWidget {
@@ -14,7 +17,6 @@ class LoginQrcodePage extends StatefulWidget {
}
class _LoginQrcodePageState extends State {
- final _urlRegex = RegExp(r'^nc://login/user:(.*)&password:(.*)&server:(.*)$');
String? _lastErrorURL;
@override
@@ -35,22 +37,11 @@ class _LoginQrcodePageState extends State {
if (url == null) {
throw InvalidQrcodeException();
}
- final match = _urlRegex.allMatches(url).single;
- if (match.groupCount != 3) {
+ final match = LoginQrcode.tryParse(url);
+ if (match == null) {
throw InvalidQrcodeException();
}
- final loginName = match.group(1)!;
- final password = match.group(2)!;
- final serverURL = match.group(3)!;
-
- final result = await LoginCheckServerStatusRoute(serverURL: serverURL).push(context);
- if ((result ?? false) && mounted) {
- LoginCheckAccountRoute(
- serverURL: serverURL,
- loginName: loginName,
- password: password,
- ).pushReplacement(context);
- }
+ await processLoginQrcode(context, match);
} catch (e, s) {
if (_lastErrorURL != url) {
debugPrint(e.toString());
@@ -64,3 +55,52 @@ class _LoginQrcodePageState extends State {
),
);
}
+
+class LoginQrcodeIntermediatePage extends StatefulWidget {
+ const LoginQrcodeIntermediatePage({
+ required this.serverURL,
+ required this.loginName,
+ required this.password,
+ super.key,
+ });
+
+ final String serverURL;
+ final String loginName;
+ final String password;
+
+ @override
+ State createState() => _LoginQrcodeIntermediatePageState();
+}
+
+class _LoginQrcodeIntermediatePageState extends State {
+ @override
+ void initState() {
+ super.initState();
+ WidgetsBinding.instance.addPostFrameCallback((final _) {
+ unawaited(
+ processLoginQrcode(
+ context,
+ LoginQrcode(
+ server: widget.serverURL,
+ user: widget.loginName,
+ password: widget.password,
+ ),
+ ),
+ );
+ });
+ }
+
+ @override
+ Widget build(final BuildContext context) => const SizedBox();
+}
+
+Future processLoginQrcode(final BuildContext context, final LoginQrcode qrcode) async {
+ final result = await LoginCheckServerStatusRoute(serverURL: qrcode.server).push(context);
+ if ((result ?? false) && context.mounted) {
+ LoginCheckAccountRoute(
+ serverURL: qrcode.server,
+ loginName: qrcode.user,
+ password: qrcode.password,
+ ).pushReplacement(context);
+ }
+}
diff --git a/packages/neon/neon/lib/src/router.dart b/packages/neon/neon/lib/src/router.dart
index f09f8924..1a453365 100644
--- a/packages/neon/neon/lib/src/router.dart
+++ b/packages/neon/neon/lib/src/router.dart
@@ -13,6 +13,7 @@ import 'package:neon/src/pages/login_flow.dart';
import 'package:neon/src/pages/login_qrcode.dart';
import 'package:neon/src/pages/nextcloud_app_settings.dart';
import 'package:neon/src/pages/settings.dart';
+import 'package:neon/src/utils/login_qrcode.dart';
import 'package:neon/src/utils/stream_listenable.dart';
import 'package:provider/provider.dart';
@@ -30,6 +31,15 @@ class AppRouter extends GoRouter {
redirect: (final context, final state) {
final account = accountsBloc.activeAccount.valueOrNull;
+ final loginQrcode = LoginQrcode.tryParse(state.location);
+ if (loginQrcode != null) {
+ return LoginQrcodeIntermediateRoute(
+ serverURL: loginQrcode.server,
+ loginName: loginQrcode.user,
+ password: loginQrcode.password,
+ ).location;
+ }
+
// redirect to loginscreen when no account is logged in
if (account == null && !state.location.startsWith(const LoginRoute().location)) {
return const LoginRoute().location;
@@ -110,6 +120,10 @@ class HomeRoute extends GoRouteData {
path: 'qrcode',
name: 'loginQrcode',
),
+ TypedGoRoute(
+ path: 'qrcode/intermediate/:serverURL/:loginName/:password',
+ name: 'loginQrcodeIntermediate',
+ ),
TypedGoRoute(
path: 'check/server/:serverURL',
name: 'checkServerStatus',
@@ -150,6 +164,26 @@ class LoginQrcodeRoute extends GoRouteData {
Widget build(final BuildContext context, final GoRouterState state) => const LoginQrcodePage();
}
+@immutable
+class LoginQrcodeIntermediateRoute extends GoRouteData {
+ const LoginQrcodeIntermediateRoute({
+ required this.serverURL,
+ required this.loginName,
+ required this.password,
+ });
+
+ final String serverURL;
+ final String loginName;
+ final String password;
+
+ @override
+ Widget build(final BuildContext context, final GoRouterState state) => LoginQrcodeIntermediatePage(
+ serverURL: serverURL,
+ loginName: loginName,
+ password: password,
+ );
+}
+
@immutable
class LoginCheckServerStatusRoute extends GoRouteData {
const LoginCheckServerStatusRoute({
diff --git a/packages/neon/neon/lib/src/router.g.dart b/packages/neon/neon/lib/src/router.g.dart
index b823ce90..c1730833 100644
--- a/packages/neon/neon/lib/src/router.g.dart
+++ b/packages/neon/neon/lib/src/router.g.dart
@@ -130,6 +130,11 @@ RouteBase get $loginRoute => GoRouteData.$route(
name: 'loginQrcode',
factory: $LoginQrcodeRouteExtension._fromState,
),
+ GoRouteData.$route(
+ path: 'qrcode/intermediate/:serverURL/:loginName/:password',
+ name: 'loginQrcodeIntermediate',
+ factory: $LoginQrcodeIntermediateRouteExtension._fromState,
+ ),
GoRouteData.$route(
path: 'check/server/:serverURL',
name: 'checkServerStatus',
@@ -192,6 +197,24 @@ extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
+extension $LoginQrcodeIntermediateRouteExtension on LoginQrcodeIntermediateRoute {
+ static LoginQrcodeIntermediateRoute _fromState(GoRouterState state) => LoginQrcodeIntermediateRoute(
+ serverURL: state.pathParameters['serverURL']!,
+ loginName: state.pathParameters['loginName']!,
+ password: state.pathParameters['password']!,
+ );
+
+ String get location => GoRouteData.$location(
+ '/login/qrcode/intermediate/${Uri.encodeComponent(serverURL)}/${Uri.encodeComponent(loginName)}/${Uri.encodeComponent(password)}',
+ );
+
+ void go(BuildContext context) => context.go(location);
+
+ Future push(BuildContext context) => context.push(location);
+
+ void pushReplacement(BuildContext context) => context.pushReplacement(location);
+}
+
extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
static LoginCheckServerStatusRoute _fromState(GoRouterState state) => LoginCheckServerStatusRoute(
serverURL: state.pathParameters['serverURL']!,
diff --git a/packages/neon/neon/lib/src/utils/login_qrcode.dart b/packages/neon/neon/lib/src/utils/login_qrcode.dart
new file mode 100644
index 00000000..4e2081e2
--- /dev/null
+++ b/packages/neon/neon/lib/src/utils/login_qrcode.dart
@@ -0,0 +1,47 @@
+import 'package:meta/meta.dart';
+
+@internal
+class LoginQrcode {
+ LoginQrcode({
+ required this.server,
+ required this.user,
+ required this.password,
+ });
+
+ static final _loginQrcodeUrlRegex = RegExp(r'^nc://login/user:(.*)&password:(.*)&server:(.*)$');
+ static final _loginQrcodePathRegex = RegExp(r'^/user:(.*)&password:(.*)&server:(.*)$');
+
+ static LoginQrcode parse(final String url) {
+ for (final regex in [_loginQrcodeUrlRegex, _loginQrcodePathRegex]) {
+ final matches = regex.allMatches(url);
+ if (matches.isEmpty) {
+ continue;
+ }
+
+ final match = matches.single;
+ if (match.groupCount != 3) {
+ continue;
+ }
+
+ return LoginQrcode(
+ server: match.group(3)!,
+ user: match.group(1)!,
+ password: match.group(2)!,
+ );
+ }
+
+ throw const FormatException();
+ }
+
+ static LoginQrcode? tryParse(final String url) {
+ try {
+ return parse(url);
+ } on FormatException {
+ return null;
+ }
+ }
+
+ final String server;
+ final String user;
+ final String password;
+}