diff --git a/lib/core/repository/label_repository.dart b/lib/core/repository/label_repository.dart index 6b5e7c6..08899bb 100644 --- a/lib/core/repository/label_repository.dart +++ b/lib/core/repository/label_repository.dart @@ -10,21 +10,26 @@ class LabelRepository extends PersistentRepository { LabelRepository(this._api) : super(const LabelRepositoryState()); - Future initialize() { - debugPrint("Initializing labels..."); - return Future.wait([ - findAllCorrespondents(), - findAllDocumentTypes(), - findAllStoragePaths(), - findAllTags(), - ]).catchError((error) { - debugPrint(error.toString()); - }, test: (error) => false); + Future initialize() async { + debugPrint("[LabelRepository] initialize() called."); + try { + await Future.wait([ + findAllCorrespondents(), + findAllDocumentTypes(), + findAllStoragePaths(), + findAllTags(), + ]); + } catch (error, stackTrace) { + debugPrint( + "[LabelRepository] An error occurred in initialize(): ${error.toString()}"); + debugPrintStack(stackTrace: stackTrace); + } } Future createTag(Tag object) async { final created = await _api.saveTag(object); - final updatedState = {...state.tags}..putIfAbsent(created.id!, () => created); + final updatedState = {...state.tags} + ..putIfAbsent(created.id!, () => created); emit(state.copyWith(tags: updatedState)); return created; } @@ -48,7 +53,8 @@ class LabelRepository extends PersistentRepository { Future> findAllTags([Iterable? ids]) async { final tags = await _api.getTags(ids); - final updatedState = {...state.tags}..addEntries(tags.map((e) => MapEntry(e.id!, e))); + final updatedState = {...state.tags} + ..addEntries(tags.map((e) => MapEntry(e.id!, e))); emit(state.copyWith(tags: updatedState)); return tags; } @@ -62,14 +68,16 @@ class LabelRepository extends PersistentRepository { Future createCorrespondent(Correspondent correspondent) async { final created = await _api.saveCorrespondent(correspondent); - final updatedState = {...state.correspondents}..putIfAbsent(created.id!, () => created); + final updatedState = {...state.correspondents} + ..putIfAbsent(created.id!, () => created); emit(state.copyWith(correspondents: updatedState)); return created; } Future deleteCorrespondent(Correspondent correspondent) async { await _api.deleteCorrespondent(correspondent); - final updatedState = {...state.correspondents}..removeWhere((k, v) => k == correspondent.id); + final updatedState = {...state.correspondents} + ..removeWhere((k, v) => k == correspondent.id); emit(state.copyWith(correspondents: updatedState)); return correspondent.id!; @@ -86,22 +94,22 @@ class LabelRepository extends PersistentRepository { return null; } - Future> findAllCorrespondents([Iterable? ids]) async { + Future> findAllCorrespondents( + [Iterable? ids]) async { debugPrint("Loading correspondents..."); final correspondents = await _api.getCorrespondents(ids); debugPrint("${correspondents.length} correspondents successfully loaded."); final updatedState = { ...state.correspondents, }..addAll({for (var element in correspondents) element.id!: element}); - debugPrint("Pushing new correspondents state."); emit(state.copyWith(correspondents: updatedState)); - debugPrint("New correspondents state pushed."); return correspondents; } Future updateCorrespondent(Correspondent correspondent) async { final updated = await _api.updateCorrespondent(correspondent); - final updatedState = {...state.correspondents}..update(updated.id!, (_) => updated); + final updatedState = {...state.correspondents} + ..update(updated.id!, (_) => updated); emit(state.copyWith(correspondents: updatedState)); return updated; @@ -109,14 +117,16 @@ class LabelRepository extends PersistentRepository { Future createDocumentType(DocumentType documentType) async { final created = await _api.saveDocumentType(documentType); - final updatedState = {...state.documentTypes}..putIfAbsent(created.id!, () => created); + final updatedState = {...state.documentTypes} + ..putIfAbsent(created.id!, () => created); emit(state.copyWith(documentTypes: updatedState)); return created; } Future deleteDocumentType(DocumentType documentType) async { await _api.deleteDocumentType(documentType); - final updatedState = {...state.documentTypes}..removeWhere((k, v) => k == documentType.id); + final updatedState = {...state.documentTypes} + ..removeWhere((k, v) => k == documentType.id); emit(state.copyWith(documentTypes: updatedState)); return documentType.id!; } @@ -131,7 +141,8 @@ class LabelRepository extends PersistentRepository { return null; } - Future> findAllDocumentTypes([Iterable? ids]) async { + Future> findAllDocumentTypes( + [Iterable? ids]) async { final documentTypes = await _api.getDocumentTypes(ids); final updatedState = {...state.documentTypes} ..addEntries(documentTypes.map((e) => MapEntry(e.id!, e))); @@ -141,21 +152,24 @@ class LabelRepository extends PersistentRepository { Future updateDocumentType(DocumentType documentType) async { final updated = await _api.updateDocumentType(documentType); - final updatedState = {...state.documentTypes}..update(updated.id!, (_) => updated); + final updatedState = {...state.documentTypes} + ..update(updated.id!, (_) => updated); emit(state.copyWith(documentTypes: updatedState)); return updated; } Future createStoragePath(StoragePath storagePath) async { final created = await _api.saveStoragePath(storagePath); - final updatedState = {...state.storagePaths}..putIfAbsent(created.id!, () => created); + final updatedState = {...state.storagePaths} + ..putIfAbsent(created.id!, () => created); emit(state.copyWith(storagePaths: updatedState)); return created; } Future deleteStoragePath(StoragePath storagePath) async { await _api.deleteStoragePath(storagePath); - final updatedState = {...state.storagePaths}..removeWhere((k, v) => k == storagePath.id); + final updatedState = {...state.storagePaths} + ..removeWhere((k, v) => k == storagePath.id); emit(state.copyWith(storagePaths: updatedState)); return storagePath.id!; } @@ -170,7 +184,8 @@ class LabelRepository extends PersistentRepository { return null; } - Future> findAllStoragePaths([Iterable? ids]) async { + Future> findAllStoragePaths( + [Iterable? ids]) async { final storagePaths = await _api.getStoragePaths(ids); final updatedState = {...state.storagePaths} ..addEntries(storagePaths.map((e) => MapEntry(e.id!, e))); @@ -180,7 +195,8 @@ class LabelRepository extends PersistentRepository { Future updateStoragePath(StoragePath storagePath) async { final updated = await _api.updateStoragePath(storagePath); - final updatedState = {...state.storagePaths}..update(updated.id!, (_) => updated); + final updatedState = {...state.storagePaths} + ..update(updated.id!, (_) => updated); emit(state.copyWith(storagePaths: updatedState)); return updated; } diff --git a/lib/features/home/view/home_page.dart b/lib/features/home/view/home_page.dart index 819d2b6..40aa42b 100644 --- a/lib/features/home/view/home_page.dart +++ b/lib/features/home/view/home_page.dart @@ -233,10 +233,28 @@ class _HomePageState extends State with WidgetsBindingObserver { BlocListener( // If app was started offline, load data once it comes back online. listenWhen: (previous, current) => + previous != ConnectivityState.connected && current == ConnectivityState.connected, - listener: (context, state) { - context.read().initialize(); - context.read().initialize(); + listener: (context, state) async { + try { + debugPrint( + "[HomePage] BlocListener#listener: " + "Loading saved views and labels...", + ); + await Future.wait([ + context.read().initialize(), + context.read().initialize(), + ]); + debugPrint("[HomePage] BlocListener#listener: " + "Saved views and labels successfully loaded."); + } catch (error, stackTrace) { + debugPrint( + '[HomePage] BlocListener.listener: ' + 'An error occurred while loading saved views and labels.\n' + '${error.toString()}', + ); + debugPrintStack(stackTrace: stackTrace); + } }, ), BlocListener( diff --git a/lib/features/labels/view/pages/labels_page.dart b/lib/features/labels/view/pages/labels_page.dart index 29d1d97..4ebcf2f 100644 --- a/lib/features/labels/view/pages/labels_page.dart +++ b/lib/features/labels/view/pages/labels_page.dart @@ -29,9 +29,12 @@ class LabelsPage extends StatefulWidget { State createState() => _LabelsPageState(); } -class _LabelsPageState extends State with SingleTickerProviderStateMixin { - final SliverOverlapAbsorberHandle searchBarHandle = SliverOverlapAbsorberHandle(); - final SliverOverlapAbsorberHandle tabBarHandle = SliverOverlapAbsorberHandle(); +class _LabelsPageState extends State + with SingleTickerProviderStateMixin { + final SliverOverlapAbsorberHandle searchBarHandle = + SliverOverlapAbsorberHandle(); + final SliverOverlapAbsorberHandle tabBarHandle = + SliverOverlapAbsorberHandle(); late final TabController _tabController; int _currentIndex = 0; @@ -81,25 +84,33 @@ class _LabelsPageState extends State with SingleTickerProviderStateM Tab( icon: Icon( Icons.person_outline, - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, ), ), Tab( icon: Icon( Icons.description_outlined, - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, ), ), Tab( icon: Icon( Icons.label_outline, - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, ), ), Tab( icon: Icon( Icons.folder_open, - color: Theme.of(context).colorScheme.onPrimaryContainer, + color: Theme.of(context) + .colorScheme + .onPrimaryContainer, ), ), ], @@ -118,25 +129,44 @@ class _LabelsPageState extends State with SingleTickerProviderStateM if (metrics.maxScrollExtent == 0) { return true; } - final desiredTab = ((metrics.pixels / metrics.maxScrollExtent) * - (_tabController.length - 1)) - .round(); + final desiredTab = + ((metrics.pixels / metrics.maxScrollExtent) * + (_tabController.length - 1)) + .round(); - if (metrics.axis == Axis.horizontal && _currentIndex != desiredTab) { + if (metrics.axis == Axis.horizontal && + _currentIndex != desiredTab) { setState(() => _currentIndex = desiredTab); } return true; }, child: RefreshIndicator( edgeOffset: kTextTabBarHeight, - notificationPredicate: (notification) => connectedState.isConnected, - onRefresh: () => [ - context.read().reloadCorrespondents, - context.read().reloadDocumentTypes, - context.read().reloadTags, - context.read().reloadStoragePaths, - ][_currentIndex] - .call(), + notificationPredicate: (notification) => + connectedState.isConnected, + onRefresh: () async { + try { + await [ + context.read().reloadCorrespondents, + context.read().reloadDocumentTypes, + context.read().reloadTags, + context.read().reloadStoragePaths, + ][_currentIndex] + .call(); + } catch (error, stackTrace) { + debugPrint( + "[LabelsPage] RefreshIndicator.onRefresh " + "${[ + "correspondents", + "document types", + "tags", + "storage paths" + ][_currentIndex]}: " + "An error occurred (${error.toString()})", + ); + debugPrintStack(stackTrace: stackTrace); + } + }, child: TabBarView( controller: _tabController, children: [ @@ -144,22 +174,29 @@ class _LabelsPageState extends State with SingleTickerProviderStateM builder: (context) { return CustomScrollView( slivers: [ - SliverOverlapInjector(handle: searchBarHandle), + SliverOverlapInjector( + handle: searchBarHandle), SliverOverlapInjector(handle: tabBarHandle), LabelTabView( labels: state.correspondents, filterBuilder: (label) => DocumentFilter( - correspondent: IdQueryParameter.fromId(label.id!), + correspondent: + IdQueryParameter.fromId(label.id!), ), - canEdit: LocalUserAccount.current.paperlessUser.hasPermission( - PermissionAction.change, PermissionTarget.correspondent), - canAddNew: LocalUserAccount.current.paperlessUser + canEdit: LocalUserAccount + .current.paperlessUser .hasPermission( - PermissionAction.add, PermissionTarget.correspondent), + PermissionAction.change, + PermissionTarget.correspondent), + canAddNew: LocalUserAccount + .current.paperlessUser + .hasPermission(PermissionAction.add, + PermissionTarget.correspondent), onEdit: _openEditCorrespondentPage, emptyStateActionButtonLabel: S.of(context)!.addNewCorrespondent, - emptyStateDescription: S.of(context)!.noCorrespondentsSetUp, + emptyStateDescription: + S.of(context)!.noCorrespondentsSetUp, onAddNew: _openAddCorrespondentPage, ), ], @@ -170,22 +207,29 @@ class _LabelsPageState extends State with SingleTickerProviderStateM builder: (context) { return CustomScrollView( slivers: [ - SliverOverlapInjector(handle: searchBarHandle), + SliverOverlapInjector( + handle: searchBarHandle), SliverOverlapInjector(handle: tabBarHandle), LabelTabView( labels: state.documentTypes, filterBuilder: (label) => DocumentFilter( - documentType: IdQueryParameter.fromId(label.id!), + documentType: + IdQueryParameter.fromId(label.id!), ), - canEdit: LocalUserAccount.current.paperlessUser.hasPermission( - PermissionAction.change, PermissionTarget.documentType), - canAddNew: LocalUserAccount.current.paperlessUser + canEdit: LocalUserAccount + .current.paperlessUser .hasPermission( - PermissionAction.add, PermissionTarget.documentType), + PermissionAction.change, + PermissionTarget.documentType), + canAddNew: LocalUserAccount + .current.paperlessUser + .hasPermission(PermissionAction.add, + PermissionTarget.documentType), onEdit: _openEditDocumentTypePage, emptyStateActionButtonLabel: S.of(context)!.addNewDocumentType, - emptyStateDescription: S.of(context)!.noDocumentTypesSetUp, + emptyStateDescription: + S.of(context)!.noDocumentTypesSetUp, onAddNew: _openAddDocumentTypePage, ), ], @@ -196,18 +240,24 @@ class _LabelsPageState extends State with SingleTickerProviderStateM builder: (context) { return CustomScrollView( slivers: [ - SliverOverlapInjector(handle: searchBarHandle), + SliverOverlapInjector( + handle: searchBarHandle), SliverOverlapInjector(handle: tabBarHandle), LabelTabView( labels: state.tags, filterBuilder: (label) => DocumentFilter( - tags: TagsQuery.ids(include: [label.id!]), + tags: + TagsQuery.ids(include: [label.id!]), ), - canEdit: LocalUserAccount.current.paperlessUser.hasPermission( - PermissionAction.change, PermissionTarget.tag), - canAddNew: LocalUserAccount.current.paperlessUser + canEdit: LocalUserAccount + .current.paperlessUser .hasPermission( - PermissionAction.add, PermissionTarget.tag), + PermissionAction.change, + PermissionTarget.tag), + canAddNew: LocalUserAccount + .current.paperlessUser + .hasPermission(PermissionAction.add, + PermissionTarget.tag), onEdit: _openEditTagPage, leadingBuilder: (t) => CircleAvatar( backgroundColor: t.color, @@ -218,8 +268,10 @@ class _LabelsPageState extends State with SingleTickerProviderStateM ) : null, ), - emptyStateActionButtonLabel: S.of(context)!.addNewTag, - emptyStateDescription: S.of(context)!.noTagsSetUp, + emptyStateActionButtonLabel: + S.of(context)!.addNewTag, + emptyStateDescription: + S.of(context)!.noTagsSetUp, onAddNew: _openAddTagPage, ), ], @@ -230,22 +282,30 @@ class _LabelsPageState extends State with SingleTickerProviderStateM builder: (context) { return CustomScrollView( slivers: [ - SliverOverlapInjector(handle: searchBarHandle), + SliverOverlapInjector( + handle: searchBarHandle), SliverOverlapInjector(handle: tabBarHandle), LabelTabView( labels: state.storagePaths, onEdit: _openEditStoragePathPage, filterBuilder: (label) => DocumentFilter( - storagePath: IdQueryParameter.fromId(label.id!), + storagePath: + IdQueryParameter.fromId(label.id!), ), - canEdit: LocalUserAccount.current.paperlessUser.hasPermission( - PermissionAction.change, PermissionTarget.storagePath), - canAddNew: LocalUserAccount.current.paperlessUser + canEdit: LocalUserAccount + .current.paperlessUser .hasPermission( - PermissionAction.add, PermissionTarget.storagePath), + PermissionAction.change, + PermissionTarget.storagePath), + canAddNew: LocalUserAccount + .current.paperlessUser + .hasPermission(PermissionAction.add, + PermissionTarget.storagePath), contentBuilder: (path) => Text(path.path), - emptyStateActionButtonLabel: S.of(context)!.addNewStoragePath, - emptyStateDescription: S.of(context)!.noStoragePathsSetUp, + emptyStateActionButtonLabel: + S.of(context)!.addNewStoragePath, + emptyStateDescription: + S.of(context)!.noStoragePathsSetUp, onAddNew: _openAddStoragePathPage, ), ], @@ -326,13 +386,13 @@ class _LabelsPageState extends State with SingleTickerProviderStateM MaterialPageRoute _buildLabelPageRoute(Widget page) { return MaterialPageRoute( - builder: (_) => MultiProvider( - providers: [ - Provider.value(value: context.read()), - Provider.value(value: context.read()) - ], - child: page - ) + builder: (_) => MultiProvider( + providers: [ + Provider.value(value: context.read()), + Provider.value(value: context.read()) + ], + child: page, + ), ); } } diff --git a/packages/paperless_api/lib/src/models/document_filter.dart b/packages/paperless_api/lib/src/models/document_filter.dart index be89909..6da3db9 100644 --- a/packages/paperless_api/lib/src/models/document_filter.dart +++ b/packages/paperless_api/lib/src/models/document_filter.dart @@ -115,7 +115,9 @@ class DocumentFilter extends Equatable { final queryParams = groupBy(params, (e) => e.key).map( (key, entries) => MapEntry( key, - entries.length == 1 ? entries.first.value : entries.map((e) => e.value).join(","), + entries.length == 1 + ? entries.first.value + : entries.map((e) => e.value).join(","), ), ); return queryParams; @@ -156,7 +158,8 @@ class DocumentFilter extends Equatable { modified: modified ?? this.modified, moreLike: moreLike != null ? moreLike.call() : this.moreLike, ); - if (query?.queryType != QueryType.extended && newFilter.forceExtendedQuery) { + if (query?.queryType != QueryType.extended && + newFilter.forceExtendedQuery) { //Prevents infinite recursion return newFilter.copyWith( query: newFilter.query.copyWith(queryType: QueryType.extended), @@ -212,7 +215,8 @@ class DocumentFilter extends Equatable { query, ]; - factory DocumentFilter.fromJson(Map json) => _$DocumentFilterFromJson(json); + factory DocumentFilter.fromJson(Map json) => + _$DocumentFilterFromJson(json); Map toJson() => _$DocumentFilterToJson(this); }