From 88085b566264c93ae3811f6ac57b9e6ff7dcf845 Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Sun, 30 Apr 2023 20:49:36 +0200 Subject: [PATCH] feat: Restructure providers and repositories (WIP, not compiling) --- .../database/tables/local_user_account.dart | 8 +- lib/core/factory/paperless_api_factory.dart | 10 + .../factory/paperless_api_factory_impl.dart | 35 ++ .../repository/saved_view_repository.dart | 5 + lib/core/security/session_manager.dart | 20 +- lib/features/home/view/home_page.dart | 58 +-- lib/features/home/view/home_route.dart | 144 +++++- lib/features/inbox/cubit/inbox_cubit.dart | 12 +- .../login/cubit/authentication_cubit.dart | 85 +--- .../cubit/authentication_cubit.freezed.dart | 465 ++++++++++++++++++ .../login/cubit/authentication_state.dart | 53 +- .../login/cubit/old_authentication_state.dart | 48 ++ .../settings/view/widgets/user_avatar.dart | 1 + lib/main.dart | 101 ++-- 14 files changed, 776 insertions(+), 269 deletions(-) create mode 100644 lib/core/factory/paperless_api_factory.dart create mode 100644 lib/core/factory/paperless_api_factory_impl.dart create mode 100644 lib/features/login/cubit/authentication_cubit.freezed.dart create mode 100644 lib/features/login/cubit/old_authentication_state.dart diff --git a/lib/core/database/tables/local_user_account.dart b/lib/core/database/tables/local_user_account.dart index c2228ce..9c44504 100644 --- a/lib/core/database/tables/local_user_account.dart +++ b/lib/core/database/tables/local_user_account.dart @@ -14,15 +14,15 @@ class LocalUserAccount extends HiveObject { final String id; @HiveField(4) - LocalUserSettings settings; + final LocalUserSettings settings; - @HiveField(5) - UserModel paperlessUser; + @HiveField(6) + final int paperlessUserId; LocalUserAccount({ required this.id, required this.serverUrl, required this.settings, - required this.paperlessUser, + required this.paperlessUserId, }); } diff --git a/lib/core/factory/paperless_api_factory.dart b/lib/core/factory/paperless_api_factory.dart new file mode 100644 index 0000000..a273f7f --- /dev/null +++ b/lib/core/factory/paperless_api_factory.dart @@ -0,0 +1,10 @@ +import 'package:dio/dio.dart'; +import 'package:paperless_api/paperless_api.dart'; + +abstract class PaperlessApiFactory { + PaperlessDocumentsApi createDocumentsApi(Dio dio, {required int apiVersion}); + PaperlessSavedViewsApi createSavedViewsApi(Dio dio, {required int apiVersion}); + PaperlessLabelsApi createLabelsApi(Dio dio, {required int apiVersion}); + PaperlessServerStatsApi createServerStatsApi(Dio dio, {required int apiVersion}); + PaperlessTasksApi createTasksApi(Dio dio, {required int apiVersion}); +} diff --git a/lib/core/factory/paperless_api_factory_impl.dart b/lib/core/factory/paperless_api_factory_impl.dart new file mode 100644 index 0000000..d4b0232 --- /dev/null +++ b/lib/core/factory/paperless_api_factory_impl.dart @@ -0,0 +1,35 @@ +import 'package:dio/dio.dart'; +import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/factory/paperless_api_factory.dart'; +import 'package:paperless_mobile/core/security/session_manager.dart'; + +class PaperlessApiFactoryImpl implements PaperlessApiFactory { + final SessionManager sessionManager; + + PaperlessApiFactoryImpl(this.sessionManager); + + @override + PaperlessDocumentsApi createDocumentsApi(Dio dio, {required int apiVersion}) { + return PaperlessDocumentsApiImpl(dio); + } + + @override + PaperlessLabelsApi createLabelsApi(Dio dio, {required int apiVersion}) { + return PaperlessLabelApiImpl(dio); + } + + @override + PaperlessSavedViewsApi createSavedViewsApi(Dio dio, {required int apiVersion}) { + return PaperlessSavedViewsApiImpl(dio); + } + + @override + PaperlessServerStatsApi createServerStatsApi(Dio dio, {required int apiVersion}) { + return PaperlessServerStatsApiImpl(dio); + } + + @override + PaperlessTasksApi createTasksApi(Dio dio, {required int apiVersion}) { + return PaperlessTasksApiImpl(dio); + } +} diff --git a/lib/core/repository/saved_view_repository.dart b/lib/core/repository/saved_view_repository.dart index b84cdda..54ae9fe 100644 --- a/lib/core/repository/saved_view_repository.dart +++ b/lib/core/repository/saved_view_repository.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:flutter/material.dart'; import 'package:hydrated_bloc/hydrated_bloc.dart'; import 'package:paperless_api/paperless_api.dart'; import 'package:paperless_mobile/core/repository/saved_view_repository_state.dart'; @@ -25,6 +26,10 @@ class SavedViewRepository extends HydratedCubit { SavedViewRepository(this._api) : super(const SavedViewRepositoryState()); + Future initialize() { + return findAll(); + } + Future create(SavedView object) async { final created = await _api.save(object); final updatedState = {...state.savedViews}..putIfAbsent(created.id!, () => created); diff --git a/lib/core/security/session_manager.dart b/lib/core/security/session_manager.dart index efca93b..a3e4ab5 100644 --- a/lib/core/security/session_manager.dart +++ b/lib/core/security/session_manager.dart @@ -2,7 +2,7 @@ import 'dart:io'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; -import 'package:paperless_api/paperless_api.dart'; +import 'package:flutter/material.dart'; import 'package:paperless_mobile/core/interceptor/retry_on_connection_change_interceptor.dart'; import 'package:paperless_mobile/features/login/model/client_certificate.dart'; import 'package:pretty_dio_logger/pretty_dio_logger.dart'; @@ -10,15 +10,10 @@ import 'package:pretty_dio_logger/pretty_dio_logger.dart'; /// Manages the security context, authentication and base request URL for /// an underlying [Dio] client which is injected into all services /// requiring authenticated access to the Paperless HTTP API. -class SessionManager { - final Dio _client; - PaperlessServerInformationModel _serverInformation; +class SessionManager extends ValueNotifier { + Dio get client => value; - Dio get client => _client; - - SessionManager([List interceptors = const []]) - : _client = _initDio(interceptors), - _serverInformation = PaperlessServerInformationModel(); + SessionManager([List interceptors = const []]) : super(_initDio(interceptors)); static Dio _initDio(List interceptors) { //en- and decoded by utf8 by default @@ -48,7 +43,6 @@ class SessionManager { String? baseUrl, String? authToken, ClientCertificate? clientCertificate, - PaperlessServerInformationModel? serverInformation, }) { if (clientCertificate != null) { final context = SecurityContext() @@ -81,15 +75,13 @@ class SessionManager { }); } - if (serverInformation != null) { - _serverInformation = serverInformation; - } + notifyListeners(); } void resetSettings() { client.httpClientAdapter = IOHttpClientAdapter(); client.options.baseUrl = ''; client.options.headers.remove(HttpHeaders.authorizationHeader); - _serverInformation = PaperlessServerInformationModel(); + notifyListeners(); } } diff --git a/lib/features/home/view/home_page.dart b/lib/features/home/view/home_page.dart index ffc5793..cf075fa 100644 --- a/lib/features/home/view/home_page.dart +++ b/lib/features/home/view/home_page.dart @@ -52,35 +52,12 @@ class HomePage extends StatefulWidget { class _HomePageState extends State with WidgetsBindingObserver { int _currentIndex = 0; - final DocumentScannerCubit _scannerCubit = DocumentScannerCubit(); - late final DocumentsCubit _documentsCubit; - late final InboxCubit _inboxCubit; - late final SavedViewCubit _savedViewCubit; late Timer _inboxTimer; @override void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - _initializeData(context); - final userId = - Hive.box(HiveBoxes.globalSettings).getValue()!.currentLoggedInUser; - _documentsCubit = DocumentsCubit( - context.read(), - context.read(), - context.read(), - Hive.box(HiveBoxes.localUserAppState).get(userId)!, - )..reload(); - _savedViewCubit = SavedViewCubit( - context.read(), - context.read(), - )..reload(); - _inboxCubit = InboxCubit( - context.read(), - context.read(), - context.read(), - context.read(), - ); _listenToInboxChanges(); WidgetsBinding.instance.addPostFrameCallback((timeStamp) { _listenForReceivedFiles(); @@ -97,7 +74,7 @@ class _HomePageState extends State with WidgetsBindingObserver { if (!mounted) { timer.cancel(); } else { - _inboxCubit.refreshItemsInInboxCount(); + context.read().refreshItemsInInboxCount(); } }); } @@ -127,9 +104,6 @@ class _HomePageState extends State with WidgetsBindingObserver { void dispose() { WidgetsBinding.instance.removeObserver(this); _inboxTimer.cancel(); - _inboxCubit.close(); - _documentsCubit.close(); - _savedViewCubit.close(); super.dispose(); } @@ -247,7 +221,6 @@ class _HomePageState extends State with WidgetsBindingObserver { ), label: S.of(context)!.inbox, badgeBuilder: (icon) => BlocBuilder( - bloc: _inboxCubit, builder: (context, state) { if (state.itemsInInboxCount > 0) { return Badge.count( @@ -261,31 +234,10 @@ class _HomePageState extends State with WidgetsBindingObserver { ), ]; final routes = [ - MultiBlocProvider( - // key: ValueKey(userId), - providers: [ - BlocProvider.value(value: _documentsCubit), - BlocProvider.value(value: _savedViewCubit), - ], - child: const DocumentsPage(), - ), - BlocProvider.value( - value: _scannerCubit, - child: const ScannerPage(), - ), - MultiBlocProvider( - // key: ValueKey(userId), - providers: [ - BlocProvider( - create: (context) => LabelCubit(context.read()), - ) - ], - child: const LabelsPage(), - ), - BlocProvider.value( - value: _inboxCubit, - child: const InboxPage(), - ), + const DocumentsPage(), + const ScannerPage(), + const LabelsPage(), + const InboxPage(), ]; return MultiBlocListener( diff --git a/lib/features/home/view/home_route.dart b/lib/features/home/view/home_route.dart index 4f06c9b..eaa97e7 100644 --- a/lib/features/home/view/home_route.dart +++ b/lib/features/home/view/home_route.dart @@ -1,29 +1,139 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/src/widgets/framework.dart'; +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'; import 'package:paperless_mobile/core/bloc/server_information_cubit.dart'; +import 'package:paperless_mobile/core/config/hive/hive_config.dart'; +import 'package:paperless_mobile/core/database/tables/local_user_app_state.dart'; +import 'package:paperless_mobile/core/factory/paperless_api_factory.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/repository/saved_view_repository.dart'; +import 'package:paperless_mobile/core/security/session_manager.dart'; +import 'package:paperless_mobile/core/service/dio_file_service.dart'; +import 'package:paperless_mobile/features/document_scan/cubit/document_scanner_cubit.dart'; +import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart'; import 'package:paperless_mobile/features/home/view/home_page.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/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:provider/provider.dart'; class HomeRoute extends StatelessWidget { - const HomeRoute({super.key}); + final String localUserId; + final int paperlessApiVersion; + final PaperlessApiFactory paperlessProviderFactory; + + const HomeRoute({ + super.key, + required this.paperlessApiVersion, + required this.paperlessProviderFactory, + required this.localUserId, + }); @override Widget build(BuildContext context) { - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => TaskStatusCubit( - context.read(), - ), - ), - BlocProvider( - create: (context) => ServerInformationCubit( - context.read(), - )..updateInformation(), - ), - ], - child: HomePage(), + return GlobalSettingsBuilder( + builder: (context, settings) { + final currentLocalUserId = settings.currentLoggedInUser!; + return MultiProvider( + providers: [ + Provider( + create: (context) => CacheManager( + Config( + localUserId, + fileService: DioFileService(context.read().client), + ), + ), + ), + ProxyProvider( + update: (context, value, previous) => paperlessProviderFactory.createDocumentsApi( + value.client, + apiVersion: paperlessApiVersion, + ), + ), + ProxyProvider( + update: (context, value, previous) => paperlessProviderFactory.createLabelsApi( + value.client, + apiVersion: paperlessApiVersion, + ), + ), + ProxyProvider( + update: (context, value, previous) => paperlessProviderFactory.createSavedViewsApi( + value.client, + apiVersion: paperlessApiVersion, + ), + ), + ProxyProvider( + update: (context, value, previous) => paperlessProviderFactory.createServerStatsApi( + value.client, + apiVersion: paperlessApiVersion, + ), + ), + ProxyProvider( + update: (context, value, previous) => paperlessProviderFactory.createTasksApi( + value.client, + apiVersion: paperlessApiVersion, + ), + ), + ], + builder: (context, child) { + return MultiProvider( + providers: [ + ProxyProvider( + update: (context, value, previous) => LabelRepository(value)..initialize(), + ), + ProxyProvider( + update: (context, value, previous) => SavedViewRepository(value)..initialize(), + ), + ], + builder: (context, child) { + return MultiBlocProvider( + providers: [ + ProxyProvider3( + update: (context, docApi, notifier, labelRepo, previous) => DocumentsCubit( + docApi, + notifier, + labelRepo, + Hive.box(HiveBoxes.localUserAppState) + .get(currentLocalUserId)!, + )..reload(), + ), + Provider(create: (context) => DocumentScannerCubit()), + ProxyProvider4( + update: (context, docApi, statsApi, labelRepo, notifier, previous) => + InboxCubit( + docApi, + statsApi, + labelRepo, + notifier, + )..initialize(), + ), + ProxyProvider2( + update: (context, savedViewRepo, labelRepo, previous) => SavedViewCubit( + savedViewRepo, + labelRepo, + )..initialize(), + ), + ProxyProvider( + update: (context, value, previous) => ServerInformationCubit(value), + ), + ProxyProvider( + update: (context, value, previous) => LabelCubit(value), + ), + ], + child: const HomePage(), + ); + }, + ); + }, + ); + }, ); } } diff --git a/lib/features/inbox/cubit/inbox_cubit.dart b/lib/features/inbox/cubit/inbox_cubit.dart index 3ed0188..3a5cf89 100644 --- a/lib/features/inbox/cubit/inbox_cubit.dart +++ b/lib/features/inbox/cubit/inbox_cubit.dart @@ -52,16 +52,18 @@ class InboxCubit extends HydratedCubit with DocumentPagingBlocMixin emit(state.copyWith(labels: labels)); }, ); - - refreshItemsInInboxCount(false); - loadInbox(); } - void refreshItemsInInboxCount([bool shouldLoadInbox = true]) async { + Future initialize() async { + await refreshItemsInInboxCount(false); + await loadInbox(); + } + + Future refreshItemsInInboxCount([bool shouldLoadInbox = true]) async { final stats = await _statsApi.getServerStatistics(); if (stats.documentsInInbox != state.itemsInInboxCount && shouldLoadInbox) { - loadInbox(); + await loadInbox(); } emit( state.copyWith( diff --git a/lib/features/login/cubit/authentication_cubit.dart b/lib/features/login/cubit/authentication_cubit.dart index fcd10a6..47e9f84 100644 --- a/lib/features/login/cubit/authentication_cubit.dart +++ b/lib/features/login/cubit/authentication_cubit.dart @@ -3,13 +3,12 @@ import 'dart:typed_data'; import 'package:equatable/equatable.dart'; import 'package:flutter/widgets.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:hive_flutter/adapters.dart'; import 'package:hydrated_bloc/hydrated_bloc.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_app_state.dart'; -import 'package:paperless_mobile/core/repository/label_repository.dart'; -import 'package:paperless_mobile/core/repository/saved_view_repository.dart'; import 'package:paperless_mobile/core/security/session_manager.dart'; import 'package:paperless_mobile/features/login/model/client_certificate.dart'; import 'package:paperless_mobile/features/login/model/login_form_credentials.dart'; @@ -19,29 +18,22 @@ import 'package:paperless_mobile/features/login/services/authentication_service. import 'package:paperless_mobile/core/database/tables/global_settings.dart'; import 'package:paperless_mobile/core/database/tables/local_user_settings.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + part 'authentication_state.dart'; +part 'authentication_cubit.freezed.dart'; class AuthenticationCubit extends Cubit { + final PaperlessUserApi _userApi; final LocalAuthenticationService _localAuthService; final PaperlessAuthenticationApi _authApi; final SessionManager _sessionManager; - final LabelRepository _labelRepository; - final SavedViewRepository _savedViewRepository; - final PaperlessServerStatsApi _serverStatsApi; - final PaperlessUserApi _userApi; - final PaperlessUserApiV3? _userApiV3; AuthenticationCubit( this._localAuthService, this._authApi, this._sessionManager, - this._labelRepository, - this._savedViewRepository, - this._serverStatsApi, - this._userApi, { - PaperlessUserApiV3? userApiV3, - }) : _userApiV3 = userApiV3, - super(const AuthenticationState()); + this._userApi, + ) : super(const AuthenticationState.unauthenticated()); Future login({ required LoginFormCredentials credentials, @@ -66,14 +58,10 @@ class AuthenticationCubit extends Cubit { final globalSettings = Hive.box(HiveBoxes.globalSettings).getValue()!; globalSettings.currentLoggedInUser = localUserId; await globalSettings.save(); - emit( - AuthenticationState( - isAuthenticated: true, - username: credentials.username, - localUserId: localUserId, - fullName: serverUser.fullName, + AuthenticationState.authenticated( apiVersion: apiVersion, + localUserId: localUserId, ), ); } @@ -116,26 +104,18 @@ class AuthenticationCubit extends Cubit { _sessionManager.updateSettings( authToken: credentials!.token, clientCertificate: credentials.clientCertificate, - serverInformation: PaperlessServerInformationModel(), baseUrl: account.serverUrl, ); - await _reloadRepositories(); globalSettings.currentLoggedInUser = localUserId; await globalSettings.save(); final response = await _sessionManager.client.get("/api/"); final apiVersion = response.headers["x-api-version"] as int; - - emit( - AuthenticationState( - isAuthenticated: true, - username: account.paperlessUser.username, - fullName: account.paperlessUser.fullName, - localUserId: localUserId, - apiVersion: apiVersion, - ), - ); + emit(AuthenticationState.authenticated( + localUserId: localUserId, + apiVersion: apiVersion, + )); } Future addAccount({ @@ -179,19 +159,19 @@ class AuthenticationCubit extends Cubit { /// Future restoreSessionState() async { final globalSettings = Hive.box(HiveBoxes.globalSettings).getValue()!; - final userId = globalSettings.currentLoggedInUser; - if (userId == null) { + final localUserId = globalSettings.currentLoggedInUser; + if (localUserId == null) { // If there is nothing to restore, we can quit here. return; } - final userAccount = Hive.box(HiveBoxes.localUserAccount).get(userId)!; + final userAccount = Hive.box(HiveBoxes.localUserAccount).get(localUserId)!; if (userAccount.settings.isBiometricAuthenticationEnabled) { final localAuthSuccess = await _localAuthService.authenticateLocalUser("Authenticate to log back in"); //TODO: INTL if (!localAuthSuccess) { - emit(const AuthenticationState(showBiometricAuthenticationScreen: true)); + emit(const AuthenticationState.requriresLocalAuthentication()); return; } } @@ -207,18 +187,13 @@ class AuthenticationCubit extends Cubit { clientCertificate: authentication.clientCertificate, authToken: authentication.token, baseUrl: userAccount.serverUrl, - serverInformation: PaperlessServerInformationModel(), ); final response = await _sessionManager.client.get("/api/"); final apiVersion = response.headers["x-api-version"] as int; - emit( - AuthenticationState( - isAuthenticated: true, - showBiometricAuthenticationScreen: false, - username: userAccount.paperlessUser.username, + AuthenticationState.authenticated( apiVersion: apiVersion, - fullName: userAccount.paperlessUser.fullName, + localUserId: localUserId, ), ); } @@ -229,7 +204,7 @@ class AuthenticationCubit extends Cubit { globalSettings ..currentLoggedInUser = null ..save(); - emit(const AuthenticationState()); + emit(const AuthenticationState.unauthenticated()); } Future _getEncryptedBoxKey() async { @@ -254,23 +229,12 @@ class AuthenticationCubit extends Cubit { ); } - Future _resetExternalState() { + Future _resetExternalState() async { _sessionManager.resetSettings(); - return Future.wait([ - HydratedBloc.storage.clear(), - _labelRepository.clear(), - _savedViewRepository.clear(), - ]); + await HydratedBloc.storage.clear(); } - Future _reloadRepositories() { - return Future.wait([ - _labelRepository.initialize(), - _savedViewRepository.findAll(), - ]); - } - - Future _addUser( + Future _addUser( String localUserId, String serverUrl, LoginFormCredentials credentials, @@ -303,7 +267,6 @@ class AuthenticationCubit extends Cubit { } final serverUserId = await _userApi.findCurrentUserId(); - final serverUser = await _userApi.find(serverUserId); // Create user account await userAccountBox.put( @@ -312,7 +275,7 @@ class AuthenticationCubit extends Cubit { id: localUserId, settings: LocalUserSettings(), serverUrl: serverUrl, - paperlessUser: serverUser, + paperlessUserId: 1, ), ); @@ -332,6 +295,6 @@ class AuthenticationCubit extends Cubit { ), ); userCredentialsBox.close(); - return serverUser; + return serverUserId; } } diff --git a/lib/features/login/cubit/authentication_cubit.freezed.dart b/lib/features/login/cubit/authentication_cubit.freezed.dart new file mode 100644 index 0000000..629ef40 --- /dev/null +++ b/lib/features/login/cubit/authentication_cubit.freezed.dart @@ -0,0 +1,465 @@ +// coverage:ignore-file +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint +// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark + +part of 'authentication_cubit.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(T value) => value; + +final _privateConstructorUsedError = UnsupportedError( + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + +/// @nodoc +mixin _$AuthenticationState { + @optionalTypeArgs + TResult when({ + required TResult Function() unauthenticated, + required TResult Function() requriresLocalAuthentication, + required TResult Function(String localUserId, int apiVersion) authenticated, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? unauthenticated, + TResult? Function()? requriresLocalAuthentication, + TResult? Function(String localUserId, int apiVersion)? authenticated, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? unauthenticated, + TResult Function()? requriresLocalAuthentication, + TResult Function(String localUserId, int apiVersion)? authenticated, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_RequiresLocalAuthentication value) + requriresLocalAuthentication, + required TResult Function(_Authenticated value) authenticated, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_RequiresLocalAuthentication value)? + requriresLocalAuthentication, + TResult? Function(_Authenticated value)? authenticated, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_RequiresLocalAuthentication value)? + requriresLocalAuthentication, + TResult Function(_Authenticated value)? authenticated, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AuthenticationStateCopyWith<$Res> { + factory $AuthenticationStateCopyWith( + AuthenticationState value, $Res Function(AuthenticationState) then) = + _$AuthenticationStateCopyWithImpl<$Res, AuthenticationState>; +} + +/// @nodoc +class _$AuthenticationStateCopyWithImpl<$Res, $Val extends AuthenticationState> + implements $AuthenticationStateCopyWith<$Res> { + _$AuthenticationStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; +} + +/// @nodoc +abstract class _$$_UnauthenticatedCopyWith<$Res> { + factory _$$_UnauthenticatedCopyWith( + _$_Unauthenticated value, $Res Function(_$_Unauthenticated) then) = + __$$_UnauthenticatedCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$_UnauthenticatedCopyWithImpl<$Res> + extends _$AuthenticationStateCopyWithImpl<$Res, _$_Unauthenticated> + implements _$$_UnauthenticatedCopyWith<$Res> { + __$$_UnauthenticatedCopyWithImpl( + _$_Unauthenticated _value, $Res Function(_$_Unauthenticated) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$_Unauthenticated implements _Unauthenticated { + const _$_Unauthenticated(); + + @override + String toString() { + return 'AuthenticationState.unauthenticated()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && other is _$_Unauthenticated); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() unauthenticated, + required TResult Function() requriresLocalAuthentication, + required TResult Function(String localUserId, int apiVersion) authenticated, + }) { + return unauthenticated(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? unauthenticated, + TResult? Function()? requriresLocalAuthentication, + TResult? Function(String localUserId, int apiVersion)? authenticated, + }) { + return unauthenticated?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? unauthenticated, + TResult Function()? requriresLocalAuthentication, + TResult Function(String localUserId, int apiVersion)? authenticated, + required TResult orElse(), + }) { + if (unauthenticated != null) { + return unauthenticated(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_RequiresLocalAuthentication value) + requriresLocalAuthentication, + required TResult Function(_Authenticated value) authenticated, + }) { + return unauthenticated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_RequiresLocalAuthentication value)? + requriresLocalAuthentication, + TResult? Function(_Authenticated value)? authenticated, + }) { + return unauthenticated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_RequiresLocalAuthentication value)? + requriresLocalAuthentication, + TResult Function(_Authenticated value)? authenticated, + required TResult orElse(), + }) { + if (unauthenticated != null) { + return unauthenticated(this); + } + return orElse(); + } +} + +abstract class _Unauthenticated implements AuthenticationState { + const factory _Unauthenticated() = _$_Unauthenticated; +} + +/// @nodoc +abstract class _$$_RequiresLocalAuthenticationCopyWith<$Res> { + factory _$$_RequiresLocalAuthenticationCopyWith( + _$_RequiresLocalAuthentication value, + $Res Function(_$_RequiresLocalAuthentication) then) = + __$$_RequiresLocalAuthenticationCopyWithImpl<$Res>; +} + +/// @nodoc +class __$$_RequiresLocalAuthenticationCopyWithImpl<$Res> + extends _$AuthenticationStateCopyWithImpl<$Res, + _$_RequiresLocalAuthentication> + implements _$$_RequiresLocalAuthenticationCopyWith<$Res> { + __$$_RequiresLocalAuthenticationCopyWithImpl( + _$_RequiresLocalAuthentication _value, + $Res Function(_$_RequiresLocalAuthentication) _then) + : super(_value, _then); +} + +/// @nodoc + +class _$_RequiresLocalAuthentication implements _RequiresLocalAuthentication { + const _$_RequiresLocalAuthentication(); + + @override + String toString() { + return 'AuthenticationState.requriresLocalAuthentication()'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_RequiresLocalAuthentication); + } + + @override + int get hashCode => runtimeType.hashCode; + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() unauthenticated, + required TResult Function() requriresLocalAuthentication, + required TResult Function(String localUserId, int apiVersion) authenticated, + }) { + return requriresLocalAuthentication(); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? unauthenticated, + TResult? Function()? requriresLocalAuthentication, + TResult? Function(String localUserId, int apiVersion)? authenticated, + }) { + return requriresLocalAuthentication?.call(); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? unauthenticated, + TResult Function()? requriresLocalAuthentication, + TResult Function(String localUserId, int apiVersion)? authenticated, + required TResult orElse(), + }) { + if (requriresLocalAuthentication != null) { + return requriresLocalAuthentication(); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_RequiresLocalAuthentication value) + requriresLocalAuthentication, + required TResult Function(_Authenticated value) authenticated, + }) { + return requriresLocalAuthentication(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_RequiresLocalAuthentication value)? + requriresLocalAuthentication, + TResult? Function(_Authenticated value)? authenticated, + }) { + return requriresLocalAuthentication?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_RequiresLocalAuthentication value)? + requriresLocalAuthentication, + TResult Function(_Authenticated value)? authenticated, + required TResult orElse(), + }) { + if (requriresLocalAuthentication != null) { + return requriresLocalAuthentication(this); + } + return orElse(); + } +} + +abstract class _RequiresLocalAuthentication implements AuthenticationState { + const factory _RequiresLocalAuthentication() = _$_RequiresLocalAuthentication; +} + +/// @nodoc +abstract class _$$_AuthenticatedCopyWith<$Res> { + factory _$$_AuthenticatedCopyWith( + _$_Authenticated value, $Res Function(_$_Authenticated) then) = + __$$_AuthenticatedCopyWithImpl<$Res>; + @useResult + $Res call({String localUserId, int apiVersion}); +} + +/// @nodoc +class __$$_AuthenticatedCopyWithImpl<$Res> + extends _$AuthenticationStateCopyWithImpl<$Res, _$_Authenticated> + implements _$$_AuthenticatedCopyWith<$Res> { + __$$_AuthenticatedCopyWithImpl( + _$_Authenticated _value, $Res Function(_$_Authenticated) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? localUserId = null, + Object? apiVersion = null, + }) { + return _then(_$_Authenticated( + localUserId: null == localUserId + ? _value.localUserId + : localUserId // ignore: cast_nullable_to_non_nullable + as String, + apiVersion: null == apiVersion + ? _value.apiVersion + : apiVersion // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc + +class _$_Authenticated implements _Authenticated { + const _$_Authenticated({required this.localUserId, required this.apiVersion}); + + @override + final String localUserId; + @override + final int apiVersion; + + @override + String toString() { + return 'AuthenticationState.authenticated(localUserId: $localUserId, apiVersion: $apiVersion)'; + } + + @override + bool operator ==(dynamic other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$_Authenticated && + (identical(other.localUserId, localUserId) || + other.localUserId == localUserId) && + (identical(other.apiVersion, apiVersion) || + other.apiVersion == apiVersion)); + } + + @override + int get hashCode => Object.hash(runtimeType, localUserId, apiVersion); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$_AuthenticatedCopyWith<_$_Authenticated> get copyWith => + __$$_AuthenticatedCopyWithImpl<_$_Authenticated>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function() unauthenticated, + required TResult Function() requriresLocalAuthentication, + required TResult Function(String localUserId, int apiVersion) authenticated, + }) { + return authenticated(localUserId, apiVersion); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function()? unauthenticated, + TResult? Function()? requriresLocalAuthentication, + TResult? Function(String localUserId, int apiVersion)? authenticated, + }) { + return authenticated?.call(localUserId, apiVersion); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function()? unauthenticated, + TResult Function()? requriresLocalAuthentication, + TResult Function(String localUserId, int apiVersion)? authenticated, + required TResult orElse(), + }) { + if (authenticated != null) { + return authenticated(localUserId, apiVersion); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(_Unauthenticated value) unauthenticated, + required TResult Function(_RequiresLocalAuthentication value) + requriresLocalAuthentication, + required TResult Function(_Authenticated value) authenticated, + }) { + return authenticated(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(_Unauthenticated value)? unauthenticated, + TResult? Function(_RequiresLocalAuthentication value)? + requriresLocalAuthentication, + TResult? Function(_Authenticated value)? authenticated, + }) { + return authenticated?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(_Unauthenticated value)? unauthenticated, + TResult Function(_RequiresLocalAuthentication value)? + requriresLocalAuthentication, + TResult Function(_Authenticated value)? authenticated, + required TResult orElse(), + }) { + if (authenticated != null) { + return authenticated(this); + } + return orElse(); + } +} + +abstract class _Authenticated implements AuthenticationState { + const factory _Authenticated( + {required final String localUserId, + required final int apiVersion}) = _$_Authenticated; + + String get localUserId; + int get apiVersion; + @JsonKey(ignore: true) + _$$_AuthenticatedCopyWith<_$_Authenticated> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/features/login/cubit/authentication_state.dart b/lib/features/login/cubit/authentication_state.dart index 8b76cd6..21c886f 100644 --- a/lib/features/login/cubit/authentication_state.dart +++ b/lib/features/login/cubit/authentication_state.dart @@ -1,48 +1,11 @@ part of 'authentication_cubit.dart'; -class AuthenticationState with EquatableMixin { - final bool showBiometricAuthenticationScreen; - final bool isAuthenticated; - final String? username; - final String? fullName; - final String? localUserId; - final int? apiVersion; - - const AuthenticationState({ - this.isAuthenticated = false, - this.showBiometricAuthenticationScreen = false, - this.username, - this.fullName, - this.localUserId, - this.apiVersion, - }); - - AuthenticationState copyWith({ - bool? isAuthenticated, - bool? showBiometricAuthenticationScreen, - String? username, - String? fullName, - String? localUserId, - int? apiVersion, - }) { - return AuthenticationState( - isAuthenticated: isAuthenticated ?? this.isAuthenticated, - showBiometricAuthenticationScreen: - showBiometricAuthenticationScreen ?? this.showBiometricAuthenticationScreen, - username: username ?? this.username, - fullName: fullName ?? this.fullName, - localUserId: localUserId ?? this.localUserId, - apiVersion: apiVersion ?? this.apiVersion, - ); - } - - @override - List get props => [ - localUserId, - username, - fullName, - isAuthenticated, - showBiometricAuthenticationScreen, - apiVersion, - ]; +@freezed +class AuthenticationState with _$AuthenticationState { + const factory AuthenticationState.unauthenticated() = _Unauthenticated; + const factory AuthenticationState.requriresLocalAuthentication() = _RequiresLocalAuthentication; + const factory AuthenticationState.authenticated({ + required String localUserId, + required int apiVersion, + }) = _Authenticated; } diff --git a/lib/features/login/cubit/old_authentication_state.dart b/lib/features/login/cubit/old_authentication_state.dart new file mode 100644 index 0000000..6cdf17c --- /dev/null +++ b/lib/features/login/cubit/old_authentication_state.dart @@ -0,0 +1,48 @@ +import 'package:equatable/equatable.dart'; + +class OldAuthenticationState with EquatableMixin { + final bool showBiometricAuthenticationScreen; + final bool isAuthenticated; + final String? username; + final String? fullName; + final String? localUserId; + final int? apiVersion; + + const OldAuthenticationState({ + this.isAuthenticated = false, + this.showBiometricAuthenticationScreen = false, + this.username, + this.fullName, + this.localUserId, + this.apiVersion, + }); + + OldAuthenticationState copyWith({ + bool? isAuthenticated, + bool? showBiometricAuthenticationScreen, + String? username, + String? fullName, + String? localUserId, + int? apiVersion, + }) { + return OldAuthenticationState( + isAuthenticated: isAuthenticated ?? this.isAuthenticated, + showBiometricAuthenticationScreen: + showBiometricAuthenticationScreen ?? this.showBiometricAuthenticationScreen, + username: username ?? this.username, + fullName: fullName ?? this.fullName, + localUserId: localUserId ?? this.localUserId, + apiVersion: apiVersion ?? this.apiVersion, + ); + } + + @override + List get props => [ + localUserId, + username, + fullName, + isAuthenticated, + showBiometricAuthenticationScreen, + apiVersion, + ]; +} diff --git a/lib/features/settings/view/widgets/user_avatar.dart b/lib/features/settings/view/widgets/user_avatar.dart index 82a8356..8c11053 100644 --- a/lib/features/settings/view/widgets/user_avatar.dart +++ b/lib/features/settings/view/widgets/user_avatar.dart @@ -4,6 +4,7 @@ import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; class UserAvatar extends StatelessWidget { final String userId; final LocalUserAccount account; + const UserAvatar({ super.key, required this.userId, diff --git a/lib/main.dart b/lib/main.dart index f8f9e1d..cd7e1e5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,6 +21,8 @@ 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/factory/paperless_api_factory.dart'; +import 'package:paperless_mobile/core/factory/paperless_api_factory_impl.dart'; import 'package:paperless_mobile/core/interceptor/dio_http_error_interceptor.dart'; import 'package:paperless_mobile/core/interceptor/language_header.interceptor.dart'; import 'package:paperless_mobile/core/notifier/document_changed_notifier.dart'; @@ -89,10 +91,8 @@ void main() async { iosInfo = await DeviceInfoPlugin().iosInfo; } - // Initialize External dependencies final connectivity = Connectivity(); final localAuthentication = LocalAuthentication(); - // Initialize other utility classes final connectivityStatusService = ConnectivityStatusServiceImpl(connectivity); final localAuthService = LocalAuthenticationService(localAuthentication); @@ -111,40 +111,12 @@ void main() async { languageHeaderInterceptor, ]); - // Initialize Paperless APIs - final authApi = PaperlessAuthenticationApiImpl(sessionManager.client); - final documentsApi = PaperlessDocumentsApiImpl(sessionManager.client); - final labelsApi = PaperlessLabelApiImpl(sessionManager.client); - final statsApi = PaperlessServerStatsApiImpl(sessionManager.client); - final savedViewsApi = PaperlessSavedViewsApiImpl(sessionManager.client); - final tasksApi = PaperlessTasksApiImpl( - sessionManager.client, - ); - // Initialize Blocs/Cubits final connectivityCubit = ConnectivityCubit(connectivityStatusService); // Load application settings and stored authentication data await connectivityCubit.initialize(); - // Create repositories - final labelRepository = LabelRepository(labelsApi); - final savedViewRepository = SavedViewRepository(savedViewsApi); - - //Create cubits/blocs - final authCubit = AuthenticationCubit( - localAuthService, - authApi, - sessionManager, - labelRepository, - savedViewRepository, - statsApi, - ); - - if (globalSettings.currentLoggedInUser != null) { - await authCubit.restoreSessionState(); - } - final localNotificationService = LocalNotificationService(); await localNotificationService.initialize(); @@ -156,42 +128,20 @@ void main() async { runApp( MultiProvider( providers: [ + ChangeNotifierProvider.value(value: sessionManager), Provider.value(value: localAuthService), - Provider.value(value: authApi), - Provider.value(value: documentsApi), - Provider.value(value: labelsApi), - Provider.value(value: statsApi), - Provider.value(value: savedViewsApi), - Provider.value(value: tasksApi), - Provider( - create: (context) => cm.CacheManager( - cm.Config( - 'cacheKey', - fileService: DioFileService(sessionManager.client), - ), - ), - ), Provider.value( value: connectivityStatusService, ), Provider.value(value: localNotificationService), Provider.value(value: DocumentChangedNotifier()), ], - child: MultiRepositoryProvider( + child: MultiBlocProvider( providers: [ - RepositoryProvider.value( - value: labelRepository, - ), - RepositoryProvider.value( - value: savedViewRepository, - ), + BlocProvider.value(value: connectivityCubit), ], - child: MultiBlocProvider( - providers: [ - BlocProvider.value(value: authCubit), - BlocProvider.value(value: connectivityCubit), - ], - child: const PaperlessMobileEntrypoint(), + child: PaperlessMobileEntrypoint( + paperlessProviderFactory: PaperlessApiFactoryImpl(sessionManager), ), ), ), @@ -199,8 +149,10 @@ void main() async { } class PaperlessMobileEntrypoint extends StatefulWidget { + final PaperlessApiFactory paperlessProviderFactory; const PaperlessMobileEntrypoint({ Key? key, + required this.paperlessProviderFactory, }) : super(key: key); @override @@ -238,7 +190,9 @@ class _PaperlessMobileEntrypointState extends State { routes: { DocumentDetailsRoute.routeName: (context) => const DocumentDetailsRoute(), }, - home: const AuthenticationWrapper(), + home: AuthenticationWrapper( + paperlessProviderFactory: widget.paperlessProviderFactory, + ), ); }, ); @@ -248,7 +202,12 @@ class _PaperlessMobileEntrypointState extends State { } class AuthenticationWrapper extends StatefulWidget { - const AuthenticationWrapper({Key? key}) : super(key: key); + final PaperlessApiFactory paperlessProviderFactory; + + const AuthenticationWrapper({ + Key? key, + required this.paperlessProviderFactory, + }) : super(key: key); @override State createState() => _AuthenticationWrapperState(); @@ -295,17 +254,19 @@ class _AuthenticationWrapperState extends State { Widget build(BuildContext context) { return BlocBuilder( builder: (context, authentication) { - if (authentication.isAuthenticated) { - return HomeRoute( - key: ValueKey(authentication.localUserId), - ); - } else if (authentication.showBiometricAuthenticationScreen) { - return const VerifyIdentityPage(); - } - return LoginPage( - titleString: S.of(context)!.connectToPaperless, - submitText: S.of(context)!.signIn, - onSubmit: _onLogin, + return authentication.when( + unauthenticated: () => LoginPage( + titleString: S.of(context)!.connectToPaperless, + submitText: S.of(context)!.signIn, + onSubmit: _onLogin, + ), + requriresLocalAuthentication: () => const VerifyIdentityPage(), + authenticated: (localUserId, apiVersion) => HomeRoute( + key: ValueKey(localUserId), + paperlessApiVersion: apiVersion, + paperlessProviderFactory: widget.paperlessProviderFactory, + localUserId: localUserId, + ), ); }, );