mosaic fixes

This commit is contained in:
Thibault Deckers 2022-10-01 20:14:40 +02:00
parent ebd6f1c84c
commit 5019cc2065
10 changed files with 66 additions and 36 deletions

View file

@ -141,19 +141,22 @@ class _CollectionGridContent extends StatelessWidget {
spacing: tileSpacing, spacing: tileSpacing,
horizontalPadding: horizontalPadding, horizontalPadding: horizontalPadding,
tileExtent: thumbnailExtent, tileExtent: thumbnailExtent,
tileBuilder: (entry) => AnimatedBuilder( tileBuilder: (entry, tileSize) {
final extent = tileSize.shortestSide;
return AnimatedBuilder(
animation: favourites, animation: favourites,
builder: (context, child) { builder: (context, child) {
return InteractiveTile( return InteractiveTile(
key: ValueKey(entry.id), key: ValueKey(entry.id),
collection: collection, collection: collection,
entry: entry, entry: entry,
thumbnailExtent: thumbnailExtent, thumbnailExtent: extent,
tileLayout: tileLayout, tileLayout: tileLayout,
isScrollingNotifier: _isScrollingNotifier, isScrollingNotifier: _isScrollingNotifier,
); );
}, },
), );
},
tileAnimationDelay: tileAnimationDelay, tileAnimationDelay: tileAnimationDelay,
child: child!, child: child!,
); );

View file

@ -11,8 +11,9 @@ import 'package:flutter/material.dart';
class TransitionImage extends StatefulWidget { class TransitionImage extends StatefulWidget {
final ImageProvider image; final ImageProvider image;
final double? width, height;
final ValueListenable<double> animation; final ValueListenable<double> animation;
final BoxFit thumbnailFit, viewerFit;
final double? width, height;
final bool gaplessPlayback = false; final bool gaplessPlayback = false;
final Color? background; final Color? background;
@ -20,6 +21,8 @@ class TransitionImage extends StatefulWidget {
super.key, super.key,
required this.image, required this.image,
required this.animation, required this.animation,
required this.thumbnailFit,
required this.viewerFit,
this.width, this.width,
this.height, this.height,
this.background, this.background,
@ -157,6 +160,8 @@ class _TransitionImageState extends State<TransitionImage> {
image: _imageInfo?.image, image: _imageInfo?.image,
scale: _imageInfo?.scale ?? 1.0, scale: _imageInfo?.scale ?? 1.0,
t: t, t: t,
thumbnailFit: widget.thumbnailFit,
viewerFit: widget.viewerFit,
background: widget.background, background: widget.background,
), ),
), ),
@ -166,15 +171,17 @@ class _TransitionImageState extends State<TransitionImage> {
class _TransitionImagePainter extends CustomPainter { class _TransitionImagePainter extends CustomPainter {
final ui.Image? image; final ui.Image? image;
final double scale; final double scale, t;
final double t;
final Color? background; final Color? background;
final BoxFit thumbnailFit, viewerFit;
const _TransitionImagePainter({ const _TransitionImagePainter({
required this.image, required this.image,
required this.scale, required this.scale,
required this.t, required this.t,
this.background, required this.thumbnailFit,
required this.viewerFit,
required this.background,
}); });
@override @override
@ -190,10 +197,10 @@ class _TransitionImagePainter extends CustomPainter {
final inputSize = Size(image!.width.toDouble(), image!.height.toDouble()); final inputSize = Size(image!.width.toDouble(), image!.height.toDouble());
final outputSize = rect.size; final outputSize = rect.size;
final coverSizes = applyBoxFit(BoxFit.cover, inputSize / scale, size); final thumbnailSizes = applyBoxFit(thumbnailFit, inputSize / scale, size);
final containSizes = applyBoxFit(BoxFit.contain, inputSize / scale, size); final viewerSizes = applyBoxFit(viewerFit, inputSize / scale, size);
final sourceSize = Size.lerp(coverSizes.source, containSizes.source, t)! * scale; final sourceSize = Size.lerp(thumbnailSizes.source, viewerSizes.source, t)! * scale;
final destinationSize = Size.lerp(coverSizes.destination, containSizes.destination, t)!; final destinationSize = Size.lerp(thumbnailSizes.destination, viewerSizes.destination, t)!;
final halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0; final halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0;
final halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0; final halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0;

View file

@ -3,6 +3,7 @@ import 'package:aves/model/source/enums/enums.dart';
import 'package:aves/widgets/common/behaviour/eager_scale_gesture_recognizer.dart'; import 'package:aves/widgets/common/behaviour/eager_scale_gesture_recognizer.dart';
import 'package:aves/widgets/common/grid/sections/fixed/scale_overlay.dart'; import 'package:aves/widgets/common/grid/sections/fixed/scale_overlay.dart';
import 'package:aves/widgets/common/grid/sections/mosaic/scale_overlay.dart'; import 'package:aves/widgets/common/grid/sections/mosaic/scale_overlay.dart';
import 'package:aves/widgets/common/grid/sections/section_layout_builder.dart';
import 'package:aves/widgets/common/grid/theme.dart'; import 'package:aves/widgets/common/grid/theme.dart';
import 'package:aves/widgets/common/tile_extent_controller.dart'; import 'package:aves/widgets/common/tile_extent_controller.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -23,7 +24,7 @@ class GridScaleGestureDetector<T> extends StatefulWidget {
final TileLayout tileLayout; final TileLayout tileLayout;
final double Function(double width) heightForWidth; final double Function(double width) heightForWidth;
final Widget Function(Offset center, Size tileSize, Widget child) gridBuilder; final Widget Function(Offset center, Size tileSize, Widget child) gridBuilder;
final Widget Function(T item, Size tileSize) scaledItemBuilder; final TileBuilder<T> scaledItemBuilder;
final MosaicItemBuilder mosaicItemBuilder; final MosaicItemBuilder mosaicItemBuilder;
final Object Function(T item)? highlightItem; final Object Function(T item)? highlightItem;
final Widget child; final Widget child;

View file

@ -11,6 +11,7 @@ import 'package:tuple/tuple.dart';
class FixedExtentSectionLayoutBuilder<T> extends SectionLayoutBuilder<T> { class FixedExtentSectionLayoutBuilder<T> extends SectionLayoutBuilder<T> {
int _currentIndex = 0; int _currentIndex = 0;
double _currentOffset = 0; double _currentOffset = 0;
List<Size> _itemSizes;
FixedExtentSectionLayoutBuilder({ FixedExtentSectionLayoutBuilder({
required super.sections, required super.sections,
@ -26,7 +27,7 @@ class FixedExtentSectionLayoutBuilder<T> extends SectionLayoutBuilder<T> {
required super.tileHeight, required super.tileHeight,
required super.tileBuilder, required super.tileBuilder,
required super.tileAnimationDelay, required super.tileAnimationDelay,
}); }) : _itemSizes = List.generate(columnCount, (index) => Size(tileWidth, tileHeight));
@override @override
SectionedListLayout<T> updateLayouts(BuildContext context) { SectionedListLayout<T> updateLayouts(BuildContext context) {
@ -93,6 +94,7 @@ class FixedExtentSectionLayoutBuilder<T> extends SectionLayoutBuilder<T> {
), ),
sectionKey: sectionKey, sectionKey: sectionKey,
headerExtent: headerExtent, headerExtent: headerExtent,
itemSizes: _itemSizes,
animate: animate, animate: animate,
buildGridRow: (children) => FixedExtentGridRow( buildGridRow: (children) => FixedExtentGridRow(
width: tileWidth, width: tileWidth,

View file

@ -113,6 +113,7 @@ class MosaicSectionLayoutBuilder<T> extends SectionLayoutBuilder<T> {
itemIndexRange: () => isHeader ? const Tuple2(0, 0) : Tuple2(row.firstIndex, row.lastIndex + 1), itemIndexRange: () => isHeader ? const Tuple2(0, 0) : Tuple2(row.firstIndex, row.lastIndex + 1),
sectionKey: sectionKey, sectionKey: sectionKey,
headerExtent: headerExtent, headerExtent: headerExtent,
itemSizes: row.itemWidths.map((v) => Size(v, row.height)).toList(),
animate: animate, animate: animate,
buildGridRow: (children) { buildGridRow: (children) {
return isHeader return isHeader

View file

@ -3,6 +3,7 @@ import 'package:aves/model/source/section_keys.dart';
import 'package:aves/widgets/common/grid/sections/fixed/section_layout_builder.dart'; import 'package:aves/widgets/common/grid/sections/fixed/section_layout_builder.dart';
import 'package:aves/widgets/common/grid/sections/list_layout.dart'; import 'package:aves/widgets/common/grid/sections/list_layout.dart';
import 'package:aves/widgets/common/grid/sections/mosaic/section_layout_builder.dart'; import 'package:aves/widgets/common/grid/sections/mosaic/section_layout_builder.dart';
import 'package:aves/widgets/common/grid/sections/section_layout_builder.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@ -14,7 +15,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
final TileLayout tileLayout; final TileLayout tileLayout;
final int columnCount; final int columnCount;
final double spacing, horizontalPadding, tileWidth, tileHeight; final double spacing, horizontalPadding, tileWidth, tileHeight;
final Widget Function(T item) tileBuilder; final TileBuilder<T> tileBuilder;
final Duration tileAnimationDelay; final Duration tileAnimationDelay;
final CoverRatioResolver<T> coverRatioResolver; final CoverRatioResolver<T> coverRatioResolver;
final Widget child; final Widget child;

View file

@ -8,6 +8,8 @@ import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:tuple/tuple.dart'; import 'package:tuple/tuple.dart';
typedef TileBuilder<T> = Widget Function(T item, Size tileSize);
abstract class SectionLayoutBuilder<T> { abstract class SectionLayoutBuilder<T> {
final Map<SectionKey, List<T>> sections; final Map<SectionKey, List<T>> sections;
final bool showHeaders; final bool showHeaders;
@ -17,7 +19,7 @@ abstract class SectionLayoutBuilder<T> {
final TileLayout tileLayout; final TileLayout tileLayout;
final int columnCount; final int columnCount;
final double spacing, horizontalPadding, tileWidth, tileHeight, bottom; final double spacing, horizontalPadding, tileWidth, tileHeight, bottom;
final Widget Function(T item) tileBuilder; final TileBuilder<T> tileBuilder;
final Duration tileAnimationDelay; final Duration tileAnimationDelay;
final bool animate; final bool animate;
@ -55,6 +57,7 @@ abstract class SectionLayoutBuilder<T> {
required Tuple2<int, int> Function() itemIndexRange, required Tuple2<int, int> Function() itemIndexRange,
required SectionKey sectionKey, required SectionKey sectionKey,
required double headerExtent, required double headerExtent,
required List<Size> itemSizes,
required bool animate, required bool animate,
required Widget Function(List<Widget> children) buildGridRow, required Widget Function(List<Widget> children) buildGridRow,
}) { }) {
@ -67,13 +70,17 @@ abstract class SectionLayoutBuilder<T> {
final itemMinMax = itemIndexRange(); final itemMinMax = itemIndexRange();
final minItemIndex = itemMinMax.item1.clamp(0, sectionItemCount); final minItemIndex = itemMinMax.item1.clamp(0, sectionItemCount);
final maxItemIndex = itemMinMax.item2.clamp(0, sectionItemCount); final maxItemIndex = itemMinMax.item2.clamp(0, sectionItemCount);
final childrenCount = maxItemIndex - minItemIndex;
final children = <Widget>[]; final children = <Widget>[];
for (var i = minItemIndex; i < maxItemIndex; i++) { for (var i = 0; i < childrenCount; i++) {
final itemGridIndex = sectionGridIndex + i - minItemIndex;
final item = RepaintBoundary( final item = RepaintBoundary(
child: tileBuilder(section[i]), child: tileBuilder(section[minItemIndex + i], itemSizes[i]),
); );
children.add(animate ? _buildAnimation(context, itemGridIndex, item) : item); if (animate) {
children.add(_buildAnimation(context, sectionGridIndex + i, item));
} else {
children.add(item);
}
} }
return Padding( return Padding(
padding: EdgeInsets.symmetric(horizontal: horizontalPadding), padding: EdgeInsets.symmetric(horizontal: horizontalPadding),

View file

@ -281,10 +281,15 @@ class OverlayIcon extends StatelessWidget {
children: [ children: [
iconChild, iconChild,
const SizedBox(width: 2), const SizedBox(width: 2),
Text( Flexible(
child: Text(
text!, text!,
// consistent with the color used for the icon next to it // consistent with the color used for the icon next to it
style: TextStyle(color: IconTheme.of(context).color), style: TextStyle(color: IconTheme.of(context).color),
softWrap: false,
overflow: TextOverflow.fade,
maxLines: 1,
),
), ),
], ],
), ),

View file

@ -272,6 +272,8 @@ class _ThumbnailImageState extends State<ThumbnailImage> {
return TransitionImage( return TransitionImage(
image: entry.bestCachedThumbnail, image: entry.bestCachedThumbnail,
animation: animation, animation: animation,
thumbnailFit: isMosaic ? BoxFit.contain : BoxFit.cover,
viewerFit: BoxFit.contain,
background: backgroundColor, background: backgroundColor,
); );
}, },

View file

@ -306,11 +306,12 @@ class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
horizontalPadding: horizontalPadding, horizontalPadding: horizontalPadding,
tileWidth: thumbnailExtent, tileWidth: thumbnailExtent,
tileHeight: tileHeight, tileHeight: tileHeight,
tileBuilder: (gridItem) { tileBuilder: (gridItem, tileSize) {
final extent = tileSize.shortestSide;
return InteractiveFilterTile( return InteractiveFilterTile(
gridItem: gridItem, gridItem: gridItem,
chipExtent: thumbnailExtent, chipExtent: extent,
thumbnailExtent: thumbnailExtent, thumbnailExtent: extent,
tileLayout: tileLayout, tileLayout: tileLayout,
banner: _getFilterBanner(context, gridItem.filter), banner: _getFilterBanner(context, gridItem.filter),
heroType: heroType, heroType: heroType,