Browse Source

fix(neon): Cleanup login pages layouts

pull/489/head
jld3103 2 years ago
parent
commit
ce2d95ee3e
No known key found for this signature in database
GPG Key ID: 9062417B9E8EB7B3
  1. 2
      packages/neon/neon/lib/l10n/en.arb
  2. 12
      packages/neon/neon/lib/l10n/localizations.dart
  3. 6
      packages/neon/neon/lib/l10n/localizations_en.dart
  4. 94
      packages/neon/neon/lib/src/pages/login.dart
  5. 45
      packages/neon/neon/lib/src/pages/login_check_account.dart
  6. 36
      packages/neon/neon/lib/src/pages/login_check_server_status.dart
  7. 2
      packages/neon/neon/lib/src/pages/login_flow.dart
  8. 6
      packages/neon/neon/lib/src/theme/theme.dart
  9. 6
      packages/neon/neon/lib/src/widgets/exception.dart
  10. 2
      packages/neon/neon/lib/src/widgets/nextcloud_logo.dart
  11. 11
      packages/neon/neon/lib/src/widgets/validation_tile.dart

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

@ -1,5 +1,7 @@
{ {
"@@locale": "en", "@@locale": "en",
"nextcloud": "Nextcloud",
"nextcloudLogo": "Nextcloud logo",
"appImplementationName": "{app, select, nextcloud{Nextcloud} core{Server} files{Files} news{News} notes{Notes} notifications{Notifications} other{}}", "appImplementationName": "{app, select, nextcloud{Nextcloud} core{Server} files{Files} news{News} notes{Notes} notifications{Notifications} other{}}",
"@appImplementationName": { "@appImplementationName": {
"placeholders": { "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. /// A list of this localizations delegate's supported locales.
static const List<Locale> supportedLocales = <Locale>[Locale('en')]; 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. /// No description provided for @appImplementationName.
/// ///
/// In en, this message translates to: /// 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 { class AppLocalizationsEn extends AppLocalizations {
AppLocalizationsEn([String locale = 'en']) : super(locale); AppLocalizationsEn([String locale = 'en']) : super(locale);
@override
String get nextcloud => 'Nextcloud';
@override
String get nextcloudLogo => 'Nextcloud logo';
@override @override
String appImplementationName(String app) { String appImplementationName(String app) {
String _temp0 = intl.Intl.selectLogic( String _temp0 = intl.Intl.selectLogic(

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

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

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

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

36
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/router.dart';
import 'package:neon/src/theme/dialog.dart'; import 'package:neon/src/theme/dialog.dart';
import 'package:neon/src/widgets/exception.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:neon/src/widgets/validation_tile.dart';
import 'package:nextcloud/nextcloud.dart'; import 'package:nextcloud/nextcloud.dart';
@ -64,20 +63,23 @@ class _LoginCheckServerStatusPageState extends State<LoginCheckServerStatusPage>
return Column( return Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
NeonLinearProgressIndicator( if (state.hasError) ...[
visible: state.isLoading, NeonValidationTile(
), title: NeonException.getDetails(context, state.error).text,
NeonException( state: ValidationState.failure,
state.error, ),
onRetry: bloc.refresh, ],
),
_buildServerVersionTile(state), _buildServerVersionTile(state),
_buildMaintenanceModeTile(state), _buildMaintenanceModeTile(state),
Align( Align(
alignment: Alignment.bottomRight, alignment: Alignment.bottomRight,
child: ElevatedButton( child: ElevatedButton(
onPressed: success ? _onContinue : null, onPressed: success ? _onContinue : bloc.refresh,
child: Text(AppLocalizations.of(context).actionContinue), 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) { Widget _buildServerVersionTile(final Result<CoreServerStatus> result) {
if (result.hasError) {
return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingServerVersion,
state: ValidationState.canceled,
);
}
if (!result.hasData) { if (!result.hasData) {
return NeonValidationTile( return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingServerVersion, title: AppLocalizations.of(context).loginCheckingServerVersion,
@ -123,6 +132,13 @@ class _LoginCheckServerStatusPageState extends State<LoginCheckServerStatusPage>
} }
Widget _buildMaintenanceModeTile(final Result<CoreServerStatus> result) { Widget _buildMaintenanceModeTile(final Result<CoreServerStatus> result) {
if (result.hasError) {
return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingMaintenanceMode,
state: ValidationState.canceled,
);
}
if (!result.hasData) { if (!result.hasData) {
return NeonValidationTile( return NeonValidationTile(
title: AppLocalizations.of(context).loginCheckingMaintenanceMode, 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(), appBar: AppBar(),
body: Center( body: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(10),
child: ResultBuilder.behaviorSubject( child: ResultBuilder.behaviorSubject(
stream: bloc.init, stream: bloc.init,
builder: (final context, final init) => Column( builder: (final context, final init) => Column(

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

@ -48,6 +48,7 @@ class AppTheme {
snackBarTheme: _snackBarTheme, snackBarTheme: _snackBarTheme,
dividerTheme: _dividerTheme, dividerTheme: _dividerTheme,
scrollbarTheme: _scrollbarTheme, scrollbarTheme: _scrollbarTheme,
inputDecorationTheme: _inputDecorationTheme,
extensions: [ extensions: [
neonTheme, neonTheme,
...?appThemes, ...?appThemes,
@ -70,4 +71,9 @@ class AppTheme {
static const _scrollbarTheme = ScrollbarThemeData( static const _scrollbarTheme = ScrollbarThemeData(
interactive: true, 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; final Color? color;
static void showSnackbar(final BuildContext context, final dynamic exception) { static void showSnackbar(final BuildContext context, final dynamic exception) {
final details = _getExceptionDetails(context, exception); final details = getDetails(context, exception);
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@ -48,7 +48,7 @@ class NeonException extends StatelessWidget {
return const SizedBox(); return const SizedBox();
} }
final details = _getExceptionDetails(context, exception); final details = getDetails(context, exception);
final color = this.color ?? Theme.of(context).colorScheme.error; final color = this.color ?? Theme.of(context).colorScheme.error;
final errorIcon = Icon( 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) { if (exception is String) {
return _ExceptionDetails( return _ExceptionDetails(
text: exception, text: exception,

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

@ -1,5 +1,6 @@
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:neon/l10n/localizations.dart';
class NextcloudLogo extends StatelessWidget { class NextcloudLogo extends StatelessWidget {
const NextcloudLogo({ const NextcloudLogo({
@ -12,5 +13,6 @@ class NextcloudLogo extends StatelessWidget {
package: 'neon', package: 'neon',
width: 100, width: 100,
height: 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, color: Theme.of(context).colorScheme.error,
size: size, size: size,
), ),
ValidationState.canceled => Icon(
Icons.cancel_outlined,
color: Theme.of(context).disabledColor,
size: size,
),
ValidationState.success => Icon( ValidationState.success => Icon(
Icons.check_circle, Icons.check_circle,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
@ -35,7 +40,10 @@ class NeonValidationTile extends StatelessWidget {
}; };
return ListTile( return ListTile(
leading: leading, 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 { enum ValidationState {
loading, loading,
failure, failure,
canceled,
success, success,
} }

Loading…
Cancel
Save