import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:paperless_mobile/core/bloc/connectivity_cubit.dart'; import 'package:paperless_mobile/core/model/error_message.dart'; import 'package:paperless_mobile/di_initializer.dart'; import 'package:paperless_mobile/features/document_details/bloc/document_details_cubit.dart'; import 'package:paperless_mobile/features/document_details/view/pages/document_details_page.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_cubit.dart'; import 'package:paperless_mobile/features/documents/bloc/documents_state.dart'; import 'package:paperless_mobile/features/documents/model/document.model.dart'; import 'package:paperless_mobile/features/documents/model/query_parameters/tags_query.dart'; import 'package:paperless_mobile/features/documents/repository/document_repository.dart'; import 'package:paperless_mobile/features/documents/view/widgets/documents_empty_state.dart'; import 'package:paperless_mobile/features/documents/view/widgets/grid/document_grid.dart'; import 'package:paperless_mobile/features/documents/view/widgets/list/document_list.dart'; import 'package:paperless_mobile/features/documents/view/widgets/search/document_filter_panel.dart'; import 'package:paperless_mobile/features/documents/view/widgets/selection/documents_page_app_bar.dart'; import 'package:paperless_mobile/features/documents/view/widgets/sort_documents_button.dart'; import 'package:paperless_mobile/features/home/view/widget/info_drawer.dart'; import 'package:paperless_mobile/features/labels/correspondent/bloc/correspondents_cubit.dart'; import 'package:paperless_mobile/features/labels/document_type/bloc/document_type_cubit.dart'; import 'package:paperless_mobile/features/labels/storage_path/bloc/storage_path_cubit.dart'; import 'package:paperless_mobile/features/labels/tags/bloc/tags_cubit.dart'; import 'package:paperless_mobile/features/login/bloc/authentication_cubit.dart'; import 'package:paperless_mobile/features/settings/bloc/application_settings_cubit.dart'; import 'package:paperless_mobile/features/settings/model/application_settings_state.dart'; import 'package:paperless_mobile/features/settings/model/view_type.dart'; import 'package:paperless_mobile/util.dart'; import 'package:sliding_up_panel/sliding_up_panel.dart'; class DocumentsPage extends StatefulWidget { const DocumentsPage({Key? key}) : super(key: key); @override State createState() => _DocumentsPageState(); } class _DocumentsPageState extends State { final _pagingController = PagingController( firstPageKey: 1, ); final _filterPanelController = PanelController(); @override void initState() { super.initState(); try { BlocProvider.of(context).load(); } on ErrorMessage catch (error, stackTrace) { showErrorMessage(context, error, stackTrace); } _pagingController.addPageRequestListener(_loadNewPage); } @override void dispose() { _pagingController.dispose(); super.dispose(); } Future _loadNewPage(int pageKey) async { final documentsCubit = BlocProvider.of(context); final pageCount = documentsCubit.state .inferPageCount(pageSize: documentsCubit.state.filter.pageSize); if (pageCount <= pageKey + 1) { _pagingController.nextPageKey = null; } try { await documentsCubit.loadMore(); } on ErrorMessage catch (error, stackTrace) { showErrorMessage(context, error, stackTrace); } } void _onSelected(DocumentModel model) { BlocProvider.of(context).toggleDocumentSelection(model); } Future _onRefresh() async { try { await BlocProvider.of(context).updateCurrentFilter( (filter) => filter.copyWith(page: 1), ); } on ErrorMessage catch (error, stackTrace) { showErrorMessage(context, error, stackTrace); } } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { if (_filterPanelController.isPanelOpen) { FocusScope.of(context).unfocus(); _filterPanelController.close(); return false; } final documentsCubit = BlocProvider.of(context); if (documentsCubit.state.selection.isNotEmpty) { documentsCubit.resetSelection(); return false; } return true; }, child: BlocConsumer( listenWhen: (previous, current) => previous != ConnectivityState.connected && current == ConnectivityState.connected, listener: (context, state) { BlocProvider.of(context).load(); }, builder: (context, connectivityState) { return Scaffold( drawer: BlocProvider.value( value: BlocProvider.of(context), child: InfoDrawer( afterInboxClosed: () => BlocProvider.of(context).reload(), ), ), resizeToAvoidBottomInset: true, body: SlidingUpPanel( backdropEnabled: true, parallaxEnabled: true, parallaxOffset: .5, controller: _filterPanelController, defaultPanelState: PanelState.CLOSED, minHeight: 48, maxHeight: (MediaQuery.of(context).size.height * 3) / 4, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), ), body: _buildBody(connectivityState), color: Theme.of(context).scaffoldBackgroundColor, panelBuilder: (scrollController) => DocumentFilterPanel( panelController: _filterPanelController, scrollController: scrollController, ), ), ); }, ), ); } Widget _buildBody(ConnectivityState connectivityState) { return BlocBuilder( builder: (context, settings) { return BlocBuilder( builder: (context, state) { // Some ugly tricks to make it work with bloc, update pageController _pagingController.value = PagingState( itemList: state.documents, nextPageKey: state.nextPageNumber, ); late Widget child; switch (settings.preferredViewType) { case ViewType.list: child = DocumentListView( onTap: _openDetails, state: state, onSelected: _onSelected, pagingController: _pagingController, hasInternetConnection: connectivityState == ConnectivityState.connected, onTagSelected: _addTagToFilter, ); break; case ViewType.grid: child = DocumentGridView( onTap: _openDetails, state: state, onSelected: _onSelected, pagingController: _pagingController, hasInternetConnection: connectivityState == ConnectivityState.connected, onTagSelected: (int tagId) => _addTagToFilter, ); break; } if (state.isLoaded && state.documents.isEmpty) { child = SliverToBoxAdapter( child: DocumentsEmptyState( state: state, ), ); } return RefreshIndicator( onRefresh: _onRefresh, child: CustomScrollView( slivers: [ DocumentsPageAppBar( actions: [ const SortDocumentsButton(), IconButton( icon: Icon( settings.preferredViewType == ViewType.grid ? Icons.list : Icons.grid_view, ), onPressed: () => BlocProvider.of(context) .setViewType( settings.preferredViewType.toggle()), ), ], ), child, SliverToBoxAdapter( child: SizedBox( height: MediaQuery.of(context).size.height / 4, ), ) ], ), ); }, ); }, ); } Future _openDetails(DocumentModel document) async { await Navigator.of(context).push( _buildDetailsPageRoute(document), ); BlocProvider.of(context).reload(); } MaterialPageRoute _buildDetailsPageRoute( DocumentModel document) { return MaterialPageRoute( builder: (_) => MultiBlocProvider( providers: [ BlocProvider.value( value: BlocProvider.of(context), ), BlocProvider.value( value: BlocProvider.of(context), ), BlocProvider.value( value: BlocProvider.of(context), ), BlocProvider.value( value: BlocProvider.of(context), ), BlocProvider.value( value: BlocProvider.of(context), ), BlocProvider.value( value: DocumentDetailsCubit(getIt(), document), ), ], child: const DocumentDetailsPage(), ), ); } void _addTagToFilter(int tagId) { final cubit = BlocProvider.of(context); try { final tagsQuery = cubit.state.filter.tags is IdsTagsQuery ? cubit.state.filter.tags as IdsTagsQuery : const IdsTagsQuery(); if (tagsQuery.includedIds.contains(tagId)) { cubit.updateCurrentFilter( (filter) => filter.copyWith( tags: tagsQuery.withIdsRemoved([tagId]), ), ); } else { cubit.updateCurrentFilter( (filter) => filter.copyWith( tags: tagsQuery.withIdQueriesAdded([IncludeTagIdQuery(tagId)]), ), ); } } on ErrorMessage catch (error, stackTrace) { showErrorMessage(context, error, stackTrace); } } }