From b79375cbe0088e891f8bff1795f2793f4af5eddc Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Tue, 1 Aug 2023 19:49:09 +0200 Subject: [PATCH] feat: Add saved views to landing page --- .../documents/view/pages/documents_page.dart | 20 ++--- .../widgets/items/document_grid_item.dart | 52 +++++++----- .../widgets/items/document_list_item.dart | 3 + lib/features/home/view/home_shell_widget.dart | 1 + lib/features/landing/view/landing_page.dart | 23 +++++ .../landing/view/widgets/expansion_card.dart | 4 +- .../cubit/saved_view_details_cubit.dart | 8 +- .../cubit/saved_view_preview_cubit.dart | 28 +++++++ .../cubit/saved_view_preview_state.dart | 11 +++ .../view/saved_view_details_preview.dart | 84 +++++++++++++++++++ 10 files changed, 198 insertions(+), 36 deletions(-) create mode 100644 lib/features/saved_view_details/cubit/saved_view_preview_cubit.dart create mode 100644 lib/features/saved_view_details/cubit/saved_view_preview_state.dart create mode 100644 lib/features/saved_view_details/view/saved_view_details_preview.dart diff --git a/lib/features/documents/view/pages/documents_page.dart b/lib/features/documents/view/pages/documents_page.dart index f06cdc8..6db35d8 100644 --- a/lib/features/documents/view/pages/documents_page.dart +++ b/lib/features/documents/view/pages/documents_page.dart @@ -61,15 +61,15 @@ class _DocumentsPageState extends State length: showSavedViews ? 2 : 1, vsync: this, ); - Future.wait([ - context.read().reload(), - context.read().reload(), - ]).onError( - (error, stackTrace) { - showErrorMessage(context, error, stackTrace); - return []; - }, - ); + // Future.wait([ + // context.read().reload(), + // context.read().reload(), + // ]).onError( + // (error, stackTrace) { + // showErrorMessage(context, error, stackTrace); + // return []; + // }, + // ); _tabController.addListener(_tabChangesListener); } @@ -117,7 +117,7 @@ class _DocumentsPageState extends State return SafeArea( top: true, child: Scaffold( - drawer: AppDrawer(), + drawer: const AppDrawer(), floatingActionButton: BlocBuilder( builder: (context, state) { final appliedFiltersCount = state.filter.appliedFiltersCount; diff --git a/lib/features/documents/view/widgets/items/document_grid_item.dart b/lib/features/documents/view/widgets/items/document_grid_item.dart index 9333610..3dcc917 100644 --- a/lib/features/documents/view/widgets/items/document_grid_item.dart +++ b/lib/features/documents/view/widgets/items/document_grid_item.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/core/database/tables/local_user_account.dart'; import 'package:paperless_mobile/core/repository/label_repository.dart'; import 'package:paperless_mobile/features/documents/view/widgets/document_preview.dart'; import 'package:paperless_mobile/features/documents/view/widgets/items/document_item.dart'; @@ -26,6 +28,7 @@ class DocumentGridItem extends DocumentItem { @override Widget build(BuildContext context) { + var currentUser = context.watch().paperlessUser; return Padding( padding: const EdgeInsets.all(8.0), child: Card( @@ -64,15 +67,16 @@ class DocumentGridItem extends DocumentItem { const SliverToBoxAdapter( child: SizedBox(width: 8), ), - TagsWidget.sliver( - tags: document.tags - .map((e) => context - .watch() - .state - .tags[e]!) - .toList(), - onTagSelected: onTagSelected, - ), + if (currentUser.canViewTags) + TagsWidget.sliver( + tags: document.tags + .map((e) => context + .watch() + .state + .tags[e]!) + .toList(), + onTagSelected: onTagSelected, + ), const SliverToBoxAdapter( child: SizedBox(width: 8), ), @@ -90,20 +94,22 @@ class DocumentGridItem extends DocumentItem { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - CorrespondentWidget( - correspondent: context - .watch() - .state - .correspondents[document.correspondent], - onSelected: onCorrespondentSelected, - ), - DocumentTypeWidget( - documentType: context - .watch() - .state - .documentTypes[document.documentType], - onSelected: onDocumentTypeSelected, - ), + if (currentUser.canViewCorrespondents) + CorrespondentWidget( + correspondent: context + .watch() + .state + .correspondents[document.correspondent], + onSelected: onCorrespondentSelected, + ), + if (currentUser.canViewDocumentTypes) + DocumentTypeWidget( + documentType: context + .watch() + .state + .documentTypes[document.documentType], + onSelected: onDocumentTypeSelected, + ), Padding( padding: const EdgeInsets.only(bottom: 8.0), child: Text( diff --git a/lib/features/documents/view/widgets/items/document_list_item.dart b/lib/features/documents/view/widgets/items/document_list_item.dart index 1736eaf..1f06605 100644 --- a/lib/features/documents/view/widgets/items/document_list_item.dart +++ b/lib/features/documents/view/widgets/items/document_list_item.dart @@ -11,8 +11,10 @@ import 'package:provider/provider.dart'; class DocumentListItem extends DocumentItem { static const _a4AspectRatio = 1 / 1.4142; + final Color? backgroundColor; const DocumentListItem({ super.key, + this.backgroundColor, required super.document, required super.isSelected, required super.isSelectionActive, @@ -31,6 +33,7 @@ class DocumentListItem extends DocumentItem { final labels = context.watch().state; return Material( child: ListTile( + tileColor: backgroundColor, dense: true, selected: isSelected, onTap: () => _onTap(), diff --git a/lib/features/home/view/home_shell_widget.dart b/lib/features/home/view/home_shell_widget.dart index f0e0785..de636d2 100644 --- a/lib/features/home/view/home_shell_widget.dart +++ b/lib/features/home/view/home_shell_widget.dart @@ -158,6 +158,7 @@ class HomeShellWidget extends StatelessWidget { return MultiProvider( providers: [ Provider( + lazy: false, create: (context) => DocumentsCubit( context.read(), context.read(), diff --git a/lib/features/landing/view/landing_page.dart b/lib/features/landing/view/landing_page.dart index 01a7027..c852182 100644 --- a/lib/features/landing/view/landing_page.dart +++ b/lib/features/landing/view/landing_page.dart @@ -8,6 +8,8 @@ import 'package:paperless_mobile/features/app_drawer/view/app_drawer.dart'; import 'package:paperless_mobile/features/document_search/view/sliver_search_bar.dart'; import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart'; import 'package:paperless_mobile/features/landing/view/widgets/mime_types_pie_chart.dart'; +import 'package:paperless_mobile/features/saved_view/cubit/saved_view_cubit.dart'; +import 'package:paperless_mobile/features/saved_view_details/view/saved_view_details_preview.dart'; import 'package:paperless_mobile/generated/l10n/app_localizations.dart'; import 'package:paperless_mobile/routes/routes.dart'; import 'package:paperless_mobile/routes/typed/branches/documents_route.dart'; @@ -52,6 +54,27 @@ class _LandingPageState extends State { ).padded(24), ), SliverToBoxAdapter(child: _buildStatisticsCard(context)), + BlocBuilder( + builder: (context, state) { + return state.maybeWhen( + loaded: (savedViews) { + return SliverList.builder( + itemBuilder: (context, index) { + return SavedViewDetailsPreview( + savedView: savedViews.values.elementAt(index), + ); + }, + itemCount: savedViews.length, + ); + }, + orElse: () => const SliverToBoxAdapter( + child: Center( + child: CircularProgressIndicator(), + ), + ), + ); + }, + ) ], ), ), diff --git a/lib/features/landing/view/widgets/expansion_card.dart b/lib/features/landing/view/widgets/expansion_card.dart index 15bb006..c90f896 100644 --- a/lib/features/landing/view/widgets/expansion_card.dart +++ b/lib/features/landing/view/widgets/expansion_card.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:paperless_mobile/extensions/flutter_extensions.dart'; class ExpansionCard extends StatelessWidget { final Widget title; @@ -10,7 +9,7 @@ class ExpansionCard extends StatelessWidget { @override Widget build(BuildContext context) { return Card( - margin: EdgeInsets.all(16), + margin: const EdgeInsets.all(16), child: Theme( data: Theme.of(context).copyWith( dividerColor: Colors.transparent, @@ -23,6 +22,7 @@ class ExpansionCard extends StatelessWidget { ), ), child: ExpansionTile( + backgroundColor: Theme.of(context).colorScheme.surface, initiallyExpanded: true, title: title, children: [content], diff --git a/lib/features/saved_view_details/cubit/saved_view_details_cubit.dart b/lib/features/saved_view_details/cubit/saved_view_details_cubit.dart index 4109f7b..e965211 100644 --- a/lib/features/saved_view_details/cubit/saved_view_details_cubit.dart +++ b/lib/features/saved_view_details/cubit/saved_view_details_cubit.dart @@ -29,6 +29,7 @@ class SavedViewDetailsCubit extends Cubit this._labelRepository, this._userState, { required this.savedView, + int initialCount = 25, }) : super( SavedViewDetailsState( correspondents: _labelRepository.state.correspondents, @@ -56,7 +57,12 @@ class SavedViewDetailsCubit extends Cubit } }, ); - updateFilter(filter: savedView.toDocumentFilter()); + updateFilter( + filter: savedView.toDocumentFilter().copyWith( + page: 1, + pageSize: initialCount, + ), + ); } void setViewType(ViewType viewType) { diff --git a/lib/features/saved_view_details/cubit/saved_view_preview_cubit.dart b/lib/features/saved_view_details/cubit/saved_view_preview_cubit.dart new file mode 100644 index 0000000..19658f7 --- /dev/null +++ b/lib/features/saved_view_details/cubit/saved_view_preview_cubit.dart @@ -0,0 +1,28 @@ +import 'package:bloc/bloc.dart'; +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:paperless_api/paperless_api.dart'; + +part 'saved_view_preview_state.dart'; +part 'saved_view_preview_cubit.freezed.dart'; + +class SavedViewPreviewCubit extends Cubit { + final PaperlessDocumentsApi _api; + final SavedView view; + SavedViewPreviewCubit(this._api, this.view) + : super(const SavedViewPreviewState.initial()); + + Future initialize() async { + emit(const SavedViewPreviewState.loading()); + try { + final documents = await _api.findAll( + view.toDocumentFilter().copyWith( + page: 1, + pageSize: 5, + ), + ); + emit(SavedViewPreviewState.loaded(documents: documents.results)); + } catch (e) { + emit(const SavedViewPreviewState.error()); + } + } +} diff --git a/lib/features/saved_view_details/cubit/saved_view_preview_state.dart b/lib/features/saved_view_details/cubit/saved_view_preview_state.dart new file mode 100644 index 0000000..49e4995 --- /dev/null +++ b/lib/features/saved_view_details/cubit/saved_view_preview_state.dart @@ -0,0 +1,11 @@ +part of 'saved_view_preview_cubit.dart'; + +@freezed +class SavedViewPreviewState with _$SavedViewPreviewState { + const factory SavedViewPreviewState.initial() = _Initial; + const factory SavedViewPreviewState.loading() = _Loading; + const factory SavedViewPreviewState.loaded({ + required List documents, + }) = _Loaded; + const factory SavedViewPreviewState.error() = _Error; +} diff --git a/lib/features/saved_view_details/view/saved_view_details_preview.dart b/lib/features/saved_view_details/view/saved_view_details_preview.dart new file mode 100644 index 0000000..5e33b9b --- /dev/null +++ b/lib/features/saved_view_details/view/saved_view_details_preview.dart @@ -0,0 +1,84 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:paperless_api/paperless_api.dart'; +import 'package:paperless_mobile/features/documents/cubit/documents_cubit.dart'; +import 'package:paperless_mobile/features/documents/view/widgets/adaptive_documents_view.dart'; +import 'package:paperless_mobile/features/documents/view/widgets/items/document_list_item.dart'; +import 'package:paperless_mobile/features/landing/view/widgets/expansion_card.dart'; +import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_details_cubit.dart'; +import 'package:paperless_mobile/features/saved_view_details/cubit/saved_view_preview_cubit.dart'; +import 'package:paperless_mobile/routes/typed/branches/documents_route.dart'; +import 'package:provider/provider.dart'; + +class SavedViewDetailsPreview extends StatelessWidget { + final SavedView savedView; + const SavedViewDetailsPreview({ + super.key, + required this.savedView, + }); + + @override + Widget build(BuildContext context) { + return Provider( + create: (context) => + SavedViewPreviewCubit(context.read(), savedView)..initialize(), + builder: (context, child) { + return ExpansionCard( + title: Text(savedView.name), + content: BlocBuilder( + builder: (context, state) { + return Column( + children: [ + state.maybeWhen( + loaded: (documents) { + return Column( + children: [ + for (final document in documents) + DocumentListItem( + document: document, + isLabelClickable: false, + isSelected: false, + isSelectionActive: false, + onTap: (document) { + DocumentDetailsRoute($extra: document) + .push(context); + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + TextButton( + child: Text("Show more"), + onPressed: documents.length >= 5 ? () {} : null, + ), + TextButton.icon( + icon: Icon(Icons.open_in_new), + label: Text("Show in documents"), + onPressed: () { + context.read().updateFilter( + filter: savedView.toDocumentFilter(), + ); + DocumentsRoute().go(context); + }, + ), + ], + ), + ], + ); + }, + error: () => + const Text("Error loading preview"), //TODO: INTL + orElse: () => const Padding( + padding: EdgeInsets.all(8.0), + child: Center(child: CircularProgressIndicator()), + ), + ), + ], + ); + }, + ), + ); + }, + ); + } +}