diff --git a/lib/features/document_search/view/sliver_search_bar.dart b/lib/features/document_search/view/sliver_search_bar.dart index efba880..a72ef43 100644 --- a/lib/features/document_search/view/sliver_search_bar.dart +++ b/lib/features/document_search/view/sliver_search_bar.dart @@ -23,8 +23,8 @@ class SliverSearchBar extends StatelessWidget { floating: floating, pinned: pinned, delegate: CustomizableSliverPersistentHeaderDelegate( - minExtent: kToolbarHeight + 8, - maxExtent: kToolbarHeight + 8, + minExtent: kToolbarHeight, + maxExtent: kToolbarHeight, child: Container( margin: const EdgeInsets.symmetric(horizontal: 8.0), child: SearchBar( diff --git a/lib/features/documents/view/pages/documents_page.dart b/lib/features/documents/view/pages/documents_page.dart index 103c87c..cfb82c6 100644 --- a/lib/features/documents/view/pages/documents_page.dart +++ b/lib/features/documents/view/pages/documents_page.dart @@ -114,31 +114,38 @@ class _DocumentsPageState extends State }, builder: (context, connectivityState) { return SafeArea( + top: context.read().state.selection.isEmpty, child: Scaffold( drawer: const AppDrawer(), floatingActionButton: BlocBuilder( builder: (context, state) { final appliedFiltersCount = state.filter.appliedFiltersCount; - return b.Badge( - position: b.BadgePosition.topEnd(top: -12, end: -6), - showBadge: appliedFiltersCount > 0, - badgeContent: Text( - '$appliedFiltersCount', - style: const TextStyle( - color: Colors.white, + final show = state.selection.isEmpty; + return AnimatedScale( + scale: show ? 1 : 0, + duration: const Duration(milliseconds: 200), + curve: Curves.easeIn, + child: b.Badge( + position: b.BadgePosition.topEnd(top: -12, end: -6), + showBadge: appliedFiltersCount > 0, + badgeContent: Text( + '$appliedFiltersCount', + style: const TextStyle( + color: Colors.white, + ), ), + animationType: b.BadgeAnimationType.fade, + badgeColor: Colors.red, + child: _currentTab == 0 + ? FloatingActionButton( + child: const Icon(Icons.filter_alt_outlined), + onPressed: _openDocumentFilter, + ) + : FloatingActionButton( + child: const Icon(Icons.add), + onPressed: () => _onCreateSavedView(state.filter), + ), ), - animationType: b.BadgeAnimationType.fade, - badgeColor: Colors.red, - child: _currentTab == 0 - ? FloatingActionButton( - child: const Icon(Icons.filter_alt_outlined), - onPressed: _openDocumentFilter, - ) - : FloatingActionButton( - child: const Icon(Icons.add), - onPressed: () => _onCreateSavedView(state.filter), - ), ); }, ), @@ -296,7 +303,7 @@ class _DocumentsPageState extends State _currentTab != 0 || currState.isLoading || currState.isLastPageLoaded) { - return true; + return false; } final offset = notification.metrics.pixels; @@ -311,6 +318,7 @@ class _DocumentsPageState extends State stackTrace, ), ); + return true; } return false; }, @@ -361,46 +369,25 @@ class _DocumentsPageState extends State Widget _buildViewActions() { return SliverToBoxAdapter( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const SortDocumentsButton(), - BlocBuilder( - builder: (context, state) { - return ViewTypeSelectionWidget( + child: BlocBuilder( + builder: (context, state) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SortDocumentsButton( + enabled: state.selection.isEmpty, + ), + ViewTypeSelectionWidget( viewType: state.viewType, onChanged: context.read().setViewType, - ); - }, - ) - ], + ), + ], + ); + }, ).paddedSymmetrically(horizontal: 8, vertical: 4), ); } - void _onDelete(DocumentsState documentsState) async { - final shouldDelete = await showDialog( - context: context, - builder: (context) => - BulkDeleteConfirmationDialog(state: documentsState), - ) ?? - false; - if (shouldDelete) { - try { - await context - .read() - .bulkDelete(documentsState.selection); - showSnackBar( - context, - S.of(context)!.documentsSuccessfullyDeleted, - ); - context.read().resetSelection(); - } on PaperlessServerException catch (error, stackTrace) { - showErrorMessage(context, error, stackTrace); - } - } - } - void _onCreateSavedView(DocumentFilter filter) async { final newView = await Navigator.of(context).push( MaterialPageRoute( diff --git a/lib/features/documents/view/widgets/items/document_detailed_item.dart b/lib/features/documents/view/widgets/items/document_detailed_item.dart index e7ad0e1..e4a5ec8 100644 --- a/lib/features/documents/view/widgets/items/document_detailed_item.dart +++ b/lib/features/documents/view/widgets/items/document_detailed_item.dart @@ -44,6 +44,7 @@ class DocumentDetailedItem extends DocumentItem { ? min(600.0, availableHeight) : min(500.0, availableHeight); return Card( + color: isSelected ? Theme.of(context).colorScheme.inversePrimary : null, child: InkWell( enableFeedback: true, borderRadius: BorderRadius.circular(12), 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 5c1bc8c..16f1d49 100644 --- a/lib/features/documents/view/widgets/items/document_list_item.dart +++ b/lib/features/documents/view/widgets/items/document_list_item.dart @@ -30,106 +30,108 @@ class DocumentListItem extends DocumentItem { @override Widget build(BuildContext context) { return DocumentTypeBlocProvider( - child: ListTile( - dense: true, - selected: isSelected, - onTap: () => _onTap(), - selectedTileColor: Theme.of(context).colorScheme.inversePrimary, - onLongPress: () => onSelected?.call(document), - title: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Row( - children: [ - AbsorbPointer( - absorbing: isSelectionActive, - child: CorrespondentWidget( - isClickable: isLabelClickable, - correspondentId: document.correspondent, - onSelected: onCorrespondentSelected, + child: Material( + child: ListTile( + dense: true, + selected: isSelected, + onTap: () => _onTap(), + selectedTileColor: Theme.of(context).colorScheme.inversePrimary, + onLongPress: () => onSelected?.call(document), + title: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Row( + children: [ + AbsorbPointer( + absorbing: isSelectionActive, + child: CorrespondentWidget( + isClickable: isLabelClickable, + correspondentId: document.correspondent, + onSelected: onCorrespondentSelected, + ), ), - ), - ], - ), - Text( - document.title, - overflow: TextOverflow.ellipsis, - maxLines: 1, - ), - AbsorbPointer( - absorbing: isSelectionActive, - child: TagsWidget( - isClickable: isLabelClickable, - tagIds: document.tags, - isMultiLine: false, - onTagSelected: (id) => onTagSelected?.call(id), + ], + ), + Text( + document.title, + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + AbsorbPointer( + absorbing: isSelectionActive, + child: TagsWidget( + isClickable: isLabelClickable, + tagIds: document.tags, + isMultiLine: false, + onTagSelected: (id) => onTagSelected?.call(id), + ), + ) + ], + ), + subtitle: Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: BlocBuilder, + LabelState>( + builder: (context, docTypes) { + return RichText( + maxLines: 1, + overflow: TextOverflow.ellipsis, + text: TextSpan( + text: DateFormat.yMMMd().format(document.created), + style: Theme.of(context) + .textTheme + .labelSmall + ?.apply(color: Colors.grey), + children: document.documentType != null + ? [ + const TextSpan(text: '\u30FB'), + TextSpan( + text: docTypes + .labels[document.documentType]?.name, + ), + ] + : null, + ), + ); + }, + ) + // Row( + // children: [ + // Text( + // DateFormat.yMMMd().format(document.created), + // style: Theme.of(context) + // .textTheme + // .bodySmall + // ?.apply(color: Colors.grey), + // ), + // if (document.documentType != null) ...[ + // Text("\u30FB"), + // DocumentTypeWidget( + // documentTypeId: document.documentType, + // textStyle: Theme.of(context).textTheme.bodySmall?.apply( + // color: Colors.grey, + // overflow: TextOverflow.ellipsis, + // ), + // ), + // ], + // ], + // ), + ), + isThreeLine: document.tags.isNotEmpty, + leading: AspectRatio( + aspectRatio: _a4AspectRatio, + child: GestureDetector( + child: DocumentPreview( + document: document, + fit: BoxFit.cover, + alignment: Alignment.topCenter, + enableHero: enableHeroAnimation, ), - ) - ], - ), - subtitle: Padding( - padding: const EdgeInsets.symmetric(vertical: 4), - child: - BlocBuilder, LabelState>( - builder: (context, docTypes) { - return RichText( - maxLines: 1, - overflow: TextOverflow.ellipsis, - text: TextSpan( - text: DateFormat.yMMMd().format(document.created), - style: Theme.of(context) - .textTheme - .labelSmall - ?.apply(color: Colors.grey), - children: document.documentType != null - ? [ - const TextSpan(text: '\u30FB'), - TextSpan( - text: - docTypes.labels[document.documentType]?.name, - ), - ] - : null, - ), - ); - }, - ) - // Row( - // children: [ - // Text( - // DateFormat.yMMMd().format(document.created), - // style: Theme.of(context) - // .textTheme - // .bodySmall - // ?.apply(color: Colors.grey), - // ), - // if (document.documentType != null) ...[ - // Text("\u30FB"), - // DocumentTypeWidget( - // documentTypeId: document.documentType, - // textStyle: Theme.of(context).textTheme.bodySmall?.apply( - // color: Colors.grey, - // overflow: TextOverflow.ellipsis, - // ), - // ), - // ], - // ], - // ), - ), - isThreeLine: document.tags.isNotEmpty, - leading: AspectRatio( - aspectRatio: _a4AspectRatio, - child: GestureDetector( - child: DocumentPreview( - document: document, - fit: BoxFit.cover, - alignment: Alignment.topCenter, - enableHero: enableHeroAnimation, ), ), + contentPadding: const EdgeInsets.all(8.0), ), - contentPadding: const EdgeInsets.all(8.0), ), ); } diff --git a/lib/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart b/lib/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart index c1a309c..bdd13f3 100644 --- a/lib/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart +++ b/lib/features/documents/view/widgets/selection/document_selection_sliver_app_bar.dart @@ -19,7 +19,11 @@ class DocumentSelectionSliverAppBar extends StatelessWidget { @override Widget build(BuildContext context) { return SliverAppBar( + stretch: false, pinned: true, + floating: true, + snap: true, + backgroundColor: Theme.of(context).colorScheme.surfaceVariant, title: Text( S.of(context)!.countSelected(state.selection.length), ), diff --git a/lib/features/documents/view/widgets/selection/view_type_selection_widget.dart b/lib/features/documents/view/widgets/selection/view_type_selection_widget.dart index b3b3f36..8289761 100644 --- a/lib/features/documents/view/widgets/selection/view_type_selection_widget.dart +++ b/lib/features/documents/view/widgets/selection/view_type_selection_widget.dart @@ -29,6 +29,10 @@ class ViewTypeSelectionWidget extends StatelessWidget { } return PopupMenuButton( + constraints: const BoxConstraints( + minWidth: 4 * 56.0, + maxWidth: 5 * 56.0, + ), // Ensures text is not split into two lines position: PopupMenuPosition.under, initialValue: viewType, icon: Icon(icon), @@ -70,7 +74,10 @@ class ViewTypeSelectionWidget extends StatelessWidget { child: ListTile( selected: selected, trailing: selected ? const Icon(Icons.done) : null, - title: Text(label), + title: Text( + label, + maxLines: 1, + ), iconColor: Theme.of(context).colorScheme.onSurface, textColor: Theme.of(context).colorScheme.onSurface, leading: Icon(icon), diff --git a/lib/features/documents/view/widgets/sort_documents_button.dart b/lib/features/documents/view/widgets/sort_documents_button.dart index 91ef997..e20631d 100644 --- a/lib/features/documents/view/widgets/sort_documents_button.dart +++ b/lib/features/documents/view/widgets/sort_documents_button.dart @@ -8,8 +8,10 @@ import 'package:paperless_mobile/features/documents/view/widgets/search/sort_fie import 'package:paperless_mobile/features/labels/cubit/label_cubit.dart'; class SortDocumentsButton extends StatelessWidget { + final bool enabled; const SortDocumentsButton({ super.key, + this.enabled = true, }); @override @@ -24,47 +26,50 @@ class SortDocumentsButton extends StatelessWidget { ? Icons.arrow_upward : Icons.arrow_downward), label: Text(translateSortField(context, state.filter.sortField)), - onPressed: () { - showModalBottomSheet( - elevation: 2, - context: context, - isScrollControlled: true, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), - builder: (_) => BlocProvider.value( - value: context.read(), - child: MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => LabelCubit( - context.read>(), + onPressed: enabled + ? () { + showModalBottomSheet( + elevation: 2, + context: context, + isScrollControlled: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), ), ), - BlocProvider( - create: (context) => LabelCubit( - context.read>(), - ), - ), - ], - child: SortFieldSelectionBottomSheet( - initialSortField: state.filter.sortField, - initialSortOrder: state.filter.sortOrder, - onSubmit: (field, order) => - context.read().updateCurrentFilter( - (filter) => filter.copyWith( - sortField: field, - sortOrder: order, - ), + builder: (_) => BlocProvider.value( + value: context.read(), + child: MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => LabelCubit( + context.read>(), ), - ), - ), - ), - ); - }, + ), + BlocProvider( + create: (context) => LabelCubit( + context.read>(), + ), + ), + ], + child: SortFieldSelectionBottomSheet( + initialSortField: state.filter.sortField, + initialSortOrder: state.filter.sortOrder, + onSubmit: (field, order) => context + .read() + .updateCurrentFilter( + (filter) => filter.copyWith( + sortField: field, + sortOrder: order, + ), + ), + ), + ), + ), + ); + } + : null, ); }, );