From c0e909937d6689774b361935cc197832d47d9c36 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Fri, 8 May 2020 23:03:20 +0900 Subject: [PATCH] highlight thumbnail after scaling --- lib/model/collection_lens.dart | 5 ++ lib/widgets/album/grid/scaling.dart | 8 +++- lib/widgets/album/thumbnail/decorated.dart | 52 ++++++++++++--------- lib/widgets/album/thumbnail/overlay.dart | 41 ++++++++++++++++ lib/widgets/album/thumbnail_collection.dart | 2 +- lib/widgets/common/fx/sweeper.dart | 24 ++++++++-- 6 files changed, 105 insertions(+), 27 deletions(-) diff --git a/lib/model/collection_lens.dart b/lib/model/collection_lens.dart index 547f7442d..c3bb1b741 100644 --- a/lib/model/collection_lens.dart +++ b/lib/model/collection_lens.dart @@ -16,6 +16,7 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel GroupFactor groupFactor; SortFactor sortFactor; final AChangeNotifier filterChangeNotifier = AChangeNotifier(); + final StreamController _highlightController = StreamController.broadcast(); List _filteredEntries; List _subscriptions = []; @@ -74,6 +75,10 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel return _sortedEntries; } + Stream get highlightStream => _highlightController.stream; + + void highlight(ImageEntry entry) => _highlightController.add(entry); + bool get showHeaders { if (sortFactor == SortFactor.size) return false; diff --git a/lib/widgets/album/grid/scaling.dart b/lib/widgets/album/grid/scaling.dart index 2b9200d8c..f01bde383 100644 --- a/lib/widgets/album/grid/scaling.dart +++ b/lib/widgets/album/grid/scaling.dart @@ -17,6 +17,7 @@ class GridScaleGestureDetector extends StatefulWidget { final ValueNotifier extentNotifier; final Size mqSize; final double mqHorizontalPadding; + final void Function(ImageEntry entry) onScaled; final Widget child; const GridScaleGestureDetector({ @@ -25,6 +26,7 @@ class GridScaleGestureDetector extends StatefulWidget { @required this.extentNotifier, @required this.mqSize, @required this.mqHorizontalPadding, + this.onScaled, @required this.child, }); @@ -112,7 +114,11 @@ class _GridScaleGestureDetectorState extends State { } else { // scroll to show the focal point thumbnail at its new position WidgetsBinding.instance.addPostFrameCallback((_) { - _scrollToEntry(_metadata.entry); + final entry = _metadata.entry; + _scrollToEntry(entry); + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.onScaled?.call(entry); + }); _applyingScale = false; }); } diff --git a/lib/widgets/album/thumbnail/decorated.dart b/lib/widgets/album/thumbnail/decorated.dart index 7f4a45fa1..728a2b505 100644 --- a/lib/widgets/album/thumbnail/decorated.dart +++ b/lib/widgets/album/thumbnail/decorated.dart @@ -1,3 +1,4 @@ +import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; import 'package:aves/widgets/album/thumbnail/overlay.dart'; import 'package:aves/widgets/album/thumbnail/raster.dart'; @@ -7,39 +8,42 @@ import 'package:flutter/material.dart'; class DecoratedThumbnail extends StatelessWidget { final ImageEntry entry; final double extent; - final Object heroTag; + final CollectionLens collection; final ValueNotifier isScrollingNotifier; final bool showOverlay; + final Object heroTag; static final Color borderColor = Colors.grey.shade700; static const double borderWidth = .5; - const DecoratedThumbnail({ + DecoratedThumbnail({ Key key, @required this.entry, @required this.extent, - this.heroTag, + this.collection, this.isScrollingNotifier, this.showOverlay = true, - }) : super(key: key); + }) : heroTag = collection?.heroTag(entry), + super(key: key); @override Widget build(BuildContext context) { - final child = Stack( - children: [ - entry.isSvg - ? ThumbnailVectorImage( - entry: entry, - extent: extent, - heroTag: heroTag, - ) - : ThumbnailRasterImage( - entry: entry, - extent: extent, - isScrollingNotifier: isScrollingNotifier, - heroTag: heroTag, - ), - if (showOverlay) + var child = entry.isSvg + ? ThumbnailVectorImage( + entry: entry, + extent: extent, + heroTag: heroTag, + ) + : ThumbnailRasterImage( + entry: entry, + extent: extent, + isScrollingNotifier: isScrollingNotifier, + heroTag: heroTag, + ); + if (showOverlay) { + child = Stack( + children: [ + child, Positioned( bottom: 0, left: 0, @@ -48,13 +52,17 @@ class DecoratedThumbnail extends StatelessWidget { extent: extent, ), ), - if (showOverlay) ThumbnailSelectionOverlay( entry: entry, extent: extent, ), - ], - ); + ThumbnailHighlightOverlay( + highlightedStream: collection.highlightStream.map((highlighted) => highlighted == entry), + extent: extent, + ), + ], + ); + } return Container( decoration: BoxDecoration( border: Border.all( diff --git a/lib/widgets/album/thumbnail/overlay.dart b/lib/widgets/album/thumbnail/overlay.dart index 89c3275c0..55ac90864 100644 --- a/lib/widgets/album/thumbnail/overlay.dart +++ b/lib/widgets/album/thumbnail/overlay.dart @@ -2,6 +2,7 @@ import 'dart:math'; import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/image_entry.dart'; +import 'package:aves/widgets/common/fx/sweeper.dart'; import 'package:aves/widgets/common/icons.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; @@ -103,3 +104,43 @@ class ThumbnailSelectionOverlay extends StatelessWidget { ); } } + +class ThumbnailHighlightOverlay extends StatefulWidget { + final double extent; + final Stream highlightedStream; + + const ThumbnailHighlightOverlay({ + Key key, + @required this.extent, + @required this.highlightedStream, + }) : super(key: key); + + @override + _ThumbnailHighlightOverlayState createState() => _ThumbnailHighlightOverlayState(); +} + +class _ThumbnailHighlightOverlayState extends State { + final ValueNotifier _highlightedNotifier = ValueNotifier(false); + + @override + Widget build(BuildContext context) { + return StreamBuilder( + stream: widget.highlightedStream, + builder: (context, snapshot) { + _highlightedNotifier.value = snapshot.hasData && snapshot.data; + return Sweeper( + builder: (context) => Container( + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).accentColor, + width: widget.extent * .1, + ), + ), + ), + toggledNotifier: _highlightedNotifier, + onSweepEnd: () => _highlightedNotifier.value = false, + ); + }, + ); + } +} diff --git a/lib/widgets/album/thumbnail_collection.dart b/lib/widgets/album/thumbnail_collection.dart index ef20a01a7..a631cfa37 100644 --- a/lib/widgets/album/thumbnail_collection.dart +++ b/lib/widgets/album/thumbnail_collection.dart @@ -55,6 +55,7 @@ class ThumbnailCollection extends StatelessWidget { extentNotifier: _tileExtentNotifier, mqSize: mqSize, mqHorizontalPadding: mqHorizontalPadding, + onScaled: collection.highlight, child: scrollView, ); @@ -210,7 +211,6 @@ class _CollectionScrollViewState extends State { widget.isScrollingNotifier.value = true; _stopScrollMonitoringTimer(); _scrollMonitoringTimer = Timer(const Duration(milliseconds: 100), () { - debugPrint('$runtimeType _onScrollChange is scrolling false'); widget.isScrollingNotifier.value = false; }); } diff --git a/lib/widgets/common/fx/sweeper.dart b/lib/widgets/common/fx/sweeper.dart index 7b5f3da3a..6dfafd29f 100644 --- a/lib/widgets/common/fx/sweeper.dart +++ b/lib/widgets/common/fx/sweeper.dart @@ -9,6 +9,7 @@ class Sweeper extends StatefulWidget { final double sweepAngle; final Curve curve; final ValueNotifier toggledNotifier; + final VoidCallback onSweepEnd; const Sweeper({ Key key, @@ -17,6 +18,7 @@ class Sweeper extends StatefulWidget { this.sweepAngle = pi / 4, this.curve = Curves.easeInOutCubic, @required this.toggledNotifier, + this.onSweepEnd, }) : super(key: key); @override @@ -96,6 +98,9 @@ class _SweeperState extends State with SingleTickerProviderStateMixin { void _onAnimationStatusChange(AnimationStatus status) { setState(() {}); + if (status == AnimationStatus.completed) { + widget.onSweepEnd?.call(); + } } Future _onToggle() async { @@ -121,10 +126,23 @@ class _SweepClipPath extends CustomClipper { @override Path getClip(Size size) { + final width = size.width; + final height = size.height; + final centerX = width / 2; + final centerY = height / 2; + final diagonal = sqrt(width * width + height * height); return Path() - ..moveTo(size.width / 2, size.height / 2) - ..addArc(Rect.fromLTWH(0, 0, size.width, size.height), startAngle, sweepAngle) - ..lineTo(size.width / 2, size.height / 2); + ..moveTo(centerX, centerY) + ..addArc( + Rect.fromCenter( + center: Offset(centerX, centerY), + width: diagonal, + height: diagonal, + ), + startAngle, + sweepAngle, + ) + ..lineTo(centerX, centerY); } @override