From 0103ff16e8675a70c4d4fb929b1495e6208da75a Mon Sep 17 00:00:00 2001 From: Shawn Date: Tue, 4 Oct 2022 09:10:31 -0600 Subject: [PATCH] Close keyboard when scrolling on search results. Add ScrollDecorator.onInit. --- lib/ui/common/controls/scroll_decorator.dart | 46 +++++++++----- .../widgets/_results_grid.dart | 62 +++++++++++++------ 2 files changed, 73 insertions(+), 35 deletions(-) diff --git a/lib/ui/common/controls/scroll_decorator.dart b/lib/ui/common/controls/scroll_decorator.dart index 3f97f116..05c191c7 100644 --- a/lib/ui/common/controls/scroll_decorator.dart +++ b/lib/ui/common/controls/scroll_decorator.dart @@ -4,12 +4,17 @@ import 'package:flutter/material.dart'; /// Easily add visual decorations to a scrolling widget based on the state of its controller. class ScrollDecorator extends StatefulWidget { - /// Creates a widget that builds foreground and/or background decorations for a scrolling widget based on the state of /// its ScrollController. // ignore: prefer_const_constructors_in_immutables - ScrollDecorator({Key? key, required this.builder, this.foregroundBuilder, this.backgroundBuilder, this.controller}) - : super(key: key); + ScrollDecorator({ + Key? key, + required this.builder, + this.fgBuilder, + this.bgBuilder, + this.controller, + this.onInit, + }) : super(key: key); /// Creates a ScrollDecorator that fades a widget in at the begin or end of the scrolling widget based on the scroll /// position. For example on a vertical list, it would fade in the `begin` widget when the list is not scrolled to the @@ -18,9 +23,10 @@ class ScrollDecorator extends StatefulWidget { Key? key, required this.builder, this.controller, + this.onInit, Widget? begin, Widget? end, - bool background = false, + bool bg = false, Axis direction = Axis.vertical, Duration duration = const Duration(milliseconds: 150), }) : super(key: key) { @@ -45,8 +51,8 @@ class ScrollDecorator extends StatefulWidget { ); } - backgroundBuilder = background ? flexBuilder : null; - foregroundBuilder = !background ? flexBuilder : null; + bgBuilder = bg ? flexBuilder : null; + fgBuilder = !bg ? flexBuilder : null; } /// Creates an ScrollDecorator that adds a shadow to the top of a vertical list when it is scrolled down. @@ -54,10 +60,11 @@ class ScrollDecorator extends StatefulWidget { Key? key, required this.builder, this.controller, + this.onInit, Color color = Colors.black54, }) : super(key: key) { - backgroundBuilder = null; - foregroundBuilder = (controller) { + bgBuilder = null; + fgBuilder = (controller) { final double ratio = controller.hasClients ? min(1, controller.position.extentBefore / 60) : 0; return IgnorePointer( child: Container( @@ -84,11 +91,13 @@ class ScrollDecorator extends StatefulWidget { /// Builder to create the decoration widget that will be layered in front of the scrolling widget. It should use the /// provided ScrollController to adjust its output as appropriate. - late final ScrollBuilder? foregroundBuilder; + late final ScrollBuilder? fgBuilder; /// Builder to create the decoration widget that will be layered behind the scrolling widget. It should use the /// provided ScrollController to adjust its output as appropriate. - late final ScrollBuilder? backgroundBuilder; + late final ScrollBuilder? bgBuilder; + + final void Function(ScrollController controller)? onInit; @override State createState() => _ScrollDecoratorState(); @@ -98,9 +107,12 @@ class _ScrollDecoratorState extends State { ScrollController? _controller; late Widget content; + ScrollController get currentController => (widget.controller ?? _controller)!; + @override void initState() { if (widget.controller == null) _controller = ScrollController(); + widget.onInit?.call(currentController); super.initState(); } @@ -112,17 +124,19 @@ class _ScrollDecoratorState extends State { @override Widget build(BuildContext context) { - ScrollController controller = (widget.controller ?? _controller)!; - - content = widget.builder(controller); + content = widget.builder(currentController); return AnimatedBuilder( - animation: controller, + animation: currentController, builder: (_, __) { return Stack( children: [ - if (widget.backgroundBuilder != null) widget.backgroundBuilder!(controller), + if (widget.bgBuilder != null) ...[ + widget.bgBuilder!(currentController), + ], content, - if (widget.foregroundBuilder != null) widget.foregroundBuilder!(controller), + if (widget.fgBuilder != null) ...[ + widget.fgBuilder!(currentController), + ], ], ); }); diff --git a/lib/ui/screens/artifact/artifact_search/widgets/_results_grid.dart b/lib/ui/screens/artifact/artifact_search/widgets/_results_grid.dart index ee891aca..8019b3b0 100644 --- a/lib/ui/screens/artifact/artifact_search/widgets/_results_grid.dart +++ b/lib/ui/screens/artifact/artifact_search/widgets/_results_grid.dart @@ -1,32 +1,56 @@ part of '../artifact_search_screen.dart'; /// Staggered Masonry styled grid for displaying two columns of different aspect-ratio images. -class _ResultsGrid extends StatelessWidget { - const _ResultsGrid({Key? key, required this.searchResults, required this.onPressed}) : super(key: key); +class _ResultsGrid extends StatefulWidget { + _ResultsGrid({Key? key, required this.searchResults, required this.onPressed}) : super(key: key); final void Function(SearchData) onPressed; final List searchResults; + @override + State<_ResultsGrid> createState() => _ResultsGridState(); +} + +class _ResultsGridState extends State<_ResultsGrid> { + late ScrollController _controller; + + double _prevVel = -1; + + void _handleResultsScrolled() { + // Hide the keyboard if the list is scrolled manually by the pointer, ignoring velocity based scroll changes + // ignore: INVALID_USE_OF_PROTECTED_MEMBER, INVALID_USE_OF_VISIBLE_FOR_TESTING_MEMBER + final vel = _controller.position.activity?.velocity; + if (vel == 0 && _prevVel == 0) { + FocusManager.instance.primaryFocus?.unfocus(); + } + _prevVel = vel ?? _prevVel; + } + @override Widget build(BuildContext context) { return ScrollDecorator.shadow( - builder: (controller) => CustomScrollView( - controller: controller, - scrollBehavior: const ScrollBehavior().copyWith(scrollbars: false), - clipBehavior: Clip.hardEdge, - slivers: [ - SliverToBoxAdapter(child: _buildLanguageMessage(context)), - SliverPadding( - padding: EdgeInsets.all($styles.insets.sm).copyWith(bottom: $styles.insets.offset * 1.5), - sliver: SliverMasonryGrid.count( - crossAxisCount: 2, - mainAxisSpacing: $styles.insets.sm, - crossAxisSpacing: $styles.insets.sm, - childCount: searchResults.length, - itemBuilder: (context, index) => _ResultTile(onPressed: onPressed, data: searchResults[index]), + onInit: (controller) => controller.addListener(_handleResultsScrolled), + builder: (controller) { + _controller = controller; + return CustomScrollView( + controller: controller, + scrollBehavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + clipBehavior: Clip.hardEdge, + slivers: [ + SliverToBoxAdapter(child: _buildLanguageMessage(context)), + SliverPadding( + padding: EdgeInsets.all($styles.insets.sm).copyWith(bottom: $styles.insets.offset * 1.5), + sliver: SliverMasonryGrid.count( + crossAxisCount: 2, + mainAxisSpacing: $styles.insets.sm, + crossAxisSpacing: $styles.insets.sm, + childCount: widget.searchResults.length, + itemBuilder: (context, index) => + _ResultTile(onPressed: widget.onPressed, data: widget.searchResults[index]), + ), ), - ), - ], - ), + ], + ); + }, ); }