mirror of
https://github.com/Xevion/paperless-mobile.git
synced 2025-12-15 02:12:25 -06:00
fix: Add custom fields, translations, add app logs to login routes
This commit is contained in:
@@ -112,6 +112,8 @@ class AuthenticationCubit extends Cubit<AuthenticationState> {
|
||||
/// Switches to another account if it exists.
|
||||
Future<void> switchAccount(String localUserId) async {
|
||||
emit(const SwitchingAccountsState());
|
||||
await FileService.instance.initialize();
|
||||
|
||||
final redactedId = redactUserId(localUserId);
|
||||
logger.fd(
|
||||
'Trying to switch to user $redactedId...',
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_api/paperless_api.dart';
|
||||
import 'package:paperless_mobile/constants.dart';
|
||||
import 'package:paperless_mobile/core/exception/server_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/model/info_message_exception.dart';
|
||||
import 'package:paperless_mobile/core/service/connectivity_status_service.dart';
|
||||
@@ -13,10 +17,13 @@ import 'package:paperless_mobile/features/login/model/client_certificate_form_mo
|
||||
import 'package:paperless_mobile/features/login/model/login_form_credentials.dart';
|
||||
import 'package:paperless_mobile/features/login/model/reachability_status.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/login_settings_page.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/server_address_form_field.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/user_credentials_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/assets.gen.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:paperless_mobile/routing/routes/app_logs_route.dart';
|
||||
|
||||
class AddAccountPage extends StatefulWidget {
|
||||
final FutureOr<void> Function(
|
||||
@@ -58,10 +65,172 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
final _formKey = GlobalKey<FormBuilderState>();
|
||||
bool _isCheckingConnection = false;
|
||||
ReachabilityStatus _reachabilityStatus = ReachabilityStatus.unknown;
|
||||
|
||||
bool _isFormSubmitted = false;
|
||||
|
||||
final _pageController = PageController();
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
appBar: AppBar(
|
||||
title: Text(widget.titleText),
|
||||
),
|
||||
body: FormBuilder(
|
||||
key: _formKey,
|
||||
child: AutofillGroup(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Assets.logos.paperlessLogoGreenPng.image(
|
||||
width: 150,
|
||||
height: 150,
|
||||
),
|
||||
Text(
|
||||
'Paperless Mobile',
|
||||
style: Theme.of(context).textTheme.displaySmall,
|
||||
).padded(),
|
||||
SizedBox(height: 24),
|
||||
Expanded(
|
||||
child: PageView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: _pageController,
|
||||
allowImplicitScrolling: false,
|
||||
children: [
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_reachabilityStatus = ReachabilityStatus.unknown;
|
||||
});
|
||||
},
|
||||
).paddedSymmetrically(
|
||||
horizontal: 12,
|
||||
vertical: 12,
|
||||
),
|
||||
ClientCertificateFormField(
|
||||
initialBytes: widget.initialClientCertificate?.bytes,
|
||||
initialPassphrase:
|
||||
widget.initialClientCertificate?.passphrase,
|
||||
).padded(),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
//TODO: Move additional headers and client cert to separate page
|
||||
// IconButton.filledTonal(
|
||||
// onPressed: () {
|
||||
// Navigator.of(context).push(
|
||||
// MaterialPageRoute(builder: (context) {
|
||||
// return LoginSettingsPage();
|
||||
// }),
|
||||
// );
|
||||
// },
|
||||
// icon: Icon(Icons.settings),
|
||||
// ),
|
||||
SizedBox(width: 8),
|
||||
FilledButton.icon(
|
||||
onPressed: () async {
|
||||
final status = await _updateReachability();
|
||||
if (status == ReachabilityStatus.reachable) {
|
||||
Future.delayed(1.seconds, () {
|
||||
_pageController.nextPage(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
});
|
||||
}
|
||||
},
|
||||
icon: _isCheckingConnection
|
||||
? SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondary,
|
||||
),
|
||||
)
|
||||
: _reachabilityStatus ==
|
||||
ReachabilityStatus.reachable
|
||||
? Icon(Icons.done)
|
||||
: Icon(Icons.arrow_forward),
|
||||
label: Text(S.of(context)!.continueLabel),
|
||||
),
|
||||
],
|
||||
).paddedSymmetrically(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
_buildStatusIndicator().padded(),
|
||||
],
|
||||
),
|
||||
Column(
|
||||
children: [
|
||||
UserCredentialsFormField(
|
||||
formKey: _formKey,
|
||||
initialUsername: widget.initialUsername,
|
||||
initialPassword: widget.initialPassword,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
TextButton.icon(
|
||||
onPressed: () {
|
||||
_pageController.previousPage(
|
||||
duration: Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
},
|
||||
icon: Icon(Icons.arrow_back),
|
||||
label: Text(S.of(context)!.edit),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_onSubmit();
|
||||
},
|
||||
child: Text(S.of(context)!.signIn),
|
||||
),
|
||||
],
|
||||
).padded(),
|
||||
Text(
|
||||
S.of(context)!.loginRequiredPermissionsHint,
|
||||
style: Theme.of(context).textTheme.bodySmall?.apply(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onBackground
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
style: Theme.of(context).textTheme.labelLarge,
|
||||
children: [
|
||||
TextSpan(text: S.of(context)!.version(packageInfo.version)),
|
||||
WidgetSpan(child: SizedBox(width: 24)),
|
||||
TextSpan(
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
text: S.of(context)!.appLogs(''),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
AppLogsRoute().push(context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
).padded(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.titleText),
|
||||
@@ -91,7 +260,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
children: [
|
||||
ServerAddressFormField(
|
||||
initialValue: widget.initialServerUrl,
|
||||
onSubmit: (address) {
|
||||
onChanged: (address) {
|
||||
_updateReachability(address);
|
||||
},
|
||||
).padded(),
|
||||
@@ -117,7 +286,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
.withOpacity(0.6),
|
||||
),
|
||||
).padded(16),
|
||||
]
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -125,7 +294,7 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _updateReachability([String? address]) async {
|
||||
Future<ReachabilityStatus> _updateReachability([String? address]) async {
|
||||
setState(() {
|
||||
_isCheckingConnection = true;
|
||||
});
|
||||
@@ -150,13 +319,10 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
_isCheckingConnection = false;
|
||||
_reachabilityStatus = status;
|
||||
});
|
||||
return status;
|
||||
}
|
||||
|
||||
Widget _buildStatusIndicator() {
|
||||
if (_isCheckingConnection) {
|
||||
return const ListTile();
|
||||
}
|
||||
|
||||
Widget _buildIconText(
|
||||
IconData icon,
|
||||
String text, [
|
||||
@@ -176,14 +342,6 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
|
||||
Color errorColor = Theme.of(context).colorScheme.error;
|
||||
switch (_reachabilityStatus) {
|
||||
case ReachabilityStatus.unknown:
|
||||
return Container();
|
||||
case ReachabilityStatus.reachable:
|
||||
return _buildIconText(
|
||||
Icons.done,
|
||||
S.of(context)!.connectionSuccessfulylEstablished,
|
||||
Colors.green,
|
||||
);
|
||||
case ReachabilityStatus.notReachable:
|
||||
return _buildIconText(
|
||||
Icons.close,
|
||||
@@ -214,6 +372,8 @@ class _AddAccountPageState extends State<AddAccountPage> {
|
||||
S.of(context)!.connectionTimedOut,
|
||||
errorColor,
|
||||
);
|
||||
default:
|
||||
return const ListTile();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,8 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/features/login/model/client_certificate_form_model.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
import 'package:paperless_mobile/helpers/message_helpers.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'obscured_input_text_form_field.dart';
|
||||
|
||||
class ClientCertificateFormField extends StatefulWidget {
|
||||
@@ -16,10 +17,10 @@ class ClientCertificateFormField extends StatefulWidget {
|
||||
final String? initialPassphrase;
|
||||
final Uint8List? initialBytes;
|
||||
|
||||
final void Function(ClientCertificateFormModel? cert) onChanged;
|
||||
final ValueChanged<ClientCertificateFormModel?>? onChanged;
|
||||
const ClientCertificateFormField({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
this.onChanged,
|
||||
this.initialPassphrase,
|
||||
this.initialBytes,
|
||||
});
|
||||
@@ -29,13 +30,15 @@ class ClientCertificateFormField extends StatefulWidget {
|
||||
_ClientCertificateFormFieldState();
|
||||
}
|
||||
|
||||
class _ClientCertificateFormFieldState
|
||||
extends State<ClientCertificateFormField> {
|
||||
class _ClientCertificateFormFieldState extends State<ClientCertificateFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
File? _selectedFile;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<ClientCertificateFormModel?>(
|
||||
key: const ValueKey('login-client-cert'),
|
||||
name: ClientCertificateFormField.fkClientCertificate,
|
||||
onChanged: widget.onChanged,
|
||||
initialValue: widget.initialBytes != null
|
||||
? ClientCertificateFormModel(
|
||||
@@ -43,16 +46,6 @@ class _ClientCertificateFormFieldState
|
||||
passphrase: widget.initialPassphrase,
|
||||
)
|
||||
: null,
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
assert(_selectedFile != null);
|
||||
if (_selectedFile?.path.split(".").last != 'pfx') {
|
||||
return S.of(context)!.invalidCertificateFormat;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
builder: (field) {
|
||||
final theme =
|
||||
Theme.of(context).copyWith(dividerColor: Colors.transparent); //new
|
||||
@@ -127,7 +120,6 @@ class _ClientCertificateFormFieldState
|
||||
),
|
||||
);
|
||||
},
|
||||
name: ClientCertificateFormField.fkClientCertificate,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -140,6 +132,11 @@ class _ClientCertificateFormFieldState
|
||||
if (result == null || result.files.single.path == null) {
|
||||
return;
|
||||
}
|
||||
final path = result.files.single.path!;
|
||||
if (p.extension(path) != '.pfx') {
|
||||
showSnackBar(context, S.of(context)!.invalidCertificateFormat);
|
||||
return;
|
||||
}
|
||||
File file = File(result.files.single.path!);
|
||||
setState(() {
|
||||
_selectedFile = file;
|
||||
@@ -171,4 +168,7 @@ class _ClientCertificateFormFieldState
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:paperless_mobile/features/login/view/widgets/form_fields/client_certificate_form_field.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
|
||||
class LoginSettingsPage extends StatelessWidget {
|
||||
const LoginSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(S.of(context)!.settings),
|
||||
),
|
||||
body: ListView(
|
||||
children: [
|
||||
ClientCertificateFormField(onChanged: (certificate) {}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -9,10 +9,11 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
class ServerAddressFormField extends StatefulWidget {
|
||||
static const String fkServerAddress = "serverAddress";
|
||||
final String? initialValue;
|
||||
final void Function(String? address) onSubmit;
|
||||
final ValueChanged<String?>? onChanged;
|
||||
|
||||
const ServerAddressFormField({
|
||||
Key? key,
|
||||
required this.onSubmit,
|
||||
this.onChanged,
|
||||
this.initialValue,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -20,8 +21,10 @@ class ServerAddressFormField extends StatefulWidget {
|
||||
State<ServerAddressFormField> createState() => _ServerAddressFormFieldState();
|
||||
}
|
||||
|
||||
class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
class _ServerAddressFormFieldState extends State<ServerAddressFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
bool _canClear = false;
|
||||
final _textFieldKey = GlobalKey();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -38,10 +41,12 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<String>(
|
||||
initialValue: widget.initialValue,
|
||||
name: ServerAddressFormField.fkServerAddress,
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
onChanged: widget.onChanged,
|
||||
builder: (field) {
|
||||
return RawAutocomplete<String>(
|
||||
focusNode: _focusNode,
|
||||
@@ -51,6 +56,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
onSelected: onSelected,
|
||||
options: options,
|
||||
maxOptionsHeight: 200.0,
|
||||
maxWidth: MediaQuery.sizeOf(context).width - 40,
|
||||
);
|
||||
},
|
||||
key: const ValueKey('login-server-address'),
|
||||
@@ -60,12 +66,12 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
.where((element) => element.contains(textEditingValue.text));
|
||||
},
|
||||
onSelected: (option) {
|
||||
_formatInput();
|
||||
field.didChange(_textEditingController.text);
|
||||
_formatInput(field);
|
||||
},
|
||||
fieldViewBuilder:
|
||||
(context, textEditingController, focusNode, onFieldSubmitted) {
|
||||
return TextFormField(
|
||||
key: _textFieldKey,
|
||||
controller: textEditingController,
|
||||
focusNode: focusNode,
|
||||
decoration: InputDecoration(
|
||||
@@ -78,15 +84,22 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
onPressed: () {
|
||||
textEditingController.clear();
|
||||
field.didChange(textEditingController.text);
|
||||
widget.onSubmit(textEditingController.text);
|
||||
},
|
||||
)
|
||||
: null,
|
||||
),
|
||||
autofocus: false,
|
||||
onFieldSubmitted: (_) {
|
||||
_formatInput(field);
|
||||
onFieldSubmitted();
|
||||
_formatInput();
|
||||
},
|
||||
onTapOutside: (event) {
|
||||
if (!FocusScope.of(context).hasFocus) {
|
||||
return;
|
||||
}
|
||||
_formatInput(field);
|
||||
onFieldSubmitted();
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||
validator: (value) {
|
||||
@@ -113,7 +126,7 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
);
|
||||
}
|
||||
|
||||
void _formatInput() {
|
||||
void _formatInput(FormFieldState<String> field) {
|
||||
String address = _textEditingController.text.trim();
|
||||
address = address.replaceAll(RegExp(r'^\/+|\/+$'), '');
|
||||
_textEditingController.text = address;
|
||||
@@ -121,8 +134,11 @@ class _ServerAddressFormFieldState extends State<ServerAddressFormField> {
|
||||
baseOffset: address.length,
|
||||
extentOffset: address.length,
|
||||
);
|
||||
widget.onSubmit(address);
|
||||
field.didChange(_textEditingController.text);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
/// Taken from [Autocomplete]
|
||||
@@ -131,12 +147,14 @@ class _AutocompleteOptions extends StatelessWidget {
|
||||
required this.onSelected,
|
||||
required this.options,
|
||||
required this.maxOptionsHeight,
|
||||
required this.maxWidth,
|
||||
});
|
||||
|
||||
final AutocompleteOnSelected<String> onSelected;
|
||||
|
||||
final Iterable<String> options;
|
||||
final double maxOptionsHeight;
|
||||
final double maxWidth;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -145,7 +163,10 @@ class _AutocompleteOptions extends StatelessWidget {
|
||||
child: Material(
|
||||
elevation: 4.0,
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(maxHeight: maxOptionsHeight),
|
||||
constraints: BoxConstraints(
|
||||
maxHeight: maxOptionsHeight,
|
||||
maxWidth: maxWidth,
|
||||
),
|
||||
child: ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
shrinkWrap: true,
|
||||
|
||||
@@ -12,13 +12,13 @@ import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
class UserCredentialsFormField extends StatefulWidget {
|
||||
static const fkCredentials = 'credentials';
|
||||
|
||||
final void Function() onFieldsSubmitted;
|
||||
final VoidCallback? onFieldsSubmitted;
|
||||
final String? initialUsername;
|
||||
final String? initialPassword;
|
||||
final GlobalKey<FormBuilderState> formKey;
|
||||
const UserCredentialsFormField({
|
||||
Key? key,
|
||||
required this.onFieldsSubmitted,
|
||||
this.onFieldsSubmitted,
|
||||
this.initialUsername,
|
||||
this.initialPassword,
|
||||
required this.formKey,
|
||||
@@ -29,12 +29,14 @@ class UserCredentialsFormField extends StatefulWidget {
|
||||
_UserCredentialsFormFieldState();
|
||||
}
|
||||
|
||||
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
class _UserCredentialsFormFieldState extends State<UserCredentialsFormField>
|
||||
with AutomaticKeepAliveClientMixin {
|
||||
final _usernameFocusNode = FocusNode();
|
||||
final _passwordFocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
return FormBuilderField<LoginFormCredentials?>(
|
||||
initialValue: LoginFormCredentials(
|
||||
password: widget.initialPassword,
|
||||
@@ -87,7 +89,7 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
LoginFormCredentials(password: password),
|
||||
),
|
||||
onFieldSubmitted: (_) {
|
||||
widget.onFieldsSubmitted();
|
||||
widget.onFieldsSubmitted?.call();
|
||||
},
|
||||
validator: (value) {
|
||||
if (value?.trim().isEmpty ?? true) {
|
||||
@@ -100,6 +102,9 @@ class _UserCredentialsFormFieldState extends State<UserCredentialsFormField> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:paperless_mobile/core/extensions/flutter_extensions.dart';
|
||||
import 'package:paperless_mobile/generated/l10n/app_localizations.dart';
|
||||
import 'package:paperless_mobile/routing/routes/app_logs_route.dart';
|
||||
import 'package:paperless_mobile/theme.dart';
|
||||
|
||||
class LoginTransitionPage extends StatelessWidget {
|
||||
@@ -20,10 +22,25 @@ class LoginTransitionPage extends StatelessWidget {
|
||||
body: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const CircularProgressIndicator(),
|
||||
const SizedBox(height: 16),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Text(text).paddedOnly(bottom: 24),
|
||||
),
|
||||
],
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Text(text).paddedOnly(bottom: 24),
|
||||
child: TextButton(
|
||||
child: Text(S.of(context)!.appLogs('')),
|
||||
onPressed: () {
|
||||
AppLogsRoute().push(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
).padded(16),
|
||||
|
||||
Reference in New Issue
Block a user