diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7a6a7d4d0..59699a7dd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file.
## [Unreleased]
+### Changed
+
+- mosaic layout: clamp ratio to 32/9
+
## [v1.9.6] - 2023-09-25
### Fixed
diff --git a/lib/widgets/common/grid/sections/mosaic/section_layout_builder.dart b/lib/widgets/common/grid/sections/mosaic/section_layout_builder.dart
index 4be25c6e6..26701c59d 100644
--- a/lib/widgets/common/grid/sections/mosaic/section_layout_builder.dart
+++ b/lib/widgets/common/grid/sections/mosaic/section_layout_builder.dart
@@ -19,7 +19,9 @@ class MosaicSectionLayoutBuilder extends SectionLayoutBuilder {
late double rowHeightMax;
final CoverRatioResolver coverRatioResolver;
- static const heightMaxFactor = 2.4;
+ static const double heightMaxFactor = 2.4;
+ static const double minThumbnailAspectRatio = 9 / 32;
+ static const double maxThumbnailAspectRatio = 32 / 9;
MosaicSectionLayoutBuilder({
required super.sections,
@@ -76,7 +78,7 @@ class MosaicSectionLayoutBuilder extends SectionLayoutBuilder {
targetExtent: tileWidth,
spacing: spacing,
bottom: bottom,
- coverRatioResolver: coverRatioResolver,
+ coverRatioResolver: (item) => coverRatioResolver(item).clamp(minThumbnailAspectRatio, maxThumbnailAspectRatio),
);
final rowCount = rows.length;
final sectionChildCount = 1 + rowCount;
diff --git a/lib/widgets/common/thumbnail/decorated.dart b/lib/widgets/common/thumbnail/decorated.dart
index e55912e26..6a1616d25 100644
--- a/lib/widgets/common/thumbnail/decorated.dart
+++ b/lib/widgets/common/thumbnail/decorated.dart
@@ -1,6 +1,7 @@
import 'package:aves/model/entry/entry.dart';
import 'package:aves/widgets/common/fx/borders.dart';
import 'package:aves/widgets/common/grid/overlay.dart';
+import 'package:aves/widgets/common/grid/sections/mosaic/section_layout_builder.dart';
import 'package:aves/widgets/common/thumbnail/image.dart';
import 'package:aves/widgets/common/thumbnail/notifications.dart';
import 'package:aves/widgets/common/thumbnail/overlay.dart';
@@ -30,8 +31,17 @@ class DecoratedThumbnail extends StatelessWidget {
@override
Widget build(BuildContext context) {
- final thumbnailWidth = isMosaic ? tileExtent * entry.displayAspectRatio : tileExtent;
- final thumbnailHeight = tileExtent;
+ final double thumbnailHeight = tileExtent;
+ final double thumbnailWidth;
+ if (isMosaic) {
+ thumbnailWidth = thumbnailHeight *
+ entry.displayAspectRatio.clamp(
+ MosaicSectionLayoutBuilder.minThumbnailAspectRatio,
+ MosaicSectionLayoutBuilder.maxThumbnailAspectRatio,
+ );
+ } else {
+ thumbnailWidth = tileExtent;
+ }
Widget child = ThumbnailImage(
entry: entry,
diff --git a/lib/widgets/common/thumbnail/image.dart b/lib/widgets/common/thumbnail/image.dart
index 794979ef8..d2e06f994 100644
--- a/lib/widgets/common/thumbnail/image.dart
+++ b/lib/widgets/common/thumbnail/image.dart
@@ -11,6 +11,7 @@ import 'package:aves/services/common/services.dart';
import 'package:aves/widgets/common/basic/insets.dart';
import 'package:aves/widgets/common/fx/checkered_decoration.dart';
import 'package:aves/widgets/common/fx/transition_image.dart';
+import 'package:aves/widgets/common/grid/sections/mosaic/section_layout_builder.dart';
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
import 'package:aves/widgets/common/thumbnail/error.dart';
import 'package:aves_model/aves_model.dart';
@@ -197,16 +198,20 @@ class _ThumbnailImageState extends State {
// use `RawImage` instead of `Image`, using `ImageInfo` to check dimensions
// and have more control when chaining image providers
- final thumbnailWidth = isMosaic ? extent * entry.displayAspectRatio : extent;
final thumbnailHeight = extent;
+ final double thumbnailWidth;
+ if (isMosaic) {
+ thumbnailWidth = thumbnailHeight *
+ entry.displayAspectRatio.clamp(
+ MosaicSectionLayoutBuilder.minThumbnailAspectRatio,
+ MosaicSectionLayoutBuilder.maxThumbnailAspectRatio,
+ );
+ } else {
+ thumbnailWidth = extent;
+ }
final canHaveAlpha = entry.canHaveAlpha;
- final fit = widget.fit ??
- (entry.isSvg
- ? BoxFit.contain
- : isMosaic
- ? BoxFit.contain
- : BoxFit.cover);
+ final fit = widget.fit ?? (entry.isSvg ? BoxFit.contain : BoxFit.cover);
final imageInfo = _lastImageInfo;
Widget image = imageInfo == null
? Container(
@@ -266,7 +271,7 @@ class _ThumbnailImageState extends State {
Widget child = TransitionImage(
image: entry.bestCachedThumbnail,
animation: animation,
- thumbnailFit: isMosaic ? BoxFit.contain : BoxFit.cover,
+ thumbnailFit: BoxFit.cover,
viewerFit: BoxFit.contain,
background: backgroundColor,
);