From 1b9e4fbb813821998770c088f1cc6000f52e860c Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Fri, 21 Apr 2023 18:56:36 +0200 Subject: [PATCH] feat: rework account management page, add tooltip to settings --- lib/core/bloc/bloc_changes_observer.dart | 8 - .../view/sliver_search_bar.dart | 17 +- .../widgets/items/document_detailed_item.dart | 4 +- lib/features/inbox/cubit/inbox_cubit.dart | 16 +- .../inbox/view/widgets/inbox_item.dart | 31 +-- .../login/cubit/authentication_cubit.dart | 1 + lib/features/login/view/login_page.dart | 5 +- .../login_pages/server_connection_page.dart | 4 +- .../search_app_bar/view/search_app_bar.dart | 71 ------ .../settings/view/manage_accounts_page.dart | 232 ++++++++++++------ .../view/pages/application_settings_page.dart | 10 + .../view/pages/security_settings_page.dart | 14 +- .../settings/view/widgets/user_avatar.dart | 28 +++ lib/main.dart | 1 + 14 files changed, 246 insertions(+), 196 deletions(-) delete mode 100644 lib/core/bloc/bloc_changes_observer.dart delete mode 100644 lib/features/search_app_bar/view/search_app_bar.dart create mode 100644 lib/features/settings/view/widgets/user_avatar.dart diff --git a/lib/core/bloc/bloc_changes_observer.dart b/lib/core/bloc/bloc_changes_observer.dart deleted file mode 100644 index f06f670..0000000 --- a/lib/core/bloc/bloc_changes_observer.dart +++ /dev/null @@ -1,8 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; - -class BlocChangesObserver extends BlocObserver { - @override - void onChange(BlocBase bloc, Change change) { - super.onChange(bloc, change); - } -} diff --git a/lib/features/document_search/view/sliver_search_bar.dart b/lib/features/document_search/view/sliver_search_bar.dart index 52caa7f..482232d 100644 --- a/lib/features/document_search/view/sliver_search_bar.dart +++ b/lib/features/document_search/view/sliver_search_bar.dart @@ -1,12 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:hive_flutter/adapters.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart'; import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart'; +import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/delegate/customizable_sliver_persistent_header_delegate.dart'; import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart' as s; import 'package:paperless_mobile/features/document_search/view/document_search_page.dart'; +import 'package:paperless_mobile/features/login/model/user_account.dart'; import 'package:paperless_mobile/features/settings/view/dialogs/account_settings_dialog.dart'; import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart'; +import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; +import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; class SliverSearchBar extends StatelessWidget { @@ -37,10 +42,14 @@ class SliverSearchBar extends StatelessWidget { onPressed: Scaffold.of(context).openDrawer, ), trailingIcon: IconButton( - icon: BlocBuilder( - builder: (context, state) { - return CircleAvatar( - child: Text(state.information?.userInitials ?? ''), + icon: GlobalSettingsBuilder( + builder: (context, settings) { + return ValueListenableBuilder( + valueListenable: Hive.box(HiveBoxes.userAccount).listenable(), + builder: (context, box, _) { + final account = box.get(settings.currentLoggedInUser!)!; + return UserAvatar(userId: settings.currentLoggedInUser!, account: account); + }, ); }, ), diff --git a/lib/features/documents/view/widgets/items/document_detailed_item.dart b/lib/features/documents/view/widgets/items/document_detailed_item.dart index 8b2e8a3..bda4953 100644 --- a/lib/features/documents/view/widgets/items/document_detailed_item.dart +++ b/lib/features/documents/view/widgets/items/document_detailed_item.dart @@ -1,16 +1,14 @@ import 'dart:math'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_html/flutter_html.dart'; import 'package:intl/intl.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; -import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart'; import 'package:paperless_mobile/features/labels/correspondent/view/widgets/correspondent_widget.dart'; import 'package:paperless_mobile/features/labels/document_type/view/widgets/document_type_widget.dart'; import 'package:paperless_mobile/features/labels/tags/view/widgets/tags_widget.dart'; -import 'package:flutter_html/flutter_html.dart'; class DocumentDetailedItem extends DocumentItem { final String? highlights; diff --git a/lib/features/inbox/cubit/inbox_cubit.dart b/lib/features/inbox/cubit/inbox_cubit.dart index 8ab72af..e2b3493 100644 --- a/lib/features/inbox/cubit/inbox_cubit.dart +++ b/lib/features/inbox/cubit/inbox_cubit.dart @@ -89,14 +89,16 @@ class InboxCubit extends HydratedCubit with DocumentPagingBlocMixin ), ); } + if (!isClosed) { + emit(state.copyWith(inboxTags: inboxTags)); - emit(state.copyWith(inboxTags: inboxTags)); - updateFilter( - filter: DocumentFilter( - sortField: SortField.added, - tags: IdsTagsQuery.fromIds(inboxTags), - ), - ); + updateFilter( + filter: DocumentFilter( + sortField: SortField.added, + tags: IdsTagsQuery.fromIds(inboxTags), + ), + ); + } } } diff --git a/lib/features/inbox/view/widgets/inbox_item.dart b/lib/features/inbox/view/widgets/inbox_item.dart index f39b85b..6c12d56 100644 --- a/lib/features/inbox/view/widgets/inbox_item.dart +++ b/lib/features/inbox/view/widgets/inbox_item.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; @@ -72,14 +73,10 @@ class _InboxItemState extends State { _buildTextWithLeadingIcon( Icon( Icons.person_outline, - size: Theme.of(context) - .textTheme - .bodyMedium - ?.fontSize, + size: Theme.of(context).textTheme.bodyMedium?.fontSize, ), LabelText( - label: state.labels.correspondents[ - widget.document.correspondent], + label: state.labels.correspondents[widget.document.correspondent], style: Theme.of(context).textTheme.bodyMedium, placeholder: "-", ), @@ -87,14 +84,10 @@ class _InboxItemState extends State { _buildTextWithLeadingIcon( Icon( Icons.description_outlined, - size: Theme.of(context) - .textTheme - .bodyMedium - ?.fontSize, + size: Theme.of(context).textTheme.bodyMedium?.fontSize, ), LabelText( - label: state.labels.documentTypes[ - widget.document.documentType], + label: state.labels.documentTypes[widget.document.documentType], style: Theme.of(context).textTheme.bodyMedium, placeholder: "-", ), @@ -102,8 +95,10 @@ class _InboxItemState extends State { const Spacer(), TagsWidget( tags: widget.document.tags - .map((e) => state.labels.tags[e]!) - .toList(), + .map((e) => state.labels.tags[e]) + .whereNot((element) => element == null) + .toList() + .cast(), isMultiLine: false, isClickable: false, showShortNames: true, @@ -142,8 +137,7 @@ class _InboxItemState extends State { onPressed: () async { final shouldDelete = await showDialog( context: context, - builder: (context) => DeleteDocumentConfirmationDialog( - document: widget.document), + builder: (context) => DeleteDocumentConfirmationDialog(document: widget.document), ) ?? false; if (shouldDelete) { @@ -237,10 +231,7 @@ class _InboxItemState extends State { setState(() { _isAsnAssignLoading = true; }); - context - .read() - .assignAsn(widget.document) - .whenComplete( + context.read().assignAsn(widget.document).whenComplete( () => setState(() => _isAsnAssignLoading = false), ); } diff --git a/lib/features/login/cubit/authentication_cubit.dart b/lib/features/login/cubit/authentication_cubit.dart index 4b08951..e06a66c 100644 --- a/lib/features/login/cubit/authentication_cubit.dart +++ b/lib/features/login/cubit/authentication_cubit.dart @@ -170,6 +170,7 @@ class AuthenticationCubit extends Cubit { }) async { assert(credentials.password != null && credentials.username != null); final userId = "${credentials.username}@$serverUrl"; + final userAccountsBox = Hive.box(HiveBoxes.userAccount); final userSettingsBox = Hive.box(HiveBoxes.userSettings); diff --git a/lib/features/login/view/login_page.dart b/lib/features/login/view/login_page.dart index 937c801..c8a1b9f 100644 --- a/lib/features/login/view/login_page.dart +++ b/lib/features/login/view/login_page.dart @@ -28,11 +28,13 @@ class LoginPage extends StatefulWidget { ) onSubmit; final String submitText; + final String titleString; const LoginPage({ Key? key, required this.onSubmit, required this.submitText, + required this.titleString, }) : super(key: key); @override @@ -47,7 +49,7 @@ class _LoginPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - resizeToAvoidBottomInset: false, // appBar: AppBar( + resizeToAvoidBottomInset: false, body: FormBuilder( key: _formKey, child: PageView( @@ -55,6 +57,7 @@ class _LoginPageState extends State { scrollBehavior: NeverScrollableScrollBehavior(), children: [ ServerConnectionPage( + titleString: widget.titleString, formBuilderKey: _formKey, onContinue: () { _pageController.nextPage( diff --git a/lib/features/login/view/widgets/login_pages/server_connection_page.dart b/lib/features/login/view/widgets/login_pages/server_connection_page.dart index c300a20..d289eab 100644 --- a/lib/features/login/view/widgets/login_pages/server_connection_page.dart +++ b/lib/features/login/view/widgets/login_pages/server_connection_page.dart @@ -15,11 +15,13 @@ import 'package:provider/provider.dart'; class ServerConnectionPage extends StatefulWidget { final GlobalKey formBuilderKey; final void Function() onContinue; + final String titleString; const ServerConnectionPage({ super.key, required this.formBuilderKey, required this.onContinue, + required this.titleString, }); @override @@ -35,7 +37,7 @@ class _ServerConnectionPageState extends State { return Scaffold( appBar: AppBar( toolbarHeight: kToolbarHeight - 4, - title: Text(S.of(context)!.connectToPaperless), + title: Text(widget.titleString), bottom: PreferredSize( child: _isCheckingConnection ? const LinearProgressIndicator() : const SizedBox(height: 4.0), diff --git a/lib/features/search_app_bar/view/search_app_bar.dart b/lib/features/search_app_bar/view/search_app_bar.dart deleted file mode 100644 index d8611cf..0000000 --- a/lib/features/search_app_bar/view/search_app_bar.dart +++ /dev/null @@ -1,71 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:paperless_mobile/core/bloc/paperless_server_information_cubit.dart'; -import 'package:paperless_mobile/core/bloc/paperless_server_information_state.dart'; -import 'package:paperless_mobile/core/widgets/material/search/m3_search_bar.dart' as s; -import 'package:paperless_mobile/extensions/flutter_extensions.dart'; -import 'package:paperless_mobile/features/settings/view/dialogs/account_settings_dialog.dart'; -import 'package:paperless_mobile/features/settings/view/manage_accounts_page.dart'; - -typedef OpenSearchCallback = void Function(BuildContext context); - -class SearchAppBar extends StatefulWidget implements PreferredSizeWidget { - final PreferredSizeWidget? bottom; - final OpenSearchCallback onOpenSearch; - final Color? backgroundColor; - final String hintText; - const SearchAppBar({ - super.key, - required this.onOpenSearch, - this.bottom, - this.backgroundColor, - required this.hintText, - }); - - @override - State createState() => _SearchAppBarState(); - - @override - Size get preferredSize => const Size.fromHeight(kToolbarHeight); -} - -class _SearchAppBarState extends State { - @override - Widget build(BuildContext context) { - return SliverAppBar( - floating: true, - pinned: true, - snap: true, - automaticallyImplyLeading: false, - backgroundColor: widget.backgroundColor, - title: s.SearchBar( - height: kToolbarHeight - 12, - supportingText: widget.hintText, - onTap: () => widget.onOpenSearch(context), - leadingIcon: IconButton( - icon: const Icon(Icons.menu), - onPressed: Scaffold.of(context).openDrawer, - ), - trailingIcon: IconButton( - icon: BlocBuilder( - builder: (context, state) { - return CircleAvatar( - child: Text(state.information?.userInitials ?? ''), - ); - }, - ), - onPressed: () { - showDialog( - context: context, - builder: (context) => BlocProvider.value( - value: context.read(), - child: const ManageAccountsPage(), - ), - ); - }, - ), - ).paddedOnly(top: 8, bottom: 4), - bottom: widget.bottom, - ); - } -} diff --git a/lib/features/settings/view/manage_accounts_page.dart b/lib/features/settings/view/manage_accounts_page.dart index 9517671..bd952d6 100644 --- a/lib/features/settings/view/manage_accounts_page.dart +++ b/lib/features/settings/view/manage_accounts_page.dart @@ -1,7 +1,10 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_typeahead/flutter_typeahead.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart'; +import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart'; import 'package:paperless_mobile/features/login/model/login_form_credentials.dart'; import 'package:paperless_mobile/features/login/model/user_account.dart'; @@ -10,6 +13,7 @@ import 'package:paperless_mobile/features/settings/model/global_settings.dart'; import 'package:paperless_mobile/features/settings/view/dialogs/switch_account_dialog.dart'; import 'package:paperless_mobile/features/settings/view/pages/switching_accounts_page.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; +import 'package:paperless_mobile/features/settings/view/widgets/user_avatar.dart'; class ManageAccountsPage extends StatelessWidget { const ManageAccountsPage({super.key}); @@ -19,43 +23,8 @@ class ManageAccountsPage extends StatelessWidget { return Dialog.fullscreen( child: Scaffold( appBar: AppBar( - leading: CloseButton(), - title: Text("Manage Accounts"), //TODO: INTL - ), - floatingActionButton: FloatingActionButton.extended( - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => LoginPage( - onSubmit: (context, username, password, serverUrl, clientCertificate) async { - final userId = await context.read().addAccount( - credentials: LoginFormCredentials( - username: username, - password: password, - ), - clientCertificate: clientCertificate, - serverUrl: serverUrl, - //TODO: Ask user whether to enable biometric authentication - enableBiometricAuthentication: false, - ); - final shoudSwitch = await showDialog( - context: context, - builder: (context) => - SwitchAccountDialog(username: username, serverUrl: serverUrl), - ) ?? - false; - if (shoudSwitch) { - context.read().switchAccount(userId); - } - }, - submitText: "Add account", - ), - ), - ); - }, - label: Text("Add account"), - icon: Icon(Icons.person_add), + leading: const CloseButton(), + title: const Text("Accounts"), //TODO: INTL ), body: GlobalSettingsBuilder( builder: (context, globalSettings) { @@ -63,16 +32,67 @@ class ManageAccountsPage extends StatelessWidget { valueListenable: Hive.box(HiveBoxes.userAccount).listenable(), builder: (context, box, _) { final userIds = box.keys.toList().cast(); - return ListView.builder( - itemBuilder: (context, index) { - return _buildAccountTile( - context, - userIds[index], - box.get(userIds[index])!, - globalSettings, - ); - }, - itemCount: userIds.length, + final otherAccounts = userIds + .whereNot((element) => element == globalSettings.currentLoggedInUser) + .toList(); + return CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Your account", //TODO: INTL + style: Theme.of(context).textTheme.labelLarge, + ).padded(16), + _buildAccountTile( + context, + globalSettings.currentLoggedInUser!, + box.get(globalSettings.currentLoggedInUser!)!, + globalSettings, + ), + if (otherAccounts.isNotEmpty) const Divider(), + ], + ), + ), + if (otherAccounts.isNotEmpty) + SliverToBoxAdapter( + child: Text( + "Other accounts", //TODO: INTL + style: Theme.of(context).textTheme.labelLarge, + ).padded(16), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) => _buildAccountTile( + context, + otherAccounts[index], + box.get(otherAccounts[index])!, + globalSettings, + ), + childCount: otherAccounts.length, + ), + ), + SliverToBoxAdapter( + child: Column( + children: [ + const Divider(), + ListTile( + title: const Text("Add account"), + leading: const Icon(Icons.person_add), + onTap: () { + _onAddAccount(context); + }, + ), + // FilledButton.tonalIcon( + // icon: Icon(Icons.person_add), + // label: Text("Add account"), + // onPressed: () {}, + // ), + ], + ), + ), + ], ); }, ); @@ -83,10 +103,13 @@ class ManageAccountsPage extends StatelessWidget { } Widget _buildAccountTile( - BuildContext context, String userId, UserAccount account, GlobalSettings settings) { + BuildContext context, + String userId, + UserAccount account, + GlobalSettings settings, + ) { final theme = Theme.of(context); return ListTile( - selected: userId == settings.currentLoggedInUser, title: Text(account.username), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -96,41 +119,90 @@ class ManageAccountsPage extends StatelessWidget { ], ), isThreeLine: true, - leading: CircleAvatar( - child: Text((account.fullName ?? account.username) - .split(" ") - .take(2) - .map((e) => e.substring(0, 1)) - .map((e) => e.toUpperCase()) - .join(" ")), + leading: UserAvatar( + account: account, + userId: userId, ), - onTap: () async { - final navigator = Navigator.of(context); - if (settings.currentLoggedInUser == userId) return; - Navigator.pushReplacement( - context, - MaterialPageRoute( - builder: (context) => SwitchingAccountsPage(), - ), - ); - await context.read().switchAccount(userId); - navigator.popUntil((route) => route.isFirst); - }, - trailing: TextButton( - child: Text( - "Remove", - style: TextStyle( - color: theme.colorScheme.error, - ), - ), - onPressed: () async { - final shouldPop = userId == settings.currentLoggedInUser; - await context.read().removeAccount(userId); - if (shouldPop) { - Navigator.pop(context); + trailing: PopupMenuButton( + icon: const Icon(Icons.more_vert), + itemBuilder: (context) { + return [ + if (settings.currentLoggedInUser != userId) + const PopupMenuItem( + child: ListTile( + title: Text("Switch"), //TODO: INTL + leading: Icon(Icons.switch_account_outlined), + ), + value: 0, + ), + const PopupMenuItem( + child: ListTile( + title: Text("Remove"), // TODO: INTL + leading: Icon( + Icons.remove_circle_outline, + color: Colors.red, + ), + ), + value: 1, + ), + ]; + }, + onSelected: (value) async { + if (value == 0) { + // Switch + final navigator = Navigator.of(context); + if (settings.currentLoggedInUser == userId) return; + Navigator.pushReplacement( + context, + MaterialPageRoute( + builder: (context) => const SwitchingAccountsPage(), + ), + ); + await context.read().switchAccount(userId); + navigator.popUntil((route) => route.isFirst); + } else if (value == 1) { + // Remove + final shouldPop = userId == settings.currentLoggedInUser; + await context.read().removeAccount(userId); + if (shouldPop) { + Navigator.pop(context); + } } }, ), ); } + + Future _onAddAccount(BuildContext context) { + return Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LoginPage( + titleString: "Add account", //TODO: INTL + onSubmit: (context, username, password, serverUrl, clientCertificate) async { + final userId = await context.read().addAccount( + credentials: LoginFormCredentials( + username: username, + password: password, + ), + clientCertificate: clientCertificate, + serverUrl: serverUrl, + //TODO: Ask user whether to enable biometric authentication + enableBiometricAuthentication: false, + ); + final shoudSwitch = await showDialog( + context: context, + builder: (context) => + SwitchAccountDialog(username: username, serverUrl: serverUrl), + ) ?? + false; + if (shoudSwitch) { + context.read().switchAccount(userId); + } + }, + submitText: "Add account", //TODO: INTL + ), + ), + ); + } } diff --git a/lib/features/settings/view/pages/application_settings_page.dart b/lib/features/settings/view/pages/application_settings_page.dart index b3f4b8a..7399e9c 100644 --- a/lib/features/settings/view/pages/application_settings_page.dart +++ b/lib/features/settings/view/pages/application_settings_page.dart @@ -12,6 +12,16 @@ class ApplicationSettingsPage extends StatelessWidget { return Scaffold( appBar: AppBar( title: Text(S.of(context)!.applicationSettings), + actions: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Tooltip( + triggerMode: TooltipTriggerMode.tap, + message: "These settings apply to all accounts", //TODO: INTL + child: Icon(Icons.info_outline), + ), + ), + ], ), body: ListView( children: const [ diff --git a/lib/features/settings/view/pages/security_settings_page.dart b/lib/features/settings/view/pages/security_settings_page.dart index 5ff2a1e..c812e54 100644 --- a/lib/features/settings/view/pages/security_settings_page.dart +++ b/lib/features/settings/view/pages/security_settings_page.dart @@ -8,7 +8,19 @@ class SecuritySettingsPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(title: Text(S.of(context)!.security)), + appBar: AppBar( + title: Text(S.of(context)!.security), + actions: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Tooltip( + triggerMode: TooltipTriggerMode.tap, + message: "These settings apply to the current user only", //TODO: INTL + child: Icon(Icons.info_outline), + ), + ), + ], + ), body: ListView( children: const [ BiometricAuthenticationSetting(), diff --git a/lib/features/settings/view/widgets/user_avatar.dart b/lib/features/settings/view/widgets/user_avatar.dart new file mode 100644 index 0000000..fc8ac8c --- /dev/null +++ b/lib/features/settings/view/widgets/user_avatar.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:paperless_mobile/features/login/model/user_account.dart'; + +class UserAvatar extends StatelessWidget { + final String userId; + final UserAccount account; + const UserAvatar({ + super.key, + required this.userId, + required this.account, + }); + + @override + Widget build(BuildContext context) { + final backgroundColor = Colors.primaries[userId.hashCode % Colors.primaries.length]; + final foregroundColor = backgroundColor.computeLuminance() > 0.5 ? Colors.black : Colors.white; + return CircleAvatar( + child: Text((account.fullName ?? account.username) + .split(" ") + .take(2) + .map((e) => e.substring(0, 1)) + .map((e) => e.toUpperCase()) + .join("")), + backgroundColor: backgroundColor, + foregroundColor: foregroundColor, + ); + } +} diff --git a/lib/main.dart b/lib/main.dart index cdc4385..8716f16 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -324,6 +324,7 @@ class _AuthenticationWrapperState extends State { return const VerifyIdentityPage(); } return LoginPage( + titleString: S.of(context)!.connectToPaperless, submitText: S.of(context)!.signIn, onSubmit: _onLogin, );