highlight thumbnail after scaling

This commit is contained in:
Thibault Deckers 2020-05-08 23:03:20 +09:00
parent e9d12ed3f3
commit c0e909937d
6 changed files with 105 additions and 27 deletions

View file

@ -16,6 +16,7 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
GroupFactor groupFactor; GroupFactor groupFactor;
SortFactor sortFactor; SortFactor sortFactor;
final AChangeNotifier filterChangeNotifier = AChangeNotifier(); final AChangeNotifier filterChangeNotifier = AChangeNotifier();
final StreamController<ImageEntry> _highlightController = StreamController.broadcast();
List<ImageEntry> _filteredEntries; List<ImageEntry> _filteredEntries;
List<StreamSubscription> _subscriptions = []; List<StreamSubscription> _subscriptions = [];
@ -74,6 +75,10 @@ class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSel
return _sortedEntries; return _sortedEntries;
} }
Stream<ImageEntry> get highlightStream => _highlightController.stream;
void highlight(ImageEntry entry) => _highlightController.add(entry);
bool get showHeaders { bool get showHeaders {
if (sortFactor == SortFactor.size) return false; if (sortFactor == SortFactor.size) return false;

View file

@ -17,6 +17,7 @@ class GridScaleGestureDetector extends StatefulWidget {
final ValueNotifier<double> extentNotifier; final ValueNotifier<double> extentNotifier;
final Size mqSize; final Size mqSize;
final double mqHorizontalPadding; final double mqHorizontalPadding;
final void Function(ImageEntry entry) onScaled;
final Widget child; final Widget child;
const GridScaleGestureDetector({ const GridScaleGestureDetector({
@ -25,6 +26,7 @@ class GridScaleGestureDetector extends StatefulWidget {
@required this.extentNotifier, @required this.extentNotifier,
@required this.mqSize, @required this.mqSize,
@required this.mqHorizontalPadding, @required this.mqHorizontalPadding,
this.onScaled,
@required this.child, @required this.child,
}); });
@ -112,7 +114,11 @@ class _GridScaleGestureDetectorState extends State<GridScaleGestureDetector> {
} else { } else {
// scroll to show the focal point thumbnail at its new position // scroll to show the focal point thumbnail at its new position
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollToEntry(_metadata.entry); final entry = _metadata.entry;
_scrollToEntry(entry);
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.onScaled?.call(entry);
});
_applyingScale = false; _applyingScale = false;
}); });
} }

View file

@ -1,3 +1,4 @@
import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.dart'; import 'package:aves/model/image_entry.dart';
import 'package:aves/widgets/album/thumbnail/overlay.dart'; import 'package:aves/widgets/album/thumbnail/overlay.dart';
import 'package:aves/widgets/album/thumbnail/raster.dart'; import 'package:aves/widgets/album/thumbnail/raster.dart';
@ -7,39 +8,42 @@ import 'package:flutter/material.dart';
class DecoratedThumbnail extends StatelessWidget { class DecoratedThumbnail extends StatelessWidget {
final ImageEntry entry; final ImageEntry entry;
final double extent; final double extent;
final Object heroTag; final CollectionLens collection;
final ValueNotifier<bool> isScrollingNotifier; final ValueNotifier<bool> isScrollingNotifier;
final bool showOverlay; final bool showOverlay;
final Object heroTag;
static final Color borderColor = Colors.grey.shade700; static final Color borderColor = Colors.grey.shade700;
static const double borderWidth = .5; static const double borderWidth = .5;
const DecoratedThumbnail({ DecoratedThumbnail({
Key key, Key key,
@required this.entry, @required this.entry,
@required this.extent, @required this.extent,
this.heroTag, this.collection,
this.isScrollingNotifier, this.isScrollingNotifier,
this.showOverlay = true, this.showOverlay = true,
}) : super(key: key); }) : heroTag = collection?.heroTag(entry),
super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final child = Stack( var child = entry.isSvg
children: [ ? ThumbnailVectorImage(
entry.isSvg entry: entry,
? ThumbnailVectorImage( extent: extent,
entry: entry, heroTag: heroTag,
extent: extent, )
heroTag: heroTag, : ThumbnailRasterImage(
) entry: entry,
: ThumbnailRasterImage( extent: extent,
entry: entry, isScrollingNotifier: isScrollingNotifier,
extent: extent, heroTag: heroTag,
isScrollingNotifier: isScrollingNotifier, );
heroTag: heroTag, if (showOverlay) {
), child = Stack(
if (showOverlay) children: [
child,
Positioned( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
@ -48,13 +52,17 @@ class DecoratedThumbnail extends StatelessWidget {
extent: extent, extent: extent,
), ),
), ),
if (showOverlay)
ThumbnailSelectionOverlay( ThumbnailSelectionOverlay(
entry: entry, entry: entry,
extent: extent, extent: extent,
), ),
], ThumbnailHighlightOverlay(
); highlightedStream: collection.highlightStream.map((highlighted) => highlighted == entry),
extent: extent,
),
],
);
}
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(

View file

@ -2,6 +2,7 @@ import 'dart:math';
import 'package:aves/model/collection_lens.dart'; import 'package:aves/model/collection_lens.dart';
import 'package:aves/model/image_entry.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:aves/widgets/common/icons.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
@ -103,3 +104,43 @@ class ThumbnailSelectionOverlay extends StatelessWidget {
); );
} }
} }
class ThumbnailHighlightOverlay extends StatefulWidget {
final double extent;
final Stream<bool> highlightedStream;
const ThumbnailHighlightOverlay({
Key key,
@required this.extent,
@required this.highlightedStream,
}) : super(key: key);
@override
_ThumbnailHighlightOverlayState createState() => _ThumbnailHighlightOverlayState();
}
class _ThumbnailHighlightOverlayState extends State<ThumbnailHighlightOverlay> {
final ValueNotifier<bool> _highlightedNotifier = ValueNotifier(false);
@override
Widget build(BuildContext context) {
return StreamBuilder<bool>(
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,
);
},
);
}
}

View file

@ -55,6 +55,7 @@ class ThumbnailCollection extends StatelessWidget {
extentNotifier: _tileExtentNotifier, extentNotifier: _tileExtentNotifier,
mqSize: mqSize, mqSize: mqSize,
mqHorizontalPadding: mqHorizontalPadding, mqHorizontalPadding: mqHorizontalPadding,
onScaled: collection.highlight,
child: scrollView, child: scrollView,
); );
@ -210,7 +211,6 @@ class _CollectionScrollViewState extends State<CollectionScrollView> {
widget.isScrollingNotifier.value = true; widget.isScrollingNotifier.value = true;
_stopScrollMonitoringTimer(); _stopScrollMonitoringTimer();
_scrollMonitoringTimer = Timer(const Duration(milliseconds: 100), () { _scrollMonitoringTimer = Timer(const Duration(milliseconds: 100), () {
debugPrint('$runtimeType _onScrollChange is scrolling false');
widget.isScrollingNotifier.value = false; widget.isScrollingNotifier.value = false;
}); });
} }

View file

@ -9,6 +9,7 @@ class Sweeper extends StatefulWidget {
final double sweepAngle; final double sweepAngle;
final Curve curve; final Curve curve;
final ValueNotifier<bool> toggledNotifier; final ValueNotifier<bool> toggledNotifier;
final VoidCallback onSweepEnd;
const Sweeper({ const Sweeper({
Key key, Key key,
@ -17,6 +18,7 @@ class Sweeper extends StatefulWidget {
this.sweepAngle = pi / 4, this.sweepAngle = pi / 4,
this.curve = Curves.easeInOutCubic, this.curve = Curves.easeInOutCubic,
@required this.toggledNotifier, @required this.toggledNotifier,
this.onSweepEnd,
}) : super(key: key); }) : super(key: key);
@override @override
@ -96,6 +98,9 @@ class _SweeperState extends State<Sweeper> with SingleTickerProviderStateMixin {
void _onAnimationStatusChange(AnimationStatus status) { void _onAnimationStatusChange(AnimationStatus status) {
setState(() {}); setState(() {});
if (status == AnimationStatus.completed) {
widget.onSweepEnd?.call();
}
} }
Future<void> _onToggle() async { Future<void> _onToggle() async {
@ -121,10 +126,23 @@ class _SweepClipPath extends CustomClipper<Path> {
@override @override
Path getClip(Size size) { 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() return Path()
..moveTo(size.width / 2, size.height / 2) ..moveTo(centerX, centerY)
..addArc(Rect.fromLTWH(0, 0, size.width, size.height), startAngle, sweepAngle) ..addArc(
..lineTo(size.width / 2, size.height / 2); Rect.fromCenter(
center: Offset(centerX, centerY),
width: diagonal,
height: diagonal,
),
startAngle,
sweepAngle,
)
..lineTo(centerX, centerY);
} }
@override @override