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

View file

@ -17,6 +17,7 @@ class GridScaleGestureDetector extends StatefulWidget {
final ValueNotifier<double> 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<GridScaleGestureDetector> {
} 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;
});
}

View file

@ -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<bool> 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(

View file

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

View file

@ -9,6 +9,7 @@ class Sweeper extends StatefulWidget {
final double sweepAngle;
final Curve curve;
final ValueNotifier<bool> 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<Sweeper> with SingleTickerProviderStateMixin {
void _onAnimationStatusChange(AnimationStatus status) {
setState(() {});
if (status == AnimationStatus.completed) {
widget.onSweepEnd?.call();
}
}
Future<void> _onToggle() async {
@ -121,10 +126,23 @@ class _SweepClipPath extends CustomClipper<Path> {
@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