Browse Source

neon: Add qrcode login

pull/291/head
jld3103 2 years ago committed by Nikolas Rimikis
parent
commit
ffb8623ddc
No known key found for this signature in database
GPG Key ID: 85ED1DE9786A4FF2
  1. 1
      packages/app/linux/flutter/generated_plugins.cmake
  2. 200
      packages/app/pubspec.lock
  3. 4
      packages/neon/neon/lib/l10n/en.arb
  4. 18
      packages/neon/neon/lib/l10n/localizations.dart
  5. 8
      packages/neon/neon/lib/l10n/localizations_en.dart
  6. 34
      packages/neon/neon/lib/src/pages/login.dart
  7. 66
      packages/neon/neon/lib/src/pages/login_qrcode.dart
  8. 13
      packages/neon/neon/lib/src/router.dart
  9. 19
      packages/neon/neon/lib/src/router.g.dart
  10. 2
      packages/neon/neon/lib/src/utils/exceptions.dart
  11. 6
      packages/neon/neon/lib/src/widgets/exception.dart
  12. 1
      packages/neon/neon/pubspec.yaml

1
packages/app/linux/flutter/generated_plugins.cmake

@ -11,6 +11,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
flutter_zxing
)
set(PLUGIN_BUNDLED_LIBRARIES)

200
packages/app/pubspec.lock

@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
ansi_styles:
dependency: transitive
description:
name: ansi_styles
sha256: "9c656cc12b3c27b17dd982b2cc5c0cfdfbdabd7bc8f3ae5e8542d9867b47ce8a"
url: "https://pub.dev"
source: hosted
version: "0.3.2+1"
archive:
dependency: transitive
description:
@ -65,6 +73,46 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.6.1"
camera:
dependency: transitive
description:
name: camera
sha256: ebebead3d5ec3d148249331d751d462d7e8c98102b8830a9b45ec96a2bd4333f
url: "https://pub.dev"
source: hosted
version: "0.10.5+2"
camera_android:
dependency: transitive
description:
name: camera_android
sha256: f43d07f9d7228ea1ca87d22e30881bd68da4b78484a1fbd1f1408b412a41cefb
url: "https://pub.dev"
source: hosted
version: "0.10.8+3"
camera_avfoundation:
dependency: transitive
description:
name: camera_avfoundation
sha256: "1a416e452b30955b392f4efbf23291d3f2ba3660a85e1628859eb62d2a2bab26"
url: "https://pub.dev"
source: hosted
version: "0.9.13+2"
camera_platform_interface:
dependency: transitive
description:
name: camera_platform_interface
sha256: "60fa0bb62a4f3bf3a7c413e31e4cd01b69c779ccc8e4668904a24581b86c316b"
url: "https://pub.dev"
source: hosted
version: "2.5.1"
camera_web:
dependency: transitive
description:
name: camera_web
sha256: bcbd775fb3a9d51cc3ece899d54ad66f6306410556bac5759f78e13f9228841f
url: "https://pub.dev"
source: hosted
version: "0.3.1+4"
characters:
dependency: transitive
description:
@ -73,6 +121,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
url: "https://pub.dev"
source: hosted
version: "1.3.1"
cli_launcher:
dependency: transitive
description:
name: cli_launcher
sha256: "5e7e0282b79e8642edd6510ee468ae2976d847a0a29b3916e85f5fa1bfe24005"
url: "https://pub.dev"
source: hosted
version: "0.3.1"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
url: "https://pub.dev"
source: hosted
version: "0.4.0"
clock:
dependency: transitive
description:
@ -89,6 +161,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.17.1"
conventional_commit:
dependency: transitive
description:
name: conventional_commit
sha256: dec15ad1118f029c618651a4359eb9135d8b88f761aa24e4016d061cd45948f2
url: "https://pub.dev"
source: hosted
version: "0.6.0+1"
convert:
dependency: transitive
description:
@ -344,11 +424,27 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_zxing:
dependency: transitive
description:
name: flutter_zxing
sha256: b25efe5ac91fe7a51aa8bfea7aca7435285b911b8758b7da38b7f037bad62c35
url: "https://pub.dev"
source: hosted
version: "1.1.2"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
go_router:
dependency: transitive
description:
@ -357,6 +453,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "8.0.3"
graphs:
dependency: transitive
description:
name: graphs
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev"
source: hosted
version: "2.3.1"
html:
dependency: transitive
description:
@ -474,6 +578,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.18.0"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
js:
dependency: transitive
description:
@ -538,6 +650,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.7296"
melos:
dependency: transitive
description:
name: melos
sha256: ccbb6ecd8bb3f08ae8f9ce22920d816bff325a98940c845eda0257cd395503ac
url: "https://pub.dev"
source: hosted
version: "3.1.0"
menu_base:
dependency: transitive
description:
@ -562,6 +682,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
mustache_template:
dependency: transitive
description:
name: mustache_template
sha256: a46e26f91445bfb0b60519be280555b06792460b27b19e2b19ad5b9740df5d1c
url: "https://pub.dev"
source: hosted
version: "2.0.0"
neon:
dependency: "direct main"
description:
@ -789,6 +917,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.7.3"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
process:
dependency: transitive
description:
@ -797,6 +933,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.4"
prompts:
dependency: transitive
description:
name: prompts
sha256: "3773b845e85a849f01e793c4fc18a45d52d7783b4cb6c0569fad19f9d0a774a1"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
provider:
dependency: transitive
description:
@ -805,6 +949,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.5"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
pub_updater:
dependency: transitive
description:
name: pub_updater
sha256: "42890302ab2672adf567dc2b20e55b4ecc29d7e19c63b6b98143ab68dd717d3a"
url: "https://pub.dev"
source: hosted
version: "0.2.4"
pubspec:
dependency: transitive
description:
name: pubspec
sha256: f534a50a2b4d48dc3bc0ec147c8bd7c304280fff23b153f3f11803c4d49d927e
url: "https://pub.dev"
source: hosted
version: "2.3.0"
queue:
dependency: transitive
description:
@ -845,6 +1013,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.4"
quiver:
dependency: transitive
description:
name: quiver
sha256: b1c1ac5ce6688d77f65f3375a9abb9319b3cb32486bdc7a1e0fdf004d7ba4e47
url: "https://pub.dev"
source: hosted
version: "3.2.1"
rxdart:
dependency: transitive
description:
@ -1009,6 +1185,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
string_scanner:
dependency: transitive
description:
@ -1105,6 +1289,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.2"
uri:
dependency: transitive
description:
name: uri
sha256: "889eea21e953187c6099802b7b4cf5219ba8f3518f604a1033064d45b1b8268a"
url: "https://pub.dev"
source: hosted
version: "1.0.0"
url_launcher:
dependency: transitive
description:
@ -1354,6 +1546,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.1.2"
yaml_edit:
dependency: transitive
description:
name: yaml_edit
sha256: "1579d4a0340a83cf9e4d580ea51a16329c916973bffd5bd4b45e911b25d46bfd"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
sdks:
dart: ">=3.0.0 <4.0.0"
flutter: ">=3.10.4"

4
packages/neon/neon/lib/l10n/en.arb

@ -10,7 +10,8 @@
"loginOpenAgain": "Open again",
"loginSwitchToBrowserWindow": "Please switch to the browser window that just opened and proceed there",
"loginWorksWith": "works with",
"loginRestart": "Restart login",
"loginUsingQrcode": "Login using a QR code",
"loginUsingServerAddress": "Login using the server address",
"loginCheckingServerVersion": "Checking server version",
"loginSupportedServerVersion": "Supported server version: {version}",
"@loginSupportedServerVersion": {
@ -66,6 +67,7 @@
},
"errorEmptyField": "This field can not be empty",
"errorInvalidURL": "Invalid URL provided",
"errorInvalidQrcode": "Invalid QR-Code provided",
"actionYes": "Yes",
"actionNo": "No",
"actionClose": "Close",

18
packages/neon/neon/lib/l10n/localizations.dart

@ -119,11 +119,17 @@ abstract class AppLocalizations {
/// **'works with'**
String get loginWorksWith;
/// No description provided for @loginRestart.
/// No description provided for @loginUsingQrcode.
///
/// In en, this message translates to:
/// **'Restart login'**
String get loginRestart;
/// **'Login using a QR code'**
String get loginUsingQrcode;
/// No description provided for @loginUsingServerAddress.
///
/// In en, this message translates to:
/// **'Login using the server address'**
String get loginUsingServerAddress;
/// No description provided for @loginCheckingServerVersion.
///
@ -245,6 +251,12 @@ abstract class AppLocalizations {
/// **'Invalid URL provided'**
String get errorInvalidURL;
/// No description provided for @errorInvalidQrcode.
///
/// In en, this message translates to:
/// **'Invalid QR-Code provided'**
String get errorInvalidQrcode;
/// No description provided for @actionYes.
///
/// In en, this message translates to:

8
packages/neon/neon/lib/l10n/localizations_en.dart

@ -36,7 +36,10 @@ class AppLocalizationsEn extends AppLocalizations {
String get loginWorksWith => 'works with';
@override
String get loginRestart => 'Restart login';
String get loginUsingQrcode => 'Login using a QR code';
@override
String get loginUsingServerAddress => 'Login using the server address';
@override
String get loginCheckingServerVersion => 'Checking server version';
@ -111,6 +114,9 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get errorInvalidURL => 'Invalid URL provided';
@override
String get errorInvalidQrcode => 'Invalid QR-Code provided';
@override
String get actionYes => 'Yes';

34
packages/neon/neon/lib/src/pages/login.dart

@ -1,10 +1,14 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/theme/branding.dart';
import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/utils/validators.dart';
import 'package:neon/src/widgets/nextcloud_logo.dart';
import 'package:provider/provider.dart';
class LoginPage extends StatefulWidget {
const LoginPage({
@ -52,6 +56,8 @@ class _LoginPageState extends State<LoginPage> {
@override
Widget build(final BuildContext context) {
final branding = Branding.of(context);
final platform = Provider.of<NeonPlatform>(context, listen: false);
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
@ -72,7 +78,7 @@ class _LoginPageState extends State<LoginPage> {
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(
height: 30,
height: 20,
),
if (branding.showLoginWithNextcloud) ...[
Text(AppLocalizations.of(context).loginWorksWith),
@ -81,6 +87,32 @@ class _LoginPageState extends State<LoginPage> {
),
],
const NextcloudLogo(),
const SizedBox(
height: 40,
),
if (platform.canUseCamera) ...[
ExcludeSemantics(
child: Center(
child: Text(AppLocalizations.of(context).loginUsingQrcode),
),
),
IconButton(
tooltip: AppLocalizations.of(context).loginUsingQrcode,
icon: const Icon(
Icons.qr_code_scanner,
size: 50,
),
onPressed: () => unawaited(const LoginQrcodeRoute().push(context)),
),
const SizedBox(
height: 20,
),
ExcludeSemantics(
child: Center(
child: Text(AppLocalizations.of(context).loginUsingServerAddress),
),
),
],
Form(
key: _formKey,
child: TextFormField(

66
packages/neon/neon/lib/src/pages/login_qrcode.dart

@ -0,0 +1,66 @@
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/widgets/exception.dart';
class LoginQrcodePage extends StatefulWidget {
const LoginQrcodePage({
super.key,
});
@override
State<LoginQrcodePage> createState() => _LoginQrcodePageState();
}
class _LoginQrcodePageState extends State<LoginQrcodePage> {
final _urlRegex = RegExp(r'^nc://login/user:(.*)&password:(.*)&server:(.*)$');
String? _lastErrorURL;
@override
Widget build(final BuildContext context) => Scaffold(
appBar: AppBar(),
body: ReaderWidget(
codeFormat: Format.qrCode,
showGallery: false,
showToggleCamera: false,
showScannerOverlay: false,
tryHarder: true,
cropPercent: 0,
scanDelaySuccess: const Duration(seconds: 3),
onScan: (final code) async {
String? url;
try {
url = code.text;
if (url == null) {
throw InvalidQrcodeException();
}
final match = _urlRegex.allMatches(url).single;
if (match.groupCount != 3) {
throw InvalidQrcodeException();
}
final loginName = match.group(1)!;
final password = match.group(2)!;
final serverURL = match.group(3)!;
final result = await LoginCheckServerStatusRoute(serverURL: serverURL).push<bool>(context);
if ((result ?? false) && mounted) {
LoginCheckAccountRoute(
serverURL: serverURL,
loginName: loginName,
password: password,
).pushReplacement(context);
}
} catch (e, s) {
if (_lastErrorURL != url) {
debugPrint(e.toString());
debugPrint(s.toString());
_lastErrorURL = url;
NeonException.showSnackbar(context, e);
}
}
},
),
);
}

13
packages/neon/neon/lib/src/router.dart

@ -10,6 +10,7 @@ import 'package:neon/src/pages/login.dart';
import 'package:neon/src/pages/login_check_account.dart';
import 'package:neon/src/pages/login_check_server_status.dart';
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/stream_listenable.dart';
@ -105,6 +106,10 @@ class HomeRoute extends GoRouteData {
path: 'flow/:serverURL',
name: 'loginFlow',
),
TypedGoRoute<LoginQrcodeRoute>(
path: 'qrcode',
name: 'loginQrcode',
),
TypedGoRoute<LoginCheckServerStatusRoute>(
path: 'check/server/:serverURL',
name: 'checkServerStatus',
@ -137,6 +142,14 @@ class LoginFlowRoute extends GoRouteData {
Widget build(final BuildContext context, final GoRouterState state) => LoginFlowPage(serverURL: serverURL);
}
@immutable
class LoginQrcodeRoute extends GoRouteData {
const LoginQrcodeRoute();
@override
Widget build(final BuildContext context, final GoRouterState state) => const LoginQrcodePage();
}
@immutable
class LoginCheckServerStatusRoute extends GoRouteData {
const LoginCheckServerStatusRoute({

19
packages/neon/neon/lib/src/router.g.dart

@ -125,6 +125,11 @@ RouteBase get $loginRoute => GoRouteData.$route(
name: 'loginFlow',
factory: $LoginFlowRouteExtension._fromState,
),
GoRouteData.$route(
path: 'qrcode',
name: 'loginQrcode',
factory: $LoginQrcodeRouteExtension._fromState,
),
GoRouteData.$route(
path: 'check/server/:serverURL',
name: 'checkServerStatus',
@ -173,6 +178,20 @@ extension $LoginFlowRouteExtension on LoginFlowRoute {
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
extension $LoginQrcodeRouteExtension on LoginQrcodeRoute {
static LoginQrcodeRoute _fromState(GoRouterState state) => const LoginQrcodeRoute();
String get location => GoRouteData.$location(
'/login/qrcode',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
}
extension $LoginCheckServerStatusRouteExtension on LoginCheckServerStatusRoute {
static LoginCheckServerStatusRoute _fromState(GoRouterState state) => LoginCheckServerStatusRoute(
serverURL: state.pathParameters['serverURL']!,

2
packages/neon/neon/lib/src/utils/exceptions.dart

@ -7,3 +7,5 @@ class MissingPermissionException implements Exception {
}
class UnableToOpenFileException implements Exception {}
class InvalidQrcodeException implements Exception {}

6
packages/neon/neon/lib/src/widgets/exception.dart

@ -128,6 +128,12 @@ class NeonException extends StatelessWidget {
);
}
if (exception is InvalidQrcodeException) {
return _ExceptionDetails(
text: AppLocalizations.of(context).errorInvalidQrcode,
);
}
if (exception is DynamiteApiException) {
if (exception.statusCode == 401) {
return _ExceptionDetails(

1
packages/neon/neon/pubspec.yaml

@ -21,6 +21,7 @@ dependencies:
sdk: flutter
flutter_native_splash: ^2.2.19
flutter_svg: ^2.0.5
flutter_zxing: ^1.1.2 # ^1.2.0 downgrades to image ^3.0.0 which breaks our dependencies. See https://github.com/khoren93/flutter_zxing/issues/94
go_router: ^8.0.3
http: ^0.13.6
intersperse: ^2.0.0

Loading…
Cancel
Save