diff --git a/lib/core/config/hive/hive_config.dart b/lib/core/config/hive/hive_config.dart index 8952bc9..6557f20 100644 --- a/lib/core/config/hive/hive_config.dart +++ b/lib/core/config/hive/hive_config.dart @@ -18,7 +18,6 @@ class HiveBoxes { static const localUserCredentials = 'localUserCredentials'; static const localUserAccount = 'localUserAccount'; static const localUserAppState = 'localUserAppState'; - static const localUserSettings = 'localUserSettings'; static const hosts = 'hosts'; } diff --git a/lib/core/config/hive/hive_extensions.dart b/lib/core/config/hive/hive_extensions.dart index 83b8823..c519dcd 100644 --- a/lib/core/config/hive/hive_extensions.dart +++ b/lib/core/config/hive/hive_extensions.dart @@ -8,7 +8,6 @@ import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart'; -import 'package:paperless_mobile/core/database/tables/local_user_settings.dart'; /// /// Opens an encrypted box, calls [callback] with the now opened box, awaits @@ -53,8 +52,6 @@ extension HiveBoxAccessors on HiveInterface { box(HiveBoxes.localUserAccount); Box get localUserAppStateBox => box(HiveBoxes.localUserAppState); - Box get localUserSettingsBox => - box(HiveBoxes.localUserSettings); Box get globalSettingsBox => box(HiveBoxes.globalSettings); } diff --git a/lib/features/app_drawer/view/app_drawer.dart b/lib/features/app_drawer/view/app_drawer.dart index 14d0a64..61b41bb 100644 --- a/lib/features/app_drawer/view/app_drawer.dart +++ b/lib/features/app_drawer/view/app_drawer.dart @@ -108,7 +108,7 @@ class AppDrawer extends StatelessWidget { final child = ListTile( dense: true, leading: const Icon(Icons.drive_folder_upload_outlined), - title: const Text("Upload Queue"), + title: const Text("Pending Files"), onTap: () { UploadQueueRoute().push(context); }, diff --git a/lib/features/document_details/cubit/document_details_cubit.dart b/lib/features/document_details/cubit/document_details_cubit.dart index 94291ea..cf09e95 100644 --- a/lib/features/document_details/cubit/document_details_cubit.dart +++ b/lib/features/document_details/cubit/document_details_cubit.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:open_filex/open_filex.dart'; import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/core/service/file_description.dart'; @@ -120,6 +121,7 @@ class DocumentDetailsCubit extends Cubit { Future downloadDocument({ bool downloadOriginal = false, required String locale, + required String userId, }) async { if (state.metaData == null) { await loadMetaData(); @@ -141,6 +143,7 @@ class DocumentDetailsCubit extends Cubit { filePath: filePath, finished: true, locale: locale, + userId: userId, ); } @@ -150,6 +153,7 @@ class DocumentDetailsCubit extends Cubit { filePath: filePath, finished: false, locale: locale, + userId: userId, ); await _api.downloadToFile( @@ -163,6 +167,7 @@ class DocumentDetailsCubit extends Cubit { filePath: filePath, finished: true, locale: locale, + userId: userId, ); debugPrint("Downloaded file to $filePath"); } diff --git a/lib/features/document_details/view/widgets/document_download_button.dart b/lib/features/document_details/view/widgets/document_download_button.dart index 9d34831..5f66073 100644 --- a/lib/features/document_details/view/widgets/document_download_button.dart +++ b/lib/features/document_details/view/widgets/document_download_button.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart'; +import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/document_details/cubit/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/view/dialogs/select_file_type_dialog.dart'; @@ -90,9 +91,11 @@ class _DocumentDownloadButtonState extends State { } setState(() => _isDownloadPending = true); + final userId = context.read().id; await context.read().downloadDocument( downloadOriginal: original, locale: globalSettings.preferredLocaleSubtag, + userId: userId, ); // showSnackBar(context, S.of(context)!.documentSuccessfullyDownloaded); } on PaperlessApiException catch (error, stackTrace) { diff --git a/lib/features/document_edit/view/document_edit_page.dart b/lib/features/document_edit/view/document_edit_page.dart index defa419..5743805 100644 --- a/lib/features/document_edit/view/document_edit_page.dart +++ b/lib/features/document_edit/view/document_edit_page.dart @@ -305,13 +305,13 @@ class _DocumentEditPageState extends State { var mergedDocument = document.copyWith( title: values[fkTitle], created: values[fkCreatedDate], - documentType: () => (values[fkDocumentType] as IdQueryParameter) - .whenOrNull(fromId: (id) => id), - correspondent: () => (values[fkCorrespondent] as IdQueryParameter) - .whenOrNull(fromId: (id) => id), - storagePath: () => (values[fkStoragePath] as IdQueryParameter) - .whenOrNull(fromId: (id) => id), - tags: (values[fkTags] as IdsTagsQuery).include, + documentType: () => (values[fkDocumentType] as IdQueryParameter?) + ?.whenOrNull(fromId: (id) => id), + correspondent: () => (values[fkCorrespondent] as IdQueryParameter?) + ?.whenOrNull(fromId: (id) => id), + storagePath: () => (values[fkStoragePath] as IdQueryParameter?) + ?.whenOrNull(fromId: (id) => id), + tags: (values[fkTags] as IdsTagsQuery?)?.include, content: values[fkContent], ); setState(() { diff --git a/lib/features/document_scan/view/scanner_page.dart b/lib/features/document_scan/view/scanner_page.dart index 0a5d788..bda703b 100644 --- a/lib/features/document_scan/view/scanner_page.dart +++ b/lib/features/document_scan/view/scanner_page.dart @@ -22,7 +22,7 @@ import 'package:paperless_mobile/features/document_scan/view/widgets/scanned_ima import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart'; import 'package:paperless_mobile/features/documents/view/pages/document_view.dart'; -import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; +import 'package:paperless_mobile/features/tasks/model/pending_tasks_notifier.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/helpers/connectivity_aware_action_wrapper.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart'; diff --git a/lib/features/document_upload/view/document_upload_preparation_page.dart b/lib/features/document_upload/view/document_upload_preparation_page.dart index 4412b0c..cb9ee11 100644 --- a/lib/features/document_upload/view/document_upload_preparation_page.dart +++ b/lib/features/document_upload/view/document_upload_preparation_page.dart @@ -60,7 +60,6 @@ class _DocumentUploadPreparationPageState static final fileNameDateFormat = DateFormat("yyyy_MM_ddTHH_mm_ss"); final GlobalKey _formKey = GlobalKey(); - Color? _titleColor; Map _errors = {}; bool _isUploadLoading = false; late bool _syncTitleAndFilename; @@ -71,10 +70,6 @@ class _DocumentUploadPreparationPageState void initState() { super.initState(); _syncTitleAndFilename = widget.filename == null && widget.title == null; - _computeAverageColor().then((value) { - _titleColor = - value.computeLuminance() > 0.5 ? Colors.black : Colors.white; - }); initializeDateFormatting(); } @@ -102,9 +97,7 @@ class _DocumentUploadPreparationPageState handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context), sliver: SliverAppBar( - leading: BackButton( - color: _titleColor, - ), + leading: BackButton(), pinned: true, expandedHeight: 150, flexibleSpace: FlexibleSpaceBar( @@ -112,20 +105,17 @@ class _DocumentUploadPreparationPageState future: widget.fileBytes, builder: (context, snapshot) { if (!snapshot.hasData) { - return const SizedBox.shrink(); + return SizedBox.shrink(); } return FileThumbnail( bytes: snapshot.data!, fit: BoxFit.fitWidth, + width: MediaQuery.sizeOf(context).width, ); }, ), - title: Text( - S.of(context)!.prepareDocument, - style: TextStyle( - color: _titleColor, - ), - ), + title: Text(S.of(context)!.prepareDocument), + collapseMode: CollapseMode.pin, ), bottom: _isUploadLoading ? PreferredSize( @@ -416,32 +406,32 @@ class _DocumentUploadPreparationPageState return source.replaceAll(RegExp(r"[\W_]"), "_").toLowerCase(); } - Future _computeAverageColor() async { - final bitmap = img.decodeImage(await widget.fileBytes); - if (bitmap == null) { - return Colors.black; - } - int redBucket = 0; - int greenBucket = 0; - int blueBucket = 0; - int pixelCount = 0; + // Future _computeAverageColor() async { + // final bitmap = img.decodeImage(await widget.fileBytes); + // if (bitmap == null) { + // return Colors.black; + // } + // int redBucket = 0; + // int greenBucket = 0; + // int blueBucket = 0; + // int pixelCount = 0; - for (int y = 0; y < bitmap.height; y++) { - for (int x = 0; x < bitmap.width; x++) { - final c = bitmap.getPixel(x, y); + // for (int y = 0; y < bitmap.height; y++) { + // for (int x = 0; x < bitmap.width; x++) { + // final c = bitmap.getPixel(x, y); - pixelCount++; - redBucket += c.r.toInt(); - greenBucket += c.g.toInt(); - blueBucket += c.b.toInt(); - } - } + // pixelCount++; + // redBucket += c.r.toInt(); + // greenBucket += c.g.toInt(); + // blueBucket += c.b.toInt(); + // } + // } - return Color.fromRGBO( - redBucket ~/ pixelCount, - greenBucket ~/ pixelCount, - blueBucket ~/ pixelCount, - 1, - ); - } + // return Color.fromRGBO( + // redBucket ~/ pixelCount, + // greenBucket ~/ pixelCount, + // blueBucket ~/ pixelCount, + // 1, + // ); + // } } diff --git a/lib/features/documents/view/pages/documents_page.dart b/lib/features/documents/view/pages/documents_page.dart index 4aca245..d6a1e3a 100644 --- a/lib/features/documents/view/pages/documents_page.dart +++ b/lib/features/documents/view/pages/documents_page.dart @@ -21,7 +21,7 @@ import 'package:paperless_mobile/features/documents/view/widgets/selection/view_ import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart'; -import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; +import 'package:paperless_mobile/features/tasks/model/pending_tasks_notifier.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/routes/typed/branches/documents_route.dart'; @@ -59,7 +59,7 @@ class _DocumentsPageState extends State { @override void initState() { super.initState(); - context.read().addListener(_onTasksChanged); + // context.read().addListener(_onTasksChanged); WidgetsBinding.instance.addPostFrameCallback((_) { _nestedScrollViewKey.currentState!.innerController .addListener(_scrollExtentChangedListener); @@ -126,8 +126,7 @@ class _DocumentsPageState extends State { void dispose() { _nestedScrollViewKey.currentState?.innerController .removeListener(_scrollExtentChangedListener); - context.read().removeListener(_onTasksChanged); - + // context.read().removeListener(_onTasksChanged); super.dispose(); } diff --git a/lib/features/home/view/home_shell_widget.dart b/lib/features/home/view/home_shell_widget.dart index 07709d4..4a01d1e 100644 --- a/lib/features/home/view/home_shell_widget.dart +++ b/lib/features/home/view/home_shell_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:paperless_api/paperless_api.dart'; @@ -16,9 +17,14 @@ import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart'; import 'package:paperless_mobile/features/home/view/model/api_version.dart'; import 'package:paperless_mobile/features/inbox/cubit/inbox_cubit.dart'; import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; +import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart'; import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart'; import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; -import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; +import 'package:paperless_mobile/features/tasks/model/pending_tasks_notifier.dart'; +import 'package:paperless_mobile/routes/typed/branches/landing_route.dart'; +import 'package:paperless_mobile/routes/typed/top_level/login_route.dart'; +import 'package:paperless_mobile/routes/typed/top_level/switching_accounts_route.dart'; +import 'package:paperless_mobile/routes/typed/top_level/verify_identity_route.dart'; import 'package:provider/provider.dart'; class HomeShellWidget extends StatelessWidget { diff --git a/lib/features/login/cubit/authentication_cubit.dart b/lib/features/login/cubit/authentication_cubit.dart index 177ca7d..9d078b1 100644 --- a/lib/features/login/cubit/authentication_cubit.dart +++ b/lib/features/login/cubit/authentication_cubit.dart @@ -20,6 +20,7 @@ import 'package:paperless_mobile/core/service/connectivity_status_service.dart'; import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:paperless_mobile/features/login/model/client_certificate.dart'; 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/services/authentication_service.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; @@ -44,34 +45,35 @@ class AuthenticationCubit extends Cubit { ClientCertificate? clientCertificate, }) async { assert(credentials.username != null && credentials.password != null); + emit(const CheckingLoginState()); final localUserId = "${credentials.username}@$serverUrl"; _debugPrintMessage( "login", "Trying to login $localUserId...", ); - await _addUser( - localUserId, - serverUrl, - credentials, - clientCertificate, - _sessionManager, - ); + try { + await _addUser( + localUserId, + serverUrl, + credentials, + clientCertificate, + _sessionManager, + ); - // Mark logged in user as currently active user. - final globalSettings = - Hive.box(HiveBoxes.globalSettings).getValue()!; - globalSettings.loggedInUserId = localUserId; - await globalSettings.save(); + // Mark logged in user as currently active user. + final globalSettings = + Hive.box(HiveBoxes.globalSettings).getValue()!; + globalSettings.loggedInUserId = localUserId; + await globalSettings.save(); - emit( - AuthenticatedState( - localUserId: localUserId, - ), - ); - _debugPrintMessage( - "login", - "User successfully logged in.", - ); + emit(AuthenticatedState(localUserId: localUserId)); + _debugPrintMessage( + "login", + "User successfully logged in.", + ); + } catch (error) { + emit(const UnauthenticatedState()); + } } /// Switches to another account if it exists. @@ -156,10 +158,8 @@ class AuthenticationCubit extends Cubit { } Future removeAccount(String userId) async { - final userAccountBox = - Hive.box(HiveBoxes.localUserAccount); - final userAppStateBox = - Hive.box(HiveBoxes.localUserAppState); + final userAccountBox = Hive.localUserAccountBox; + final userAppStateBox = Hive.localUserAppStateBox; await FileService.clearUserData(userId: userId); await userAccountBox.delete(userId); await userAppStateBox.delete(userId); @@ -263,9 +263,13 @@ class AuthenticationCubit extends Cubit { "restoreSessionState", "Current session state successfully updated.", ); - final hasInternetConnection = - await _connectivityService.isConnectedToInternet(); - if (hasInternetConnection) { + final isPaperlessServerReachable = + await _connectivityService.isPaperlessServerReachable( + localUserAccount.serverUrl, + authentication.clientCertificate, + ) == + ReachabilityStatus.reachable; + if (isPaperlessServerReachable) { _debugPrintMessage( "restoreSessionMState", "Updating server user...", @@ -283,7 +287,7 @@ class AuthenticationCubit extends Cubit { } else { _debugPrintMessage( "restoreSessionMState", - "Skipping update of server user (no internet connection).", + "Skipping update of server user (server could not be reached).", ); } @@ -295,14 +299,18 @@ class AuthenticationCubit extends Cubit { ); } - Future logout() async { + Future logout([bool removeAccount = false]) async { + emit(const LogginOutState()); _debugPrintMessage( "logout", "Trying to log out current user...", ); await _resetExternalState(); - final globalSettings = - Hive.box(HiveBoxes.globalSettings).getValue()!; + final globalSettings = Hive.globalSettingsBox.getValue()!; + final userId = globalSettings.loggedInUserId!; + if (removeAccount) { + this.removeAccount(userId); + } globalSettings.loggedInUserId = null; await globalSettings.save(); @@ -459,19 +467,32 @@ class AuthenticationCubit extends Cubit { return serverUser.id; } - Future _getApiVersion(Dio dio) async { + Future _getApiVersion( + Dio dio, { + Duration? timeout, + int defaultValue = 2, + }) async { _debugPrintMessage( "_getApiVersion", "Trying to fetch API version...", ); - final response = await dio.get("/api/"); - final apiVersion = - int.parse(response.headers.value('x-api-version') ?? "3"); - _debugPrintMessage( - "_getApiVersion", - "API version ($apiVersion) successfully retrieved.", - ); - return apiVersion; + try { + final response = await dio.get( + "/api/", + options: Options( + sendTimeout: timeout, + ), + ); + final apiVersion = + int.parse(response.headers.value('x-api-version') ?? "3"); + _debugPrintMessage( + "_getApiVersion", + "API version ($apiVersion) successfully retrieved.", + ); + return apiVersion; + } on DioException catch (e) { + return defaultValue; + } } /// Fetches possibly updated (permissions, name, updated server version and thus new user model, ...) remote user data. diff --git a/lib/features/login/cubit/authentication_state.dart b/lib/features/login/cubit/authentication_state.dart index db4455c..bc4d29c 100644 --- a/lib/features/login/cubit/authentication_state.dart +++ b/lib/features/login/cubit/authentication_state.dart @@ -15,6 +15,14 @@ class RequiresLocalAuthenticationState extends AuthenticationState { const RequiresLocalAuthenticationState(); } +class CheckingLoginState extends AuthenticationState { + const CheckingLoginState(); +} + +class LogginOutState extends AuthenticationState { + const LogginOutState(); +} + class AuthenticatedState extends AuthenticationState { final String localUserId; diff --git a/lib/features/login/view/add_account_page.dart b/lib/features/login/view/add_account_page.dart index 7e1bdc6..5c48e44 100644 --- a/lib/features/login/view/add_account_page.dart +++ b/lib/features/login/view/add_account_page.dart @@ -57,72 +57,76 @@ class _AddAccountPageState extends State { @override Widget build(BuildContext context) { - final localAccounts = - Hive.box(HiveBoxes.localUserAccount); - return Scaffold( - resizeToAvoidBottomInset: false, - body: FormBuilder( - key: _formKey, - child: PageView( - controller: _pageController, - scrollBehavior: NeverScrollableScrollBehavior(), - children: [ - if (widget.showLocalAccounts && localAccounts.isNotEmpty) - Scaffold( - appBar: AppBar( - title: Text(S.of(context)!.logInToExistingAccount), - ), - bottomNavigationBar: BottomAppBar( - child: Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - FilledButton( - child: Text(S.of(context)!.goToLogin), - onPressed: () { - _pageController.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - }, + return ValueListenableBuilder( + valueListenable: + Hive.box(HiveBoxes.localUserAccount).listenable(), + builder: (context, localAccounts, child) { + return Scaffold( + resizeToAvoidBottomInset: false, + body: FormBuilder( + key: _formKey, + child: PageView( + controller: _pageController, + scrollBehavior: NeverScrollableScrollBehavior(), + children: [ + if (widget.showLocalAccounts && localAccounts.isNotEmpty) + Scaffold( + appBar: AppBar( + title: Text(S.of(context)!.logInToExistingAccount), + ), + bottomNavigationBar: BottomAppBar( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + FilledButton( + child: Text(S.of(context)!.goToLogin), + onPressed: () { + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }, + ), + ], ), - ], + ), + body: ListView.builder( + itemBuilder: (context, index) { + final account = localAccounts.values.elementAt(index); + return Card( + child: UserAccountListTile( + account: account, + onTap: () { + context + .read() + .switchAccount(account.id); + }, + ), + ); + }, + itemCount: localAccounts.length, + ), ), - ), - body: ListView.builder( - itemBuilder: (context, index) { - final account = localAccounts.values.elementAt(index); - return Card( - child: UserAccountListTile( - account: account, - onTap: () { - context - .read() - .switchAccount(account.id); - }, - ), + ServerConnectionPage( + titleText: widget.titleString, + formBuilderKey: _formKey, + onContinue: () { + _pageController.nextPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, ); }, - itemCount: localAccounts.length, ), - ), - ServerConnectionPage( - titleText: widget.titleString, - formBuilderKey: _formKey, - onContinue: () { - _pageController.nextPage( - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOut, - ); - }, + ServerLoginPage( + formBuilderKey: _formKey, + submitText: widget.submitText, + onSubmit: _login, + ), + ], ), - ServerLoginPage( - formBuilderKey: _formKey, - submitText: widget.submitText, - onSubmit: _login, - ), - ], - ), - ), + ), + ); + }, ); } diff --git a/lib/features/login/view/login_page.dart b/lib/features/login/view/login_page.dart index afae0fc..44d2e36 100644 --- a/lib/features/login/view/login_page.dart +++ b/lib/features/login/view/login_page.dart @@ -5,6 +5,7 @@ import 'package:hive_flutter/adapters.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/database/tables/global_settings.dart'; +import 'package:paperless_mobile/core/model/info_message_exception.dart'; import 'package:paperless_mobile/features/app_intro/application_intro_slideshow.dart'; import 'package:paperless_mobile/features/login/cubit/authentication_cubit.dart'; import 'package:paperless_mobile/features/login/model/client_certificate.dart'; @@ -71,6 +72,8 @@ class LoginPage extends StatelessWidget { stackTrace, ); //TODO: Check if we can show error message directly on field here. } + } on InfoMessageException catch (error) { + showInfoMessage(context, error); } catch (unknownError, stackTrace) { showGenericError(context, unknownError.toString(), stackTrace); } diff --git a/lib/features/notifications/services/local_notification_service.dart b/lib/features/notifications/services/local_notification_service.dart index f4e7f85..01c8246 100644 --- a/lib/features/notifications/services/local_notification_service.dart +++ b/lib/features/notifications/services/local_notification_service.dart @@ -18,6 +18,8 @@ class LocalNotificationService { LocalNotificationService(); + final Map> _pendingNotifications = {}; + Future initialize() async { const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('paperless_logo_green'); @@ -51,6 +53,7 @@ class LocalNotificationService { required String filePath, required bool finished, required String locale, + required String userId, }) async { final tr = await S.delegate.load(Locale(locale)); @@ -88,6 +91,15 @@ class LocalNotificationService { ).toJson(), ), ); //TODO: INTL + _addNotification(userId, id); + } + + void _addNotification(String userId, int notificationId) { + _pendingNotifications.update( + userId, + (notifications) => [...notifications, notificationId], + ifAbsent: () => [notificationId], + ); } Future notifyFileSaved({ @@ -119,24 +131,20 @@ class LocalNotificationService { ), iOS: DarwinNotificationDetails( attachments: [ - DarwinNotificationAttachment( - filePath, - ), + DarwinNotificationAttachment(filePath), ], ), ), payload: jsonEncode( - OpenDownloadedDocumentPayload( - filePath: filePath, - ).toJson(), + OpenDownloadedDocumentPayload(filePath: filePath).toJson(), ), ); } //TODO: INTL - Future notifyTaskChanged(Task task) { + Future notifyTaskChanged(Task task, {required String userId}) async { log("[LocalNotificationService] notifyTaskChanged: ${task.toString()}"); - int id = task.id; + int id = task.id + 1000; final status = task.status; late String title; late String? body; @@ -171,7 +179,7 @@ class LocalNotificationService { default: break; } - return _plugin.show( + await _plugin.show( id, title, body, @@ -204,6 +212,13 @@ class LocalNotificationService { ), payload: jsonEncode(payload), ); + _addNotification(userId, id); + } + + Future cancelUserNotifications(String userId) async { + await Future.wait([ + for (var id in _pendingNotifications[userId] ?? []) _plugin.cancel(id), + ]); } void onDidReceiveLocalNotification( @@ -272,6 +287,7 @@ class LocalNotificationService { } } +@protected void onDidReceiveBackgroundNotificationResponse(NotificationResponse response) { //TODO: When periodic background inbox check is implemented, notification tap is handled here debugPrint(response.toString()); diff --git a/lib/features/settings/view/manage_accounts_page.dart b/lib/features/settings/view/manage_accounts_page.dart index 1d41262..5cce035 100644 --- a/lib/features/settings/view/manage_accounts_page.dart +++ b/lib/features/settings/view/manage_accounts_page.dart @@ -70,12 +70,9 @@ class ManageAccountsPage extends StatelessWidget { ], onSelected: (value) async { if (value == 0) { - final currentUser = globalSettings.loggedInUserId!; - await context.read().logout(); - Navigator.of(context).pop(); await context .read() - .removeAccount(currentUser); + .logout(true); } }, ), diff --git a/lib/features/sharing/cubit/receive_share_cubit.dart b/lib/features/sharing/cubit/receive_share_cubit.dart index a6322e6..19317d4 100644 --- a/lib/features/sharing/cubit/receive_share_cubit.dart +++ b/lib/features/sharing/cubit/receive_share_cubit.dart @@ -1,20 +1,24 @@ +import 'dart:async'; import 'dart:io'; -import 'package:bloc/bloc.dart'; import 'package:flutter/foundation.dart'; import 'package:paperless_mobile/core/service/file_service.dart'; import 'package:path/path.dart' as p; -import 'package:provider/provider.dart'; part 'receive_share_state.dart'; class ConsumptionChangeNotifier extends ChangeNotifier { List pendingFiles = []; - ConsumptionChangeNotifier(); + final Completer _restored = Completer(); + + Future get isInitialized => _restored.future; Future loadFromConsumptionDirectory({required String userId}) async { pendingFiles = await _getCurrentFiles(userId); + if (!_restored.isCompleted) { + _restored.complete(); + } notifyListeners(); } diff --git a/lib/features/sharing/view/consumption_queue_view.dart b/lib/features/sharing/view/consumption_queue_view.dart index c33527d..e2d2617 100644 --- a/lib/features/sharing/view/consumption_queue_view.dart +++ b/lib/features/sharing/view/consumption_queue_view.dart @@ -1,8 +1,4 @@ -import 'dart:io'; - import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:intl/intl.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/extensions/flutter_extensions.dart'; import 'package:paperless_mobile/features/sharing/cubit/receive_share_cubit.dart'; @@ -20,13 +16,13 @@ class ConsumptionQueueView extends StatelessWidget { final currentUser = context.watch(); return Scaffold( appBar: AppBar( - title: Text("Upload Queue"), //TODO: INTL + title: Text("Pending Files"), //TODO: INTL ), body: Consumer( builder: (context, value, child) { if (value.pendingFiles.isEmpty) { return Center( - child: Text("No pending files."), + child: Text("There are no pending files."), //TODO: INTL ); } return ListView.builder( @@ -34,7 +30,37 @@ class ConsumptionQueueView extends StatelessWidget { final file = value.pendingFiles.elementAt(index); final filename = p.basename(file.path); return ListTile( - title: Text(filename), + title: Text( + filename, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + subtitle: Row( + children: [ + ActionChip( + label: Text(S.of(context)!.upload), + avatar: const Icon(Icons.file_upload_outlined), + onPressed: () { + consumeLocalFile( + context, + file: file, + userId: currentUser.id, + ); + }, + ), + const SizedBox(width: 8), + ActionChip( + label: Text(S.of(context)!.discard), + avatar: const Icon(Icons.delete), + onPressed: () { + context.read().discardFile( + file, + userId: currentUser.id, + ); + }, + ), + ], + ), leading: Padding( padding: const EdgeInsets.all(4), child: ClipRRect( @@ -46,60 +72,7 @@ class ConsumptionQueueView extends StatelessWidget { ), ), ), - trailing: IconButton( - icon: Icon(Icons.delete), - onPressed: () { - context - .read() - .discardFile(file, userId: currentUser.id); - }, - ), ); - return Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: Column( - children: [ - Text(filename, maxLines: 1), - SizedBox( - height: 56, - child: ListView( - scrollDirection: Axis.horizontal, - children: [ - ActionChip( - label: Text(S.of(context)!.upload), - avatar: Icon(Icons.file_upload_outlined), - onPressed: () { - consumeLocalFile( - context, - file: file, - userId: currentUser.id, - ); - }, - ), - SizedBox(width: 8), - ActionChip( - label: Text(S.of(context)!.discard), - avatar: Icon(Icons.delete), - onPressed: () { - context - .read() - .discardFile( - file, - userId: currentUser.id, - ); - }, - ), - ], - ), - ), - ], - ).padded(), - ), - ], - ).padded(); }, itemCount: value.pendingFiles.length, ); diff --git a/lib/features/sharing/view/dialog/discard_shared_file_dialog.dart b/lib/features/sharing/view/dialog/discard_shared_file_dialog.dart index 311172e..ae81620 100644 --- a/lib/features/sharing/view/dialog/discard_shared_file_dialog.dart +++ b/lib/features/sharing/view/dialog/discard_shared_file_dialog.dart @@ -6,7 +6,7 @@ import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button. import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart'; import 'package:paperless_mobile/core/widgets/future_or_builder.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; -import 'package:transparent_image/transparent_image.dart'; +import 'package:paperless_mobile/features/sharing/view/widgets/file_thumbnail.dart'; class DiscardSharedFileDialog extends StatelessWidget { final FutureOr bytes; @@ -24,13 +24,13 @@ class DiscardSharedFileDialog extends StatelessWidget { if (!snapshot.hasData) { return const CircularProgressIndicator(); } - return LimitedBox( - maxHeight: 200, - maxWidth: 200, - child: FadeInImage( - fit: BoxFit.contain, - placeholder: MemoryImage(kTransparentImage), - image: MemoryImage(snapshot.data!), + return ClipRRect( + borderRadius: BorderRadius.circular(12), + child: FileThumbnail( + bytes: snapshot.data!, + width: 150, + height: 100, + fit: BoxFit.cover, ), ); }, diff --git a/lib/features/sharing/view/dialog/pending_files_info_dialog.dart b/lib/features/sharing/view/dialog/pending_files_info_dialog.dart new file mode 100644 index 0000000..d4d087a --- /dev/null +++ b/lib/features/sharing/view/dialog/pending_files_info_dialog.dart @@ -0,0 +1,29 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; +import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_cancel_button.dart'; +import 'package:paperless_mobile/core/widgets/dialog_utils/dialog_confirm_button.dart'; +import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; + +class PendingFilesInfoDialog extends StatelessWidget { + final List pendingFiles; + const PendingFilesInfoDialog({super.key, required this.pendingFiles}); + + @override + Widget build(BuildContext context) { + final fileCount = pendingFiles.length; + return AlertDialog( + title: Text("Pending Files"), + content: Text( + "$fileCount files are waiting to be uploaded. Do you want to upload them now?", + ), + actions: [ + DialogCancelButton(), + DialogConfirmButton( + label: S.of(context)!.upload, + ), + ], + ); + } +} diff --git a/lib/features/sharing/view/widgets/file_thumbnail.dart b/lib/features/sharing/view/widgets/file_thumbnail.dart index f4e8c91..8abfa20 100644 --- a/lib/features/sharing/view/widgets/file_thumbnail.dart +++ b/lib/features/sharing/view/widgets/file_thumbnail.dart @@ -28,13 +28,15 @@ class FileThumbnail extends StatefulWidget { class _FileThumbnailState extends State { late String? mimeType; - + late final Future _fileBytes; @override void initState() { super.initState(); mimeType = widget.file != null ? mime.lookupMimeType(widget.file!.path) : mime.lookupMimeType('', headerBytes: widget.bytes); + _fileBytes = widget.file?.readAsBytes().then(_convertPdfToPng) ?? + _convertPdfToPng(widget.bytes!); } @override @@ -45,8 +47,7 @@ class _FileThumbnailState extends State { height: widget.height, child: Center( child: FutureBuilder( - future: widget.file?.readAsBytes().then(_convertPdfToPng) ?? - _convertPdfToPng(widget.bytes!), + future: _fileBytes, builder: (context, snapshot) { if (!snapshot.hasData) { return const SizedBox.shrink(); diff --git a/lib/features/sharing/view/widgets/upload_queue_shell.dart b/lib/features/sharing/view/widgets/upload_queue_shell.dart index 05c384f..59d8f14 100644 --- a/lib/features/sharing/view/widgets/upload_queue_shell.dart +++ b/lib/features/sharing/view/widgets/upload_queue_shell.dart @@ -10,15 +10,16 @@ import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/config/hive/hive_config.dart'; import 'package:paperless_mobile/core/config/hive/hive_extensions.dart'; import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; +import 'package:paperless_mobile/core/service/connectivity_status_service.dart'; import 'package:paperless_mobile/features/document_upload/view/document_upload_preparation_page.dart'; import 'package:paperless_mobile/features/notifications/services/local_notification_service.dart'; import 'package:paperless_mobile/features/sharing/cubit/receive_share_cubit.dart'; import 'package:paperless_mobile/features/sharing/view/dialog/discard_shared_file_dialog.dart'; -import 'package:paperless_mobile/features/tasks/cubit/task_status_cubit.dart'; +import 'package:paperless_mobile/features/sharing/view/dialog/pending_files_info_dialog.dart'; +import 'package:paperless_mobile/features/tasks/model/pending_tasks_notifier.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/routes/typed/branches/scanner_route.dart'; -import 'package:paperless_mobile/routes/typed/branches/upload_queue_route.dart'; import 'package:path/path.dart' as p; import 'package:receive_sharing_intent/receive_sharing_intent.dart'; @@ -39,57 +40,40 @@ class _UploadQueueShellState extends State { ReceiveSharingIntent.getInitialMedia().then(_onReceiveSharedFiles); _subscription = ReceiveSharingIntent.getMediaStream().listen(_onReceiveSharedFiles); + context.read().addListener(_onTasksChanged); - // WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - // context.read().loadFromConsumptionDirectory( - // userId: context.read().id, - // ); - // final state = context.read().state; - // print("Current state is " + state.toString()); - // final files = state.files; - // if (files.isNotEmpty) { - // showSnackBar( + // WidgetsBinding.instance.addPostFrameCallback((_) async { + // final notifier = context.read(); + // await notifier.isInitialized; + // final pendingFiles = notifier.pendingFiles; + // if (pendingFiles.isEmpty) { + // return; + // } + + // final shouldProcess = await showDialog( + // context: context, + // builder: (context) => + // PendingFilesInfoDialog(pendingFiles: pendingFiles), + // ) ?? + // false; + // if (shouldProcess) { + // final userId = context.read().id; + // await consumeLocalFiles( // context, - // "You have ${files.length} shared files waiting to be uploaded.", - // action: SnackBarActionConfig( - // label: "Show me", - // onPressed: () { - // UploadQueueRoute().push(context); - // }, - // ), + // files: pendingFiles, + // userId: userId, // ); - // // showDialog( - // // context: context, - // // builder: (context) => AlertDialog( - // // title: Text("Pending files"), - // // content: Text( - // // "You have ${files.length} files waiting to be uploaded.", - // // ), - // // actions: [ - // // TextButton( - // // child: Text(S.of(context)!.gotIt), - // // onPressed: () { - // // Navigator.pop(context); - // // UploadQueueRoute().push(context); - // // }, - // // ), - // // ], - // // ), - // // ); // } // }); } - @override - void didChangeDependencies() { - super.didChangeDependencies(); - context.read().addListener(_onTasksChanged); - } - void _onTasksChanged() { final taskNotifier = context.read(); + final userId = context.read().id; for (var task in taskNotifier.value.values) { - context.read().notifyTaskChanged(task); + context + .read() + .notifyTaskChanged(task, userId: userId); } } @@ -103,23 +87,18 @@ class _UploadQueueShellState extends State { files: files, userId: userId, ); - final localFiles = notifier.pendingFiles; - for (int i = 0; i < localFiles.length; i++) { - final file = localFiles[i]; - await consumeLocalFile( - context, - file: file, - userId: userId, - exitAppAfterConsumed: i == localFiles.length - 1, - ); - } + consumeLocalFiles( + context, + files: files, + userId: userId, + exitAppAfterConsumed: true, + ); } } @override void dispose() { _subscription?.cancel(); - context.read().removeListener(_onTasksChanged); super.dispose(); } @@ -135,28 +114,43 @@ Future consumeLocalFile( required String userId, bool exitAppAfterConsumed = false, }) async { + final filename = p.basename(file.path); + final hasInternetConnection = + await context.read().isConnectedToInternet(); + if (!hasInternetConnection) { + showSnackBar( + context, + "Could not consume $filename", //TODO: INTL + details: S.of(context)!.youreOffline, + ); + return; + } final consumptionNotifier = context.read(); final taskNotifier = context.read(); - final ioFile = File(file.path); - // if (!await ioFile.exists()) { - // Fluttertoast.showToast( - // msg: S.of(context)!.couldNotAccessReceivedFile, - // toastLength: Toast.LENGTH_LONG, - // ); - // } - final bytes = ioFile.readAsBytes(); + final bytes = file.readAsBytes(); final shouldDirectlyUpload = Hive.globalSettingsBox.getValue()!.skipDocumentPreprarationOnUpload; if (shouldDirectlyUpload) { - final taskId = await context.read().create( - await bytes, - filename: p.basename(file.path), - title: p.basenameWithoutExtension(file.path), - ); - consumptionNotifier.discardFile(file, userId: userId); - if (taskId != null) { - taskNotifier.listenToTaskChanges(taskId); + try { + final taskId = await context.read().create( + await bytes, + filename: filename, + title: p.basenameWithoutExtension(file.path), + ); + consumptionNotifier.discardFile(file, userId: userId); + if (taskId != null) { + taskNotifier.listenToTaskChanges(taskId); + } + } catch (error) { + await Fluttertoast.showToast( + msg: S.of(context)!.couldNotUploadDocument, + ); + return; + } finally { + if (exitAppAfterConsumed) { + SystemNavigator.pop(); + } } } else { final result = await DocumentUploadRoute( @@ -193,3 +187,20 @@ Future consumeLocalFile( } } } + +Future consumeLocalFiles( + BuildContext context, { + required List files, + required String userId, + bool exitAppAfterConsumed = false, +}) async { + for (int i = 0; i < files.length; i++) { + final file = files[i]; + await consumeLocalFile( + context, + file: file, + userId: userId, + exitAppAfterConsumed: exitAppAfterConsumed && (i == files.length - 1), + ); + } +} diff --git a/lib/features/tasks/cubit/task_status_cubit.dart b/lib/features/tasks/cubit/task_status_cubit.dart deleted file mode 100644 index 25dbcd4..0000000 --- a/lib/features/tasks/cubit/task_status_cubit.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:paperless_api/paperless_api.dart'; - -class PendingTasksNotifier extends ValueNotifier> { - final PaperlessTasksApi _api; - PendingTasksNotifier(this._api) : super({}); - - void listenToTaskChanges(String taskId) { - _api.listenForTaskChanges(taskId).forEach((task) { - value = {...value, taskId: task}; - notifyListeners(); - }).whenComplete( - () { - value = value..remove(taskId); - notifyListeners(); - }, - ); - } - - Future acknowledgeTasks(Iterable taskIds) async { - final tasks = value.values.where((task) => taskIds.contains(task.taskId)); - await Future.wait([for (var task in tasks) _api.acknowledgeTask(task)]); - value = value..removeWhere((key, value) => taskIds.contains(key)); - notifyListeners(); - } -} diff --git a/lib/features/tasks/model/pending_tasks_notifier.dart b/lib/features/tasks/model/pending_tasks_notifier.dart new file mode 100644 index 0000000..b1e1f03 --- /dev/null +++ b/lib/features/tasks/model/pending_tasks_notifier.dart @@ -0,0 +1,68 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:paperless_api/paperless_api.dart'; + +class PendingTasksNotifier extends ValueNotifier> { + final PaperlessTasksApi _api; + + final Map _subscriptions = {}; + + PendingTasksNotifier(this._api) : super({}); + + @override + void dispose() { + stopListeningToTaskChanges(); + super.dispose(); + } + + void listenToTaskChanges(String taskId) { + final sub = _api.listenForTaskChanges(taskId).listen( + (task) { + if (value.containsKey(taskId)) { + final oldTask = value[taskId]!; + if (oldTask.status != task.status) { + // Only notify of changes if task status has changed... + value = {...value, taskId: task}; + notifyListeners(); + } + } else { + value = {...value, taskId: task}; + notifyListeners(); + } + }, + ); + sub + ..onDone(() { + sub.cancel(); + value = value..remove(taskId); + notifyListeners(); + }) + ..onError((_) { + sub.cancel(); + value = value..remove(taskId); + notifyListeners(); + }); + + _subscriptions.putIfAbsent(taskId, () => sub); + } + + void stopListeningToTaskChanges([String? taskId]) { + if (taskId != null) { + _subscriptions[taskId]?.cancel(); + _subscriptions.remove(taskId); + } else { + _subscriptions.forEach((key, value) { + value.cancel(); + _subscriptions.remove(key); + }); + } + } + + Future acknowledgeTasks(Iterable taskIds) async { + final tasks = value.values.where((task) => taskIds.contains(task.taskId)); + await Future.wait([for (var task in tasks) _api.acknowledgeTask(task)]); + value = value..removeWhere((key, value) => taskIds.contains(key)); + notifyListeners(); + } +} diff --git a/lib/main.dart b/lib/main.dart index d2a51c1..bc36a56 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -28,6 +28,7 @@ import 'package:paperless_mobile/core/exception/server_message_exception.dart'; import 'package:paperless_mobile/core/factory/paperless_api_factory.dart'; import 'package:paperless_mobile/core/factory/paperless_api_factory_impl.dart'; import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart'; +import 'package:paperless_mobile/core/model/info_message_exception.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; import 'package:paperless_mobile/core/security/session_manager.dart'; import 'package:paperless_mobile/core/service/connectivity_status_service.dart'; @@ -37,7 +38,9 @@ import 'package:paperless_mobile/features/notifications/services/local_notificat import 'package:paperless_mobile/features/settings/view/widgets/global_settings_builder.dart'; import 'package:paperless_mobile/features/sharing/model/share_intent_queue.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; +import 'package:paperless_mobile/helpers/message_helpers.dart'; import 'package:paperless_mobile/routes/navigation_keys.dart'; +import 'package:paperless_mobile/routes/routes.dart'; import 'package:paperless_mobile/routes/typed/branches/documents_route.dart'; import 'package:paperless_mobile/routes/typed/branches/inbox_route.dart'; import 'package:paperless_mobile/routes/typed/branches/labels_route.dart'; @@ -47,6 +50,8 @@ import 'package:paperless_mobile/routes/typed/branches/scanner_route.dart'; import 'package:paperless_mobile/routes/typed/branches/upload_queue_route.dart'; import 'package:paperless_mobile/routes/typed/shells/provider_shell_route.dart'; import 'package:paperless_mobile/routes/typed/shells/scaffold_shell_route.dart'; +import 'package:paperless_mobile/routes/typed/top_level/checking_login_route.dart'; +import 'package:paperless_mobile/routes/typed/top_level/logging_out_route.dart'; import 'package:paperless_mobile/routes/typed/top_level/login_route.dart'; import 'package:paperless_mobile/routes/typed/top_level/settings_route.dart'; import 'package:paperless_mobile/routes/typed/top_level/switching_accounts_route.dart'; @@ -234,6 +239,8 @@ class _GoRouterShellState extends State { $loginRoute, $verifyIdentityRoute, $switchingAccountsRoute, + $logginOutRoute, + $checkingLoginRoute, ShellRoute( navigatorKey: rootNavigatorKey, builder: ProviderShellRoute(widget.apiFactory).build, @@ -280,19 +287,33 @@ class _GoRouterShellState extends State { listener: (context, state) { switch (state) { case UnauthenticatedState(): - const LoginRoute().go(context); + _router.goNamed(R.login); break; case RequiresLocalAuthenticationState(): - const VerifyIdentityRoute().go(context); + _router.goNamed(R.verifyIdentity); break; case SwitchingAccountsState(): - const SwitchingAccountsRoute().go(context); + final userId = context.read().id; + context + .read() + .cancelUserNotifications(userId); + _router.goNamed(R.switchingAccounts); break; case AuthenticatedState(): - const LandingRoute().go(context); + _router.goNamed(R.landing); + break; + case CheckingLoginState(): + _router.goNamed(R.checkingLogin); + break; + case LogginOutState(): + final userId = context.read().id; + context + .read() + .cancelUserNotifications(userId); + _router.goNamed(R.loggingOut); break; case AuthenticationErrorState(): - const LoginRoute().go(context); + _router.goNamed(R.login); break; } }, diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index ed91b82..9df4834 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -21,4 +21,6 @@ class R { static const linkedDocuments = "linkedDocuments"; static const bulkEditDocuments = "bulkEditDocuments"; static const uploadQueue = "uploadQueue"; + static const checkingLogin = "checkingLogin"; + static const loggingOut = "loggingOut"; } diff --git a/lib/routes/typed/shells/provider_shell_route.dart b/lib/routes/typed/shells/provider_shell_route.dart index e100479..0a150b6 100644 --- a/lib/routes/typed/shells/provider_shell_route.dart +++ b/lib/routes/typed/shells/provider_shell_route.dart @@ -61,11 +61,15 @@ class ProviderShellRoute extends ShellRouteData { ) { final currentUserId = Hive.box(HiveBoxes.globalSettings) .getValue()! - .loggedInUserId!; + .loggedInUserId; + if (currentUserId == null) { + return const SizedBox.shrink(); + } final authenticatedUser = Hive.box(HiveBoxes.localUserAccount).get( currentUserId, )!; + return HomeShellWidget( localUserId: authenticatedUser.id, paperlessApiVersion: authenticatedUser.apiVersion, diff --git a/lib/routes/typed/top_level/checking_login_route.dart b/lib/routes/typed/top_level/checking_login_route.dart new file mode 100644 index 0000000..1a9d62b --- /dev/null +++ b/lib/routes/typed/top_level/checking_login_route.dart @@ -0,0 +1,23 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:paperless_mobile/routes/routes.dart'; + +part 'checking_login_route.g.dart'; + +@TypedGoRoute( + path: "/checking-login", + name: R.checkingLogin, +) +class CheckingLoginRoute extends GoRouteData { + const CheckingLoginRoute(); + @override + Widget build(BuildContext context, GoRouterState state) { + return Scaffold( + body: Center( + child: Text("Logging in..."), + ), + ); + } +} diff --git a/lib/routes/typed/top_level/logging_out_route.dart b/lib/routes/typed/top_level/logging_out_route.dart new file mode 100644 index 0000000..d584be9 --- /dev/null +++ b/lib/routes/typed/top_level/logging_out_route.dart @@ -0,0 +1,23 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import 'package:paperless_mobile/routes/routes.dart'; + +part 'logging_out_route.g.dart'; + +@TypedGoRoute( + path: "/logging-out", + name: R.loggingOut, +) +class LogginOutRoute extends GoRouteData { + const LogginOutRoute(); + @override + Widget build(BuildContext context, GoRouterState state) { + return Scaffold( + body: Center( + child: Text("Logging out..."), + ), + ); + } +}