From 2445c97d44555ff89e1453efa379f83045569119 Mon Sep 17 00:00:00 2001 From: Anton Stubenbord Date: Mon, 2 Jan 2023 23:37:53 +0100 Subject: [PATCH] Implemented m3 full screen bottom sheet for document search --- ...rm_builder_extended_date_range_picker.dart | 8 +- .../documents/view/pages/documents_page.dart | 8 +- .../widgets/search/document_filter_panel.dart | 176 ++++++++++++------ .../widgets/search/text_query_form_field.dart | 32 ++-- 4 files changed, 150 insertions(+), 74 deletions(-) diff --git a/lib/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart b/lib/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart index be39e77..6106764 100644 --- a/lib/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart +++ b/lib/core/widgets/form_builder_fields/extended_date_range_form_field/form_builder_extended_date_range_picker.dart @@ -11,6 +11,7 @@ class FormBuilderExtendedDateRangePicker extends StatefulWidget { final String labelText; final DateRangeQuery initialValue; final void Function(DateRangeQuery? query)? onChanged; + const FormBuilderExtendedDateRangePicker({ super.key, required this.name, @@ -65,7 +66,12 @@ class _FormBuilderExtendedDateRangePickerState : null, ), ), - RelativeDateRangePickerHelper(field: field), + MediaQuery.removePadding( + context: context, + removeLeft: true, + removeRight: true, + child: RelativeDateRangePickerHelper(field: field), + ), ], ); }, diff --git a/lib/features/documents/view/pages/documents_page.dart b/lib/features/documents/view/pages/documents_page.dart index 8ea2df1..2cd466e 100644 --- a/lib/features/documents/view/pages/documents_page.dart +++ b/lib/features/documents/view/pages/documents_page.dart @@ -23,7 +23,6 @@ import 'package:paperless_mobile/features/settings/bloc/application_settings_cub 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:provider/provider.dart'; class DocumentsPage extends StatefulWidget { const DocumentsPage({Key? key}) : super(key: key); @@ -100,7 +99,9 @@ class _DocumentsPageState extends State { } void _openDocumentFilter() async { + final draggableSheetController = DraggableScrollableController(); final filter = await showModalBottomSheet( + useSafeArea: true, context: context, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( @@ -112,14 +113,17 @@ class _DocumentsPageState extends State { builder: (_) => BlocProvider.value( value: context.read(), child: DraggableScrollableSheet( + controller: draggableSheetController, expand: false, snap: true, + snapSizes: const [0.9, 1], initialChildSize: .9, - snapSizes: const [.9, 1], + maxChildSize: 1, builder: (context, controller) => LabelsBlocProvider( child: DocumentFilterPanel( initialFilter: context.read().state.filter, scrollController: controller, + draggableSheetController: draggableSheetController, ), ), ), diff --git a/lib/features/documents/view/widgets/search/document_filter_panel.dart b/lib/features/documents/view/widgets/search/document_filter_panel.dart index 623163c..221031d 100644 --- a/lib/features/documents/view/widgets/search/document_filter_panel.dart +++ b/lib/features/documents/view/widgets/search/document_filter_panel.dart @@ -1,3 +1,7 @@ +import 'dart:developer' as dev; +import 'dart:math'; + +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; @@ -17,10 +21,12 @@ enum DateRangeSelection { before, after } class DocumentFilterPanel extends StatefulWidget { final DocumentFilter initialFilter; final ScrollController scrollController; + final DraggableScrollableController draggableSheetController; const DocumentFilterPanel({ Key? key, required this.initialFilter, required this.scrollController, + required this.draggableSheetController, }) : super(key: key); @override @@ -38,21 +44,49 @@ class _DocumentFilterPanelState extends State { final _formKey = GlobalKey(); late bool _allowOnlyExtendedQuery; + double _heightAnimationValue = 0; + @override void initState() { super.initState(); _allowOnlyExtendedQuery = widget.initialFilter.forceExtendedQuery; + widget.draggableSheetController.addListener(animateTitleByDrag); + } + + void animateTitleByDrag() { + setState( + () { + _heightAnimationValue = dp( + ((max(0.9, widget.draggableSheetController.size) - 0.9) / 0.1), 5); + }, + ); + } + + bool get isDockedToTop => _heightAnimationValue == 1; + + @override + void dispose() { + widget.draggableSheetController.removeListener(animateTitleByDrag); + super.dispose(); + } + + /// Rounds double to [places] decimal places. + double dp(double val, int places) { + num mod = pow(10.0, places); + return ((val * mod).round().toDouble() / mod); } @override Widget build(BuildContext context) { + final double radius = (1 - max(0, (_heightAnimationValue) - 0.5) * 2) * 16; return ClipRRect( - borderRadius: const BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(radius), + topRight: Radius.circular(radius), ), child: Scaffold( floatingActionButtonLocation: FloatingActionButtonLocation.endDocked, + backgroundColor: Theme.of(context).colorScheme.surface, floatingActionButton: Visibility( visible: MediaQuery.of(context).viewInsets.bottom == 0, child: FloatingActionButton.extended( @@ -69,7 +103,7 @@ class _DocumentFilterPanelState extends State { onPressed: _resetFilter, icon: const Icon(Icons.refresh), label: Text(S.of(context).documentFilterResetLabel), - ) + ), ], ), ), @@ -82,67 +116,97 @@ class _DocumentFilterPanelState extends State { ); } - ListView _buildFormList(BuildContext context) { - return ListView( + Widget _buildFormList(BuildContext context) { + return CustomScrollView( controller: widget.scrollController, - children: [ - Align( - alignment: Alignment.center, - child: _buildDragHandle(context), - ), - Text( - S.of(context).documentFilterTitle, - style: Theme.of(context).textTheme.headlineSmall, - ).paddedOnly( - top: 16.0, - left: 16.0, - bottom: 24, - ), - Align( - alignment: Alignment.centerLeft, - child: Text( - S.of(context).documentFilterSearchLabel, - style: Theme.of(context).textTheme.bodySmall, + slivers: [ + SliverAppBar( + pinned: true, + automaticallyImplyLeading: false, + toolbarHeight: kToolbarHeight + 22, + title: SizedBox( + width: MediaQuery.of(context).size.width, + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Opacity( + opacity: 1 - _heightAnimationValue, + child: Padding( + padding: EdgeInsets.only(bottom: 11), + child: _buildDragHandle(), + ), + ), + Align( + alignment: Alignment.centerLeft, + child: Stack( + alignment: Alignment.centerLeft, + children: [ + Opacity( + opacity: max(0, (_heightAnimationValue - 0.5) * 2), + child: GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: const Icon(Icons.expand_more_rounded), + ), + ), + Padding( + padding: + EdgeInsets.only(left: _heightAnimationValue * 48), + child: Text(S.of(context).documentFilterTitle), + ), + ], + ), + ), + ], + ), ), - ).paddedOnly(left: 8.0), - _buildQueryFormField().padded(), - Align( - alignment: Alignment.centerLeft, - child: Text( - S.of(context).documentFilterAdvancedLabel, - style: Theme.of(context).textTheme.bodySmall, - ), - ).padded(), - FormBuilderExtendedDateRangePicker( - name: fkCreatedAt, - initialValue: widget.initialFilter.created, - labelText: S.of(context).documentCreatedPropertyLabel, - onChanged: (_) { - _checkQueryConstraints(); - }, - ).padded(), - FormBuilderExtendedDateRangePicker( - name: fkAddedAt, - initialValue: widget.initialFilter.added, - labelText: S.of(context).documentAddedPropertyLabel, - onChanged: (_) { - _checkQueryConstraints(); - }, - ).padded(), - _buildCorrespondentFormField().padded(), - _buildDocumentTypeFormField().padded(), - _buildStoragePathFormField().padded(), - _buildTagsFormField().padded(), + ), + ..._buildFormFieldList(), ], ); } - Container _buildDragHandle(BuildContext context) { + List _buildFormFieldList() { + return [ + _buildQueryFormField().paddedSymmetrically(vertical: 8, horizontal: 16), + Align( + alignment: Alignment.centerLeft, + child: Text( + S.of(context).documentFilterAdvancedLabel, + style: Theme.of(context).textTheme.bodySmall, + ), + ).paddedSymmetrically(vertical: 8, horizontal: 16), + FormBuilderExtendedDateRangePicker( + name: fkCreatedAt, + initialValue: widget.initialFilter.created, + labelText: S.of(context).documentCreatedPropertyLabel, + onChanged: (_) { + _checkQueryConstraints(); + }, + ).paddedSymmetrically(vertical: 8, horizontal: 16), + FormBuilderExtendedDateRangePicker( + name: fkAddedAt, + initialValue: widget.initialFilter.added, + labelText: S.of(context).documentAddedPropertyLabel, + onChanged: (_) { + _checkQueryConstraints(); + }, + ).paddedSymmetrically(vertical: 8, horizontal: 16), + _buildCorrespondentFormField() + .paddedSymmetrically(vertical: 8, horizontal: 16), + _buildDocumentTypeFormField() + .paddedSymmetrically(vertical: 8, horizontal: 16), + _buildStoragePathFormField() + .paddedSymmetrically(vertical: 8, horizontal: 16), + _buildTagsFormField().padded(16), + ].map((w) => SliverToBoxAdapter(child: w)).toList(); + } + + Container _buildDragHandle() { return Container( - // According to m3 spec + // According to m3 spec https://m3.material.io/components/bottom-sheets/specs width: 32, height: 4, - margin: const EdgeInsets.only(top: 16), decoration: BoxDecoration( color: Theme.of(context).colorScheme.onSurfaceVariant.withOpacity(0.4), borderRadius: BorderRadius.circular(16), diff --git a/lib/features/documents/view/widgets/search/text_query_form_field.dart b/lib/features/documents/view/widgets/search/text_query_form_field.dart index eaca98f..ace67a0 100644 --- a/lib/features/documents/view/widgets/search/text_query_form_field.dart +++ b/lib/features/documents/view/widgets/search/text_query_form_field.dart @@ -28,24 +28,26 @@ class TextQueryFormField extends StatelessWidget { prefixIcon: const Icon(Icons.search_outlined), labelText: _buildLabelText(context, field.value!.queryType), suffixIcon: PopupMenuButton( + enabled: !onlyExtendedQueryAllowed, + color: onlyExtendedQueryAllowed + ? Theme.of(context).disabledColor + : null, itemBuilder: (context) => [ - if (!onlyExtendedQueryAllowed) ...[ - PopupMenuItem( - child: ListTile( - title: Text(S - .of(context) - .documentFilterQueryOptionsTitleAndContentLabel), - ), - value: QueryType.titleAndContent, + PopupMenuItem( + child: ListTile( + title: Text(S + .of(context) + .documentFilterQueryOptionsTitleAndContentLabel), ), - PopupMenuItem( - child: ListTile( - title: Text( - S.of(context).documentFilterQueryOptionsTitleLabel), - ), - value: QueryType.title, + value: QueryType.titleAndContent, + ), + PopupMenuItem( + child: ListTile( + title: Text( + S.of(context).documentFilterQueryOptionsTitleLabel), ), - ], + value: QueryType.title, + ), PopupMenuItem( child: ListTile( title: Text(