Browse Source

Merge pull request #489 from provokateurin/cleanup/login

Cleanup/login
pull/525/head
Kate 1 year ago committed by GitHub
parent
commit
185f749bd3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      docs/login.md
  2. 2
      packages/neon/neon/lib/l10n/en.arb
  3. 12
      packages/neon/neon/lib/l10n/localizations.dart
  4. 6
      packages/neon/neon/lib/l10n/localizations_en.dart
  5. 96
      packages/neon/neon/lib/src/pages/login.dart
  6. 43
      packages/neon/neon/lib/src/pages/login_check_account.dart
  7. 34
      packages/neon/neon/lib/src/pages/login_check_server_status.dart
  8. 2
      packages/neon/neon/lib/src/pages/login_flow.dart
  9. 2
      packages/neon/neon/lib/src/theme/branding.dart
  10. 6
      packages/neon/neon/lib/src/theme/theme.dart
  11. 6
      packages/neon/neon/lib/src/widgets/exception.dart
  12. 2
      packages/neon/neon/lib/src/widgets/nextcloud_logo.dart
  13. 11
      packages/neon/neon/lib/src/widgets/validation_tile.dart

2
docs/login.md

@ -1,5 +1,5 @@
# Login user flow
This diagram displays the user flow for logging into the app. This is not how it currently works, but how it should work at some point.
This diagram displays the user flow for logging into the app.
![Login user flow diagram](login.svg)

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

@ -1,5 +1,7 @@
{
"@@locale": "en",
"nextcloud": "Nextcloud",
"nextcloudLogo": "Nextcloud logo",
"appImplementationName": "{app, select, nextcloud{Nextcloud} core{Server} files{Files} news{News} notes{Notes} notifications{Notifications} other{}}",
"@appImplementationName": {
"placeholders": {

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

@ -89,6 +89,18 @@ abstract class AppLocalizations {
/// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[Locale('en')];
/// No description provided for @nextcloud.
///
/// In en, this message translates to:
/// **'Nextcloud'**
String get nextcloud;
/// No description provided for @nextcloudLogo.
///
/// In en, this message translates to:
/// **'Nextcloud logo'**
String get nextcloudLogo;
/// No description provided for @appImplementationName.
///
/// In en, this message translates to:

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

@ -6,6 +6,12 @@ import 'localizations.dart';
class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get nextcloud => 'Nextcloud';
@override
String get nextcloudLogo => 'Nextcloud logo';
@override
String appImplementationName(String app) {
String _temp0 = intl.Intl.selectLogic(

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

@ -20,18 +20,23 @@ class LoginPage extends StatefulWidget {
class _LoginPageState extends State<LoginPage> {
final _formKey = GlobalKey<FormState>();
final _focusNode = FocusNode();
@override
void initState() {
super.initState();
}
final _controller = TextEditingController();
@override
void dispose() {
_focusNode.dispose();
_controller.dispose();
super.dispose();
}
void login(final String url) {
if (_formKey.currentState!.validate()) {
LoginCheckServerStatusRoute(serverUrl: url).go(context);
} else {
_focusNode.requestFocus();
}
}
@override
Widget build(final BuildContext context) {
final branding = Branding.of(context);
@ -39,77 +44,76 @@ class _LoginPageState extends State<LoginPage> {
return Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
leading: Navigator.canPop(context) ? const CloseButton() : null,
),
appBar: Navigator.canPop(context)
? AppBar(
leading: const CloseButton(),
)
: null,
body: Center(
child: ConstrainedBox(
constraints: NeonDialogTheme.of(context).constraints,
child: Scrollbar(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(vertical: 40, horizontal: 20),
padding: const EdgeInsets.all(10),
primary: true,
child: Column(
children: [
branding.logo,
ExcludeSemantics(
child: branding.logo,
),
Text(
branding.name,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(
height: 20,
),
if (branding.showLoginWithNextcloud) ...[
Text(AppLocalizations.of(context).loginWorksWith),
const SizedBox(
height: 20,
height: 10,
),
],
const NextcloudLogo(),
Text(AppLocalizations.of(context).loginWorksWith),
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,
height: 10,
),
onPressed: () => const LoginQrcodeRoute().go(context),
Semantics(
label: AppLocalizations.of(context).nextcloud,
child: const NextcloudLogo(),
),
],
const SizedBox(
height: 20,
),
ExcludeSemantics(
child: Center(
child: Text(AppLocalizations.of(context).loginUsingServerAddress),
),
height: 50,
),
],
Form(
key: _formKey,
child: TextFormField(
focusNode: _focusNode,
decoration: const InputDecoration(
controller: _controller,
decoration: InputDecoration(
hintText: 'https://...',
labelText: AppLocalizations.of(context).loginUsingServerAddress,
suffixIcon: IconButton(
icon: const Icon(Icons.arrow_forward),
onPressed: () {
login(_controller.text);
},
),
),
keyboardType: TextInputType.url,
validator: (final input) => validateHttpUrl(context, input),
onFieldSubmitted: (final input) {
if (_formKey.currentState!.validate()) {
LoginCheckServerStatusRoute(serverUrl: input).go(context);
} else {
_focusNode.requestFocus();
}
},
onFieldSubmitted: login,
),
),
if (platform.canUseCamera || true) ...[
const SizedBox(
height: 50,
),
IconButton(
tooltip: AppLocalizations.of(context).loginUsingQrcode,
icon: const Icon(
Icons.qr_code_scanner_rounded,
size: 60,
),
onPressed: () => const LoginQrcodeRoute().go(context),
),
],
],
),
),

43
packages/neon/neon/lib/src/pages/login_check_account.dart

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/bloc/result.dart';
@ -9,7 +11,6 @@ import 'package:neon/src/router.dart';
import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/widgets/account_tile.dart';
import 'package:neon/src/widgets/exception.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart';
import 'package:neon/src/widgets/validation_tile.dart';
import 'package:provider/provider.dart';
@ -62,13 +63,19 @@ class _LoginCheckAccountPageState extends State<LoginCheckAccountPage> {
builder: (final context, final state) => Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NeonLinearProgressIndicator(
visible: state.isLoading,
),
NeonException(
state.error,
onRetry: bloc.refresh,
if (state.hasError) ...[
Builder(
builder: (final context) {
final details = NeonException.getDetails(context, state.error);
return NeonValidationTile(
title: details.isUnauthorized
? AppLocalizations.of(context).errorCredentialsForAccountNoLongerMatch
: details.text,
state: ValidationState.failure,
);
},
),
],
_buildAccountTile(state),
Align(
alignment: Alignment.bottomRight,
@ -81,8 +88,19 @@ class _LoginCheckAccountPageState extends State<LoginCheckAccountPage> {
const HomeRoute().go(context);
}
: null,
child: Text(AppLocalizations.of(context).actionContinue),
: () {
if (state.hasError && NeonException.getDetails(context, state.error).isUnauthorized) {
Navigator.pop(context);
return;
}
unawaited(bloc.refresh());
},
child: Text(
state.hasData
? AppLocalizations.of(context).actionContinue
: AppLocalizations.of(context).actionRetry,
),
),
),
],
@ -94,6 +112,13 @@ class _LoginCheckAccountPageState extends State<LoginCheckAccountPage> {
);
Widget _buildAccountTile(final Result<Account> result) {
if (result.hasError) {
return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingAccount,
state: ValidationState.canceled,
);
}
if (result.hasData) {
return NeonAccountTile(
account: result.requireData,

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

@ -6,7 +6,6 @@ import 'package:neon/src/blocs/login_check_server_status.dart';
import 'package:neon/src/router.dart';
import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/widgets/exception.dart';
import 'package:neon/src/widgets/linear_progress_indicator.dart';
import 'package:neon/src/widgets/validation_tile.dart';
import 'package:nextcloud/nextcloud.dart';
@ -64,20 +63,23 @@ class _LoginCheckServerStatusPageState extends State<LoginCheckServerStatusPage>
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
NeonLinearProgressIndicator(
visible: state.isLoading,
),
NeonException(
state.error,
onRetry: bloc.refresh,
if (state.hasError) ...[
NeonValidationTile(
title: NeonException.getDetails(context, state.error).text,
state: ValidationState.failure,
),
],
_buildServerVersionTile(state),
_buildMaintenanceModeTile(state),
Align(
alignment: Alignment.bottomRight,
child: ElevatedButton(
onPressed: success ? _onContinue : null,
child: Text(AppLocalizations.of(context).actionContinue),
onPressed: success ? _onContinue : bloc.refresh,
child: Text(
success
? AppLocalizations.of(context).actionContinue
: AppLocalizations.of(context).actionRetry,
),
),
),
],
@ -102,6 +104,13 @@ class _LoginCheckServerStatusPageState extends State<LoginCheckServerStatusPage>
}
Widget _buildServerVersionTile(final Result<CoreServerStatus> result) {
if (result.hasError) {
return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingServerVersion,
state: ValidationState.canceled,
);
}
if (!result.hasData) {
return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingServerVersion,
@ -123,6 +132,13 @@ class _LoginCheckServerStatusPageState extends State<LoginCheckServerStatusPage>
}
Widget _buildMaintenanceModeTile(final Result<CoreServerStatus> result) {
if (result.hasError) {
return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingMaintenanceMode,
state: ValidationState.canceled,
);
}
if (!result.hasData) {
return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingMaintenanceMode,

2
packages/neon/neon/lib/src/pages/login_flow.dart

@ -57,7 +57,7 @@ class _LoginFlowPageState extends State<LoginFlowPage> {
appBar: AppBar(),
body: Center(
child: Padding(
padding: const EdgeInsets.all(24),
padding: const EdgeInsets.all(10),
child: ResultBuilder.behaviorSubject(
stream: bloc.init,
builder: (final context, final init) => Column(

2
packages/neon/neon/lib/src/theme/branding.dart

@ -13,7 +13,7 @@ class Branding {
required this.name,
required this.logo,
this.legalese,
this.showLoginWithNextcloud = false,
this.showLoginWithNextcloud = true,
});
/// App name

6
packages/neon/neon/lib/src/theme/theme.dart

@ -48,6 +48,7 @@ class AppTheme {
snackBarTheme: _snackBarTheme,
dividerTheme: _dividerTheme,
scrollbarTheme: _scrollbarTheme,
inputDecorationTheme: _inputDecorationTheme,
extensions: [
neonTheme,
...?appThemes,
@ -70,4 +71,9 @@ class AppTheme {
static const _scrollbarTheme = ScrollbarThemeData(
interactive: true,
);
static const _inputDecorationTheme = InputDecorationTheme(
border: OutlineInputBorder(),
floatingLabelBehavior: FloatingLabelBehavior.always,
);
}

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

@ -27,7 +27,7 @@ class NeonException extends StatelessWidget {
final Color? color;
static void showSnackbar(final BuildContext context, final dynamic exception) {
final details = _getExceptionDetails(context, exception);
final details = getDetails(context, exception);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
@ -48,7 +48,7 @@ class NeonException extends StatelessWidget {
return const SizedBox();
}
final details = _getExceptionDetails(context, exception);
final details = getDetails(context, exception);
final color = this.color ?? Theme.of(context).colorScheme.error;
final errorIcon = Icon(
@ -109,7 +109,7 @@ class NeonException extends StatelessWidget {
);
}
static _ExceptionDetails _getExceptionDetails(final BuildContext context, final dynamic exception) {
static _ExceptionDetails getDetails(final BuildContext context, final dynamic exception) {
if (exception is String) {
return _ExceptionDetails(
text: exception,

2
packages/neon/neon/lib/src/widgets/nextcloud_logo.dart

@ -1,5 +1,6 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:neon/l10n/localizations.dart';
class NextcloudLogo extends StatelessWidget {
const NextcloudLogo({
@ -12,5 +13,6 @@ class NextcloudLogo extends StatelessWidget {
package: 'neon',
width: 100,
height: 100,
semanticsLabel: AppLocalizations.of(context).nextcloudLogo,
);
}

11
packages/neon/neon/lib/src/widgets/validation_tile.dart

@ -27,6 +27,11 @@ class NeonValidationTile extends StatelessWidget {
color: Theme.of(context).colorScheme.error,
size: size,
),
ValidationState.canceled => Icon(
Icons.cancel_outlined,
color: Theme.of(context).disabledColor,
size: size,
),
ValidationState.success => Icon(
Icons.check_circle,
color: Theme.of(context).colorScheme.primary,
@ -35,7 +40,10 @@ class NeonValidationTile extends StatelessWidget {
};
return ListTile(
leading: leading,
title: Text(title),
title: Text(
title,
style: state == ValidationState.canceled ? TextStyle(color: Theme.of(context).disabledColor) : null,
),
);
}
}
@ -43,5 +51,6 @@ class NeonValidationTile extends StatelessWidget {
enum ValidationState {
loading,
failure,
canceled,
success,
}

Loading…
Cancel
Save