#38 changed decorated filter layout, with label below cover
This commit is contained in:
parent
8f2a0a8247
commit
5939668fd5
9 changed files with 318 additions and 216 deletions
|
@ -79,18 +79,17 @@ class _CollectionGridContent extends StatelessWidget {
|
|||
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
|
||||
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
|
||||
builder: (context, tileExtent, child) {
|
||||
return GridTheme(
|
||||
extent: tileExtent,
|
||||
child: Selector<TileExtentController, Tuple3<double, int, double>>(
|
||||
selector: (context, c) => Tuple3(c.viewportSize.width, c.columnCount, c.spacing),
|
||||
builder: (context, c, child) {
|
||||
final scrollableWidth = c.item1;
|
||||
final columnCount = c.item2;
|
||||
final tileSpacing = c.item3;
|
||||
// do not listen for animation delay change
|
||||
final controller = Provider.of<TileExtentController>(context, listen: false);
|
||||
final tileAnimationDelay = controller.getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
|
||||
return SectionedEntryListLayoutProvider(
|
||||
return Selector<TileExtentController, Tuple3<double, int, double>>(
|
||||
selector: (context, c) => Tuple3(c.viewportSize.width, c.columnCount, c.spacing),
|
||||
builder: (context, c, child) {
|
||||
final scrollableWidth = c.item1;
|
||||
final columnCount = c.item2;
|
||||
final tileSpacing = c.item3;
|
||||
// do not listen for animation delay change
|
||||
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
|
||||
return GridTheme(
|
||||
extent: tileExtent,
|
||||
child: SectionedEntryListLayoutProvider(
|
||||
collection: collection,
|
||||
scrollableWidth: scrollableWidth,
|
||||
columnCount: columnCount,
|
||||
|
@ -104,16 +103,18 @@ class _CollectionGridContent extends StatelessWidget {
|
|||
isScrollingNotifier: _isScrollingNotifier,
|
||||
),
|
||||
tileAnimationDelay: tileAnimationDelay,
|
||||
child: _CollectionSectionedContent(
|
||||
collection: collection,
|
||||
isScrollingNotifier: _isScrollingNotifier,
|
||||
scrollController: PrimaryScrollController.of(context)!,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
child: child!,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: _CollectionSectionedContent(
|
||||
collection: collection,
|
||||
isScrollingNotifier: _isScrollingNotifier,
|
||||
scrollController: PrimaryScrollController.of(context)!,
|
||||
),
|
||||
);
|
||||
return sectionedListLayoutProvider;
|
||||
},
|
||||
|
@ -199,10 +200,11 @@ class _CollectionScaler extends StatelessWidget {
|
|||
final tileSpacing = context.select<TileExtentController, double>((controller) => controller.spacing);
|
||||
return GridScaleGestureDetector<AvesEntry>(
|
||||
scrollableKey: scrollableKey,
|
||||
gridBuilder: (center, extent, child) => CustomPaint(
|
||||
heightForWidth: (width) => width,
|
||||
gridBuilder: (center, tileSize, child) => CustomPaint(
|
||||
painter: GridPainter(
|
||||
center: center,
|
||||
extent: extent,
|
||||
tileSize: tileSize,
|
||||
spacing: tileSpacing,
|
||||
borderWidth: DecoratedThumbnail.borderWidth,
|
||||
borderRadius: Radius.zero,
|
||||
|
@ -210,7 +212,7 @@ class _CollectionScaler extends StatelessWidget {
|
|||
),
|
||||
child: child,
|
||||
),
|
||||
scaledBuilder: (entry, extent) => DecoratedThumbnail(
|
||||
scaledBuilder: (entry, tileSize) => DecoratedThumbnail(
|
||||
entry: entry,
|
||||
tileExtent: context.read<TileExtentController>().effectiveExtentMax,
|
||||
selectable: false,
|
||||
|
|
|
@ -23,7 +23,8 @@ class SectionedEntryListLayoutProvider extends SectionedListLayoutProvider<AvesE
|
|||
scrollableWidth: scrollableWidth,
|
||||
columnCount: columnCount,
|
||||
spacing: spacing,
|
||||
tileExtent: tileExtent,
|
||||
tileWidth: tileExtent,
|
||||
tileHeight: tileExtent,
|
||||
tileBuilder: tileBuilder,
|
||||
tileAnimationDelay: tileAnimationDelay,
|
||||
child: child,
|
||||
|
|
|
@ -23,7 +23,7 @@ class DraggableThumbLabel<T> extends StatelessWidget {
|
|||
|
||||
final section = sll.sections[sectionLayout.sectionKey]!;
|
||||
final dy = offsetY - (sectionLayout.minOffset + sectionLayout.headerExtent);
|
||||
final itemIndex = dy < 0 ? 0 : (dy ~/ (sll.tileExtent + sll.spacing)) * sll.columnCount;
|
||||
final itemIndex = dy < 0 ? 0 : (dy ~/ (sll.tileHeight + sll.spacing)) * sll.columnCount;
|
||||
final item = section[itemIndex];
|
||||
|
||||
final lines = lineBuilder(context, item);
|
||||
|
|
|
@ -13,7 +13,7 @@ import 'package:provider/provider.dart';
|
|||
abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
||||
final double scrollableWidth;
|
||||
final int columnCount;
|
||||
final double spacing, tileExtent;
|
||||
final double spacing, tileWidth, tileHeight;
|
||||
final Widget Function(T item) tileBuilder;
|
||||
final Duration tileAnimationDelay;
|
||||
final Widget child;
|
||||
|
@ -23,7 +23,8 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
|||
required this.scrollableWidth,
|
||||
required this.columnCount,
|
||||
required this.spacing,
|
||||
required this.tileExtent,
|
||||
required this.tileWidth,
|
||||
required this.tileHeight,
|
||||
required this.tileBuilder,
|
||||
required this.tileAnimationDelay,
|
||||
required this.child,
|
||||
|
@ -60,7 +61,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
|||
final sectionLastIndex = currentIndex - 1;
|
||||
|
||||
final sectionMinOffset = currentOffset;
|
||||
currentOffset += headerExtent + tileExtent * rowCount + spacing * (rowCount - 1);
|
||||
currentOffset += headerExtent + tileHeight * rowCount + spacing * (rowCount - 1);
|
||||
final sectionMaxOffset = currentOffset;
|
||||
|
||||
sectionLayouts.add(
|
||||
|
@ -71,7 +72,7 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
|||
minOffset: sectionMinOffset,
|
||||
maxOffset: sectionMaxOffset,
|
||||
headerExtent: headerExtent,
|
||||
tileExtent: tileExtent,
|
||||
tileHeight: tileHeight,
|
||||
spacing: spacing,
|
||||
builder: (context, listIndex) => _buildInSection(
|
||||
context,
|
||||
|
@ -89,7 +90,8 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
|||
sections: _sections,
|
||||
showHeaders: _showHeaders,
|
||||
columnCount: columnCount,
|
||||
tileExtent: tileExtent,
|
||||
tileWidth: tileWidth,
|
||||
tileHeight: tileHeight,
|
||||
spacing: spacing,
|
||||
sectionLayouts: sectionLayouts,
|
||||
);
|
||||
|
@ -123,7 +125,8 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
|||
children.add(animate ? _buildAnimation(itemGridIndex, item) : item);
|
||||
}
|
||||
return _GridRow(
|
||||
extent: tileExtent,
|
||||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
spacing: spacing,
|
||||
children: children,
|
||||
);
|
||||
|
@ -158,7 +161,8 @@ abstract class SectionedListLayoutProvider<T> extends StatelessWidget {
|
|||
properties.add(DoubleProperty('scrollableWidth', scrollableWidth));
|
||||
properties.add(IntProperty('columnCount', columnCount));
|
||||
properties.add(DoubleProperty('spacing', spacing));
|
||||
properties.add(DoubleProperty('tileExtent', tileExtent));
|
||||
properties.add(DoubleProperty('tileWidth', tileWidth));
|
||||
properties.add(DoubleProperty('tileHeight', tileHeight));
|
||||
properties.add(DiagnosticsProperty<bool>('showHeaders', showHeaders));
|
||||
}
|
||||
}
|
||||
|
@ -167,14 +171,15 @@ class SectionedListLayout<T> {
|
|||
final Map<SectionKey, List<T>> sections;
|
||||
final bool showHeaders;
|
||||
final int columnCount;
|
||||
final double tileExtent, spacing;
|
||||
final double tileWidth, tileHeight, spacing;
|
||||
final List<SectionLayout> sectionLayouts;
|
||||
|
||||
const SectionedListLayout({
|
||||
required this.sections,
|
||||
required this.showHeaders,
|
||||
required this.columnCount,
|
||||
required this.tileExtent,
|
||||
required this.tileWidth,
|
||||
required this.tileHeight,
|
||||
required this.spacing,
|
||||
required this.sectionLayouts,
|
||||
});
|
||||
|
@ -192,9 +197,9 @@ class SectionedListLayout<T> {
|
|||
final row = (sectionItemIndex / columnCount).floor();
|
||||
final listIndex = sectionLayout.firstIndex + 1 + row;
|
||||
|
||||
final left = tileExtent * column + spacing * (column - 1);
|
||||
final left = tileWidth * column + spacing * (column - 1);
|
||||
final top = sectionLayout.indexToLayoutOffset(listIndex);
|
||||
return Rect.fromLTWH(left, top, tileExtent, tileExtent);
|
||||
return Rect.fromLTWH(left, top, tileWidth, tileHeight);
|
||||
}
|
||||
|
||||
SectionLayout? getSectionAt(double offsetY) => sectionLayouts.firstWhereOrNull((sl) => offsetY < sl.maxOffset);
|
||||
|
@ -210,8 +215,8 @@ class SectionedListLayout<T> {
|
|||
dy -= sectionLayout.minOffset + sectionLayout.headerExtent;
|
||||
if (dy < 0) return null;
|
||||
|
||||
final row = dy ~/ (tileExtent + spacing);
|
||||
final column = position.dx ~/ (tileExtent + spacing);
|
||||
final row = dy ~/ (tileHeight + spacing);
|
||||
final column = position.dx ~/ (tileWidth + spacing);
|
||||
final index = row * columnCount + column;
|
||||
if (index >= section.length) return null;
|
||||
|
||||
|
@ -219,7 +224,7 @@ class SectionedListLayout<T> {
|
|||
}
|
||||
|
||||
@override
|
||||
String toString() => '$runtimeType#${shortHash(this)}{sectionCount=${sections.length} columnCount=$columnCount, tileExtent=$tileExtent}';
|
||||
String toString() => '$runtimeType#${shortHash(this)}{sectionCount=${sections.length} columnCount=$columnCount, tileWidth=$tileWidth, tileHeight=$tileHeight}';
|
||||
}
|
||||
|
||||
@immutable
|
||||
|
@ -227,11 +232,11 @@ class SectionLayout extends Equatable {
|
|||
final SectionKey sectionKey;
|
||||
final int firstIndex, lastIndex, bodyFirstIndex;
|
||||
final double minOffset, maxOffset, bodyMinOffset;
|
||||
final double headerExtent, tileExtent, spacing, mainAxisStride;
|
||||
final double headerExtent, tileHeight, spacing, mainAxisStride;
|
||||
final IndexedWidgetBuilder builder;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [sectionKey, firstIndex, lastIndex, minOffset, maxOffset, headerExtent, tileExtent, spacing];
|
||||
List<Object?> get props => [sectionKey, firstIndex, lastIndex, minOffset, maxOffset, headerExtent, tileHeight, spacing];
|
||||
|
||||
const SectionLayout({
|
||||
required this.sectionKey,
|
||||
|
@ -240,12 +245,12 @@ class SectionLayout extends Equatable {
|
|||
required this.minOffset,
|
||||
required this.maxOffset,
|
||||
required this.headerExtent,
|
||||
required this.tileExtent,
|
||||
required this.tileHeight,
|
||||
required this.spacing,
|
||||
required this.builder,
|
||||
}) : bodyFirstIndex = firstIndex + 1,
|
||||
bodyMinOffset = minOffset + headerExtent,
|
||||
mainAxisStride = tileExtent + spacing;
|
||||
mainAxisStride = tileHeight + spacing;
|
||||
|
||||
bool hasChild(int index) => firstIndex <= index && index <= lastIndex;
|
||||
|
||||
|
@ -271,11 +276,12 @@ class SectionLayout extends Equatable {
|
|||
}
|
||||
|
||||
class _GridRow extends MultiChildRenderObjectWidget {
|
||||
final double extent, spacing;
|
||||
final double width, height, spacing;
|
||||
|
||||
_GridRow({
|
||||
Key? key,
|
||||
required this.extent,
|
||||
required this.width,
|
||||
required this.height,
|
||||
required this.spacing,
|
||||
required List<Widget> children,
|
||||
}) : super(key: key, children: children);
|
||||
|
@ -283,21 +289,24 @@ class _GridRow extends MultiChildRenderObjectWidget {
|
|||
@override
|
||||
RenderObject createRenderObject(BuildContext context) {
|
||||
return _RenderGridRow(
|
||||
extent: extent,
|
||||
width: width,
|
||||
height: height,
|
||||
spacing: spacing,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, _RenderGridRow renderObject) {
|
||||
renderObject.extent = extent;
|
||||
renderObject.width = width;
|
||||
renderObject.height = height;
|
||||
renderObject.spacing = spacing;
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DoubleProperty('extent', extent));
|
||||
properties.add(DoubleProperty('width', width));
|
||||
properties.add(DoubleProperty('height', height));
|
||||
properties.add(DoubleProperty('spacing', spacing));
|
||||
}
|
||||
}
|
||||
|
@ -307,19 +316,30 @@ class _GridRowParentData extends ContainerBoxParentData<RenderBox> {}
|
|||
class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox, _GridRowParentData>, RenderBoxContainerDefaultsMixin<RenderBox, _GridRowParentData> {
|
||||
_RenderGridRow({
|
||||
List<RenderBox>? children,
|
||||
required double extent,
|
||||
required double width,
|
||||
required double height,
|
||||
required double spacing,
|
||||
}) : _extent = extent,
|
||||
}) : _width = width,
|
||||
_height = height,
|
||||
_spacing = spacing {
|
||||
addAll(children);
|
||||
}
|
||||
|
||||
double get extent => _extent;
|
||||
double _extent;
|
||||
double get width => _width;
|
||||
double _width;
|
||||
|
||||
set extent(double value) {
|
||||
if (_extent == value) return;
|
||||
_extent = value;
|
||||
set width(double value) {
|
||||
if (_width == value) return;
|
||||
_width = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
double get height => _height;
|
||||
double _height;
|
||||
|
||||
set height(double value) {
|
||||
if (_height == value) return;
|
||||
_height = value;
|
||||
markNeedsLayout();
|
||||
}
|
||||
|
||||
|
@ -339,7 +359,7 @@ class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
|||
}
|
||||
}
|
||||
|
||||
double get intrinsicWidth => extent * childCount + spacing * (childCount - 1);
|
||||
double get intrinsicWidth => width * childCount + spacing * (childCount - 1);
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicWidth(double height) => intrinsicWidth;
|
||||
|
@ -348,10 +368,10 @@ class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
|||
double computeMaxIntrinsicWidth(double height) => intrinsicWidth;
|
||||
|
||||
@override
|
||||
double computeMinIntrinsicHeight(double width) => extent;
|
||||
double computeMinIntrinsicHeight(double width) => height;
|
||||
|
||||
@override
|
||||
double computeMaxIntrinsicHeight(double width) => extent;
|
||||
double computeMaxIntrinsicHeight(double width) => height;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
|
@ -360,14 +380,14 @@ class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
|||
size = constraints.smallest;
|
||||
return;
|
||||
}
|
||||
size = Size(constraints.maxWidth, extent);
|
||||
final childConstraints = BoxConstraints.tight(Size(extent, extent));
|
||||
size = Size(constraints.maxWidth, height);
|
||||
final childConstraints = BoxConstraints.tight(Size(width, height));
|
||||
var offset = Offset.zero;
|
||||
while (child != null) {
|
||||
child.layout(childConstraints, parentUsesSize: false);
|
||||
final childParentData = child.parentData! as _GridRowParentData;
|
||||
childParentData.offset = offset;
|
||||
offset += Offset(extent + spacing, 0);
|
||||
offset += Offset(width + spacing, 0);
|
||||
child = childParentData.nextSibling;
|
||||
}
|
||||
}
|
||||
|
@ -390,7 +410,8 @@ class _RenderGridRow extends RenderBox with ContainerRenderObjectMixin<RenderBox
|
|||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DoubleProperty('extent', extent));
|
||||
properties.add(DoubleProperty('width', width));
|
||||
properties.add(DoubleProperty('height', height));
|
||||
properties.add(DoubleProperty('spacing', spacing));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,8 @@ import 'package:aves/model/filters/location.dart';
|
|||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/basic/menu.dart';
|
||||
import 'package:aves/widgets/common/providers/media_query_data_provider.dart';
|
||||
import 'package:aves/widgets/filter_grids/common/action_delegates/chip.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
|
@ -18,14 +18,28 @@ typedef OffsetFilterCallback = void Function(BuildContext context, CollectionFil
|
|||
|
||||
enum HeroType { always, onTap, never }
|
||||
|
||||
@immutable
|
||||
class AvesFilterDecoration {
|
||||
final Widget widget;
|
||||
final Radius radius;
|
||||
|
||||
const AvesFilterDecoration({
|
||||
required this.widget,
|
||||
required this.radius,
|
||||
});
|
||||
|
||||
BorderRadius get textBorderRadius => BorderRadius.vertical(bottom: radius);
|
||||
|
||||
BorderRadius get chipBorderRadius => BorderRadius.all(radius);
|
||||
}
|
||||
|
||||
class AvesFilterChip extends StatefulWidget {
|
||||
final CollectionFilter filter;
|
||||
final bool removable;
|
||||
final bool showGenericIcon;
|
||||
final Widget? background;
|
||||
final AvesFilterDecoration? decoration;
|
||||
final String? banner;
|
||||
final Widget? details;
|
||||
final BorderRadius? borderRadius;
|
||||
final double padding;
|
||||
final HeroType heroType;
|
||||
final FilterCallback? onTap;
|
||||
|
@ -37,16 +51,18 @@ class AvesFilterChip extends StatefulWidget {
|
|||
static const double minChipHeight = kMinInteractiveDimension;
|
||||
static const double minChipWidth = 80;
|
||||
static const double maxChipWidth = 160;
|
||||
static const double iconSize = 18;
|
||||
static const double fontSize = 14;
|
||||
static const double decoratedContentVerticalPadding = 5;
|
||||
|
||||
const AvesFilterChip({
|
||||
Key? key,
|
||||
required this.filter,
|
||||
this.removable = false,
|
||||
this.showGenericIcon = true,
|
||||
this.background,
|
||||
this.decoration,
|
||||
this.banner,
|
||||
this.details,
|
||||
this.borderRadius,
|
||||
this.padding = 6.0,
|
||||
this.heroType = HeroType.onTap,
|
||||
this.onTap,
|
||||
|
@ -140,15 +156,15 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final chipBackground = Theme.of(context).scaffoldBackgroundColor;
|
||||
final textScaleFactor = MediaQuery.textScaleFactorOf(context);
|
||||
final iconSize = 20 * textScaleFactor;
|
||||
|
||||
final hasBackground = widget.background != null;
|
||||
final leading = filter.iconBuilder(context, iconSize, showGenericIcon: widget.showGenericIcon, embossed: hasBackground);
|
||||
final iconSize = AvesFilterChip.iconSize * textScaleFactor;
|
||||
final leading = filter.iconBuilder(context, iconSize, showGenericIcon: widget.showGenericIcon);
|
||||
final trailing = widget.removable ? Icon(AIcons.clear, size: iconSize) : null;
|
||||
|
||||
final decoration = widget.decoration;
|
||||
Widget content = Row(
|
||||
mainAxisSize: hasBackground ? MainAxisSize.max : MainAxisSize.min,
|
||||
mainAxisSize: decoration != null ? MainAxisSize.max : MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
if (leading != null) ...[
|
||||
|
@ -158,6 +174,9 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
Flexible(
|
||||
child: Text(
|
||||
filter.getLabel(context),
|
||||
style: const TextStyle(
|
||||
fontSize: AvesFilterChip.fontSize,
|
||||
),
|
||||
softWrap: false,
|
||||
overflow: TextOverflow.fade,
|
||||
maxLines: 1,
|
||||
|
@ -170,36 +189,37 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
],
|
||||
);
|
||||
|
||||
if (widget.details != null) {
|
||||
final details = widget.details;
|
||||
if (details != null) {
|
||||
content = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
content,
|
||||
Flexible(child: widget.details!),
|
||||
Flexible(child: details),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
content = Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding * 2, vertical: 2),
|
||||
child: content,
|
||||
);
|
||||
|
||||
if (hasBackground) {
|
||||
content = Center(
|
||||
child: ColoredBox(
|
||||
color: Colors.black54,
|
||||
child: DefaultTextStyle(
|
||||
style: Theme.of(context).textTheme.bodyText2!.copyWith(
|
||||
shadows: Constants.embossShadows,
|
||||
),
|
||||
if (decoration != null) {
|
||||
content = Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: ClipRRect(
|
||||
borderRadius: decoration.textBorderRadius,
|
||||
child: Container(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding * 2, vertical: AvesFilterChip.decoratedContentVerticalPadding),
|
||||
color: chipBackground,
|
||||
child: content,
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
content = Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: padding * 2, vertical: 2),
|
||||
child: content,
|
||||
);
|
||||
}
|
||||
|
||||
final borderRadius = widget.borderRadius ?? const BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius));
|
||||
final borderRadius = decoration?.chipBorderRadius ?? const BorderRadius.all(Radius.circular(AvesFilterChip.defaultRadius));
|
||||
final banner = widget.banner;
|
||||
Widget chip = Container(
|
||||
constraints: const BoxConstraints(
|
||||
|
@ -210,13 +230,13 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
child: Stack(
|
||||
fit: StackFit.passthrough,
|
||||
children: [
|
||||
if (hasBackground)
|
||||
if (decoration != null)
|
||||
ClipRRect(
|
||||
borderRadius: borderRadius,
|
||||
child: widget.background,
|
||||
borderRadius: decoration.chipBorderRadius,
|
||||
child: decoration.widget,
|
||||
),
|
||||
Material(
|
||||
color: hasBackground ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor,
|
||||
color: decoration != null ? Colors.transparent : chipBackground,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
|
@ -248,7 +268,7 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
),
|
||||
position: DecorationPosition.foreground,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
padding: EdgeInsets.symmetric(vertical: decoration != null ? 0 : 8),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
|
@ -279,9 +299,11 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
chip = Hero(
|
||||
tag: filter,
|
||||
transitionOnUserGestures: true,
|
||||
child: DefaultTextStyle(
|
||||
style: const TextStyle(),
|
||||
child: chip,
|
||||
child: MediaQueryDataProvider(
|
||||
child: DefaultTextStyle(
|
||||
style: const TextStyle(),
|
||||
child: chip,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -19,14 +19,16 @@ class ScalerMetadata<T> {
|
|||
|
||||
class GridScaleGestureDetector<T> extends StatefulWidget {
|
||||
final GlobalKey scrollableKey;
|
||||
final Widget Function(Offset center, double extent, Widget child) gridBuilder;
|
||||
final Widget Function(T item, double extent) scaledBuilder;
|
||||
final double Function(double width) heightForWidth;
|
||||
final Widget Function(Offset center, Size tileSize, Widget child) gridBuilder;
|
||||
final Widget Function(T item, Size tileSize) scaledBuilder;
|
||||
final Object Function(T item)? highlightItem;
|
||||
final Widget child;
|
||||
|
||||
const GridScaleGestureDetector({
|
||||
Key? key,
|
||||
required this.scrollableKey,
|
||||
required this.heightForWidth,
|
||||
required this.gridBuilder,
|
||||
required this.scaledBuilder,
|
||||
this.highlightItem,
|
||||
|
@ -38,9 +40,10 @@ class GridScaleGestureDetector<T> extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T>> {
|
||||
double? _startExtent, _extentMin, _extentMax;
|
||||
Size? _startSize;
|
||||
double? _extentMin, _extentMax;
|
||||
bool _applyingScale = false;
|
||||
ValueNotifier<double>? _scaledExtentNotifier;
|
||||
ValueNotifier<Size>? _scaledSizeNotifier;
|
||||
OverlayEntry? _overlayEntry;
|
||||
ScalerMetadata<T>? _metadata;
|
||||
|
||||
|
@ -63,8 +66,8 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
|
|||
// abort if we cannot find an image to show on overlay
|
||||
if (renderMetaData == null) return;
|
||||
_metadata = renderMetaData.metaData;
|
||||
_startExtent = renderMetaData.size.width;
|
||||
_scaledExtentNotifier = ValueNotifier(_startExtent!);
|
||||
_startSize = renderMetaData.size;
|
||||
_scaledSizeNotifier = ValueNotifier(_startSize!);
|
||||
|
||||
// not the same as `MediaQuery.size.width`, because of screen insets/padding
|
||||
final gridWidth = scrollableBox.size.width;
|
||||
|
@ -73,33 +76,33 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
|
|||
_extentMin = tileExtentController.effectiveExtentMin;
|
||||
_extentMax = tileExtentController.effectiveExtentMax;
|
||||
|
||||
final halfExtent = _startExtent! / 2;
|
||||
final thumbnailCenter = renderMetaData.localToGlobal(Offset(halfExtent, halfExtent));
|
||||
final halfSize = _startSize! / 2;
|
||||
final thumbnailCenter = renderMetaData.localToGlobal(Offset(halfSize.width, halfSize.height));
|
||||
_overlayEntry = OverlayEntry(
|
||||
builder: (context) => ScaleOverlay(
|
||||
builder: (extent) => SizedBox(
|
||||
width: extent,
|
||||
height: extent,
|
||||
builder: (scaledTileSize) => SizedBox.fromSize(
|
||||
size: scaledTileSize,
|
||||
child: GridTheme(
|
||||
extent: extent,
|
||||
child: widget.scaledBuilder(_metadata!.item, extent),
|
||||
extent: scaledTileSize.width,
|
||||
child: widget.scaledBuilder(_metadata!.item, scaledTileSize),
|
||||
),
|
||||
),
|
||||
center: thumbnailCenter,
|
||||
viewportWidth: gridWidth,
|
||||
gridBuilder: widget.gridBuilder,
|
||||
scaledExtentNotifier: _scaledExtentNotifier!,
|
||||
scaledSizeNotifier: _scaledSizeNotifier!,
|
||||
),
|
||||
);
|
||||
Overlay.of(scrollableContext)!.insert(_overlayEntry!);
|
||||
},
|
||||
onScaleUpdate: (details) {
|
||||
if (_scaledExtentNotifier == null) return;
|
||||
if (_scaledSizeNotifier == null) return;
|
||||
final s = details.scale;
|
||||
_scaledExtentNotifier!.value = (_startExtent! * s).clamp(_extentMin!, _extentMax!);
|
||||
final scaledWidth = (_startSize!.width * s).clamp(_extentMin!, _extentMax!);
|
||||
_scaledSizeNotifier!.value = Size(scaledWidth, widget.heightForWidth(scaledWidth));
|
||||
},
|
||||
onScaleEnd: (details) {
|
||||
if (_scaledExtentNotifier == null) return;
|
||||
if (_scaledSizeNotifier == null) return;
|
||||
if (_overlayEntry != null) {
|
||||
_overlayEntry!.remove();
|
||||
_overlayEntry = null;
|
||||
|
@ -109,8 +112,8 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
|
|||
final tileExtentController = context.read<TileExtentController>();
|
||||
final oldExtent = tileExtentController.extentNotifier.value;
|
||||
// sanitize and update grid layout if necessary
|
||||
final newExtent = tileExtentController.setUserPreferredExtent(_scaledExtentNotifier!.value);
|
||||
_scaledExtentNotifier = null;
|
||||
final newExtent = tileExtentController.setUserPreferredExtent(_scaledSizeNotifier!.value.width);
|
||||
_scaledSizeNotifier = null;
|
||||
if (newExtent == oldExtent) {
|
||||
_applyingScale = false;
|
||||
} else {
|
||||
|
@ -138,18 +141,18 @@ class _GridScaleGestureDetectorState<T> extends State<GridScaleGestureDetector<T
|
|||
}
|
||||
|
||||
class ScaleOverlay extends StatefulWidget {
|
||||
final Widget Function(double extent) builder;
|
||||
final Widget Function(Size scaledTileSize) builder;
|
||||
final Offset center;
|
||||
final double viewportWidth;
|
||||
final ValueNotifier<double> scaledExtentNotifier;
|
||||
final Widget Function(Offset center, double extent, Widget child) gridBuilder;
|
||||
final ValueNotifier<Size> scaledSizeNotifier;
|
||||
final Widget Function(Offset center, Size extent, Widget child) gridBuilder;
|
||||
|
||||
const ScaleOverlay({
|
||||
Key? key,
|
||||
required this.builder,
|
||||
required this.center,
|
||||
required this.viewportWidth,
|
||||
required this.scaledExtentNotifier,
|
||||
required this.scaledSizeNotifier,
|
||||
required this.gridBuilder,
|
||||
}) : super(key: key);
|
||||
|
||||
|
@ -197,26 +200,28 @@ class _ScaleOverlayState extends State<ScaleOverlay> {
|
|||
),
|
||||
),
|
||||
duration: Durations.collectionScalingBackgroundAnimation,
|
||||
child: ValueListenableBuilder<double>(
|
||||
valueListenable: widget.scaledExtentNotifier,
|
||||
builder: (context, extent, child) {
|
||||
child: ValueListenableBuilder<Size>(
|
||||
valueListenable: widget.scaledSizeNotifier,
|
||||
builder: (context, scaledSize, child) {
|
||||
final width = scaledSize.width;
|
||||
final height = scaledSize.height;
|
||||
// keep scaled thumbnail within the screen
|
||||
final xMin = context.select<MediaQueryData, double>((mq) => mq.padding.left);
|
||||
final xMax = xMin + gridWidth;
|
||||
var dx = .0;
|
||||
if (center.dx - extent / 2 < xMin) {
|
||||
dx = xMin - (center.dx - extent / 2);
|
||||
} else if (center.dx + extent / 2 > xMax) {
|
||||
dx = xMax - (center.dx + extent / 2);
|
||||
if (center.dx - width / 2 < xMin) {
|
||||
dx = xMin - (center.dx - width / 2);
|
||||
} else if (center.dx + width / 2 > xMax) {
|
||||
dx = xMax - (center.dx + width / 2);
|
||||
}
|
||||
final clampedCenter = center.translate(dx, 0);
|
||||
|
||||
var child = widget.builder(extent);
|
||||
var child = widget.builder(scaledSize);
|
||||
child = Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
left: clampedCenter.dx - extent / 2,
|
||||
top: clampedCenter.dy - extent / 2,
|
||||
left: clampedCenter.dx - width / 2,
|
||||
top: clampedCenter.dy - height / 2,
|
||||
child: DefaultTextStyle(
|
||||
style: const TextStyle(),
|
||||
child: child,
|
||||
|
@ -224,7 +229,7 @@ class _ScaleOverlayState extends State<ScaleOverlay> {
|
|||
),
|
||||
],
|
||||
);
|
||||
child = widget.gridBuilder(clampedCenter, extent, child);
|
||||
child = widget.gridBuilder(clampedCenter, scaledSize, child);
|
||||
return child;
|
||||
},
|
||||
),
|
||||
|
@ -237,13 +242,14 @@ class _ScaleOverlayState extends State<ScaleOverlay> {
|
|||
|
||||
class GridPainter extends CustomPainter {
|
||||
final Offset center;
|
||||
final double extent, spacing, borderWidth;
|
||||
final Size tileSize;
|
||||
final double spacing, borderWidth;
|
||||
final Radius borderRadius;
|
||||
final Color color;
|
||||
|
||||
const GridPainter({
|
||||
required this.center,
|
||||
required this.extent,
|
||||
required this.tileSize,
|
||||
required this.spacing,
|
||||
required this.borderWidth,
|
||||
required this.borderRadius,
|
||||
|
@ -252,12 +258,15 @@ class GridPainter extends CustomPainter {
|
|||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final tileWidth = tileSize.width;
|
||||
final tileHeight = tileSize.height;
|
||||
|
||||
final strokePaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = borderWidth
|
||||
..shader = ui.Gradient.radial(
|
||||
center,
|
||||
extent * 2,
|
||||
tileWidth * 2,
|
||||
[
|
||||
color,
|
||||
Colors.transparent,
|
||||
|
@ -271,17 +280,18 @@ class GridPainter extends CustomPainter {
|
|||
..style = PaintingStyle.fill
|
||||
..color = color.withOpacity(.25);
|
||||
|
||||
final delta = extent + spacing;
|
||||
final deltaX = tileWidth + spacing;
|
||||
final deltaY = tileHeight + spacing;
|
||||
for (var i = -2; i <= 2; i++) {
|
||||
final dx = delta * i;
|
||||
final dx = deltaX * i;
|
||||
for (var j = -2; j <= 2; j++) {
|
||||
if (i == 0 && j == 0) continue;
|
||||
final dy = delta * j;
|
||||
final dy = deltaY * j;
|
||||
final rect = RRect.fromRectAndRadius(
|
||||
Rect.fromCenter(
|
||||
center: center + Offset(dx, dy),
|
||||
width: extent,
|
||||
height: extent,
|
||||
width: tileWidth,
|
||||
height: tileHeight,
|
||||
),
|
||||
borderRadius,
|
||||
);
|
||||
|
|
|
@ -12,6 +12,7 @@ import 'package:aves/model/source/tag.dart';
|
|||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/theme/icons.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/utils/color_utils.dart';
|
||||
import 'package:aves/utils/constants.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_filter_chip.dart';
|
||||
import 'package:aves/widgets/common/thumbnail/image.dart';
|
||||
|
@ -40,6 +41,22 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
|||
}) : thumbnailExtent = thumbnailExtent ?? extent,
|
||||
super(key: key);
|
||||
|
||||
static double tileHeight({required double extent, required double textScaleFactor}) {
|
||||
return extent + infoHeight(extent: extent, textScaleFactor: textScaleFactor);
|
||||
}
|
||||
|
||||
static double infoHeight({required double extent, required double textScaleFactor}) {
|
||||
// height can actually be a little larger or smaller, when info includes icons or non-latin scripts
|
||||
// but it's not worth measuring text metrics, as the widget is flexible enough to absorb the difference
|
||||
return (AvesFilterChip.fontSize + detailFontSize(extent) + 4) * textScaleFactor + AvesFilterChip.decoratedContentVerticalPadding * 2;
|
||||
}
|
||||
|
||||
static Radius radius(double extent) => Radius.circular(min<double>(AvesFilterChip.defaultRadius, extent / 4));
|
||||
|
||||
static double detailIconSize(double extent) => min<double>(AvesFilterChip.fontSize, extent / 8);
|
||||
|
||||
static double detailFontSize(double extent) => min<double>(AvesFilterChip.fontSize, extent / 6);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<CollectionSource>(
|
||||
|
@ -50,7 +67,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
|||
final album = (filter as AlbumFilter).album;
|
||||
return StreamBuilder<AlbumSummaryInvalidatedEvent>(
|
||||
stream: source.eventBus.on<AlbumSummaryInvalidatedEvent>().where((event) => event.directories == null || event.directories!.contains(album)),
|
||||
builder: (context, snapshot) => _buildChip(source),
|
||||
builder: (context, snapshot) => _buildChip(context, source),
|
||||
);
|
||||
}
|
||||
case LocationFilter:
|
||||
|
@ -58,7 +75,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
|||
final countryCode = (filter as LocationFilter).countryCode;
|
||||
return StreamBuilder<CountrySummaryInvalidatedEvent>(
|
||||
stream: source.eventBus.on<CountrySummaryInvalidatedEvent>().where((event) => event.countryCodes == null || event.countryCodes!.contains(countryCode)),
|
||||
builder: (context, snapshot) => _buildChip(source),
|
||||
builder: (context, snapshot) => _buildChip(context, source),
|
||||
);
|
||||
}
|
||||
case TagFilter:
|
||||
|
@ -66,7 +83,7 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
|||
final tag = (filter as TagFilter).tag;
|
||||
return StreamBuilder<TagSummaryInvalidatedEvent>(
|
||||
stream: source.eventBus.on<TagSummaryInvalidatedEvent>().where((event) => event.tags == null || event.tags!.contains(tag)),
|
||||
builder: (context, snapshot) => _buildChip(source),
|
||||
builder: (context, snapshot) => _buildChip(context, source),
|
||||
);
|
||||
}
|
||||
default:
|
||||
|
@ -76,38 +93,53 @@ class CoveredFilterChip<T extends CollectionFilter> extends StatelessWidget {
|
|||
);
|
||||
}
|
||||
|
||||
static Radius radius(double extent) => Radius.circular(min<double>(AvesFilterChip.defaultRadius, extent / 4));
|
||||
|
||||
Widget _buildChip(CollectionSource source) {
|
||||
Widget _buildChip(BuildContext context, CollectionSource source) {
|
||||
final entry = coverEntry ?? source.coverEntry(filter);
|
||||
final backgroundImage = entry == null
|
||||
? Container(color: Colors.white)
|
||||
: ThumbnailImage(
|
||||
entry: entry,
|
||||
extent: thumbnailExtent,
|
||||
);
|
||||
final titlePadding = min<double>(4.0, extent / 32);
|
||||
return SizedBox(
|
||||
width: extent,
|
||||
height: extent,
|
||||
child: AvesFilterChip(
|
||||
filter: filter,
|
||||
showGenericIcon: false,
|
||||
background: backgroundImage,
|
||||
banner: banner,
|
||||
details: _buildDetails(source, filter),
|
||||
borderRadius: BorderRadius.all(radius(extent)),
|
||||
padding: titlePadding,
|
||||
onTap: onTap,
|
||||
onLongPress: null,
|
||||
return AvesFilterChip(
|
||||
filter: filter,
|
||||
showGenericIcon: false,
|
||||
decoration: AvesFilterDecoration(
|
||||
widget: Selector<MediaQueryData, double>(
|
||||
selector: (context, mq) => mq.textScaleFactor,
|
||||
builder: (context, textScaleFactor, child) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(bottom: infoHeight(extent: extent, textScaleFactor: textScaleFactor)),
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: entry == null
|
||||
? Container(
|
||||
decoration: BoxDecoration(
|
||||
gradient: LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
Colors.white,
|
||||
stringToColor(filter.getLabel(context)),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
: ThumbnailImage(
|
||||
entry: entry,
|
||||
extent: thumbnailExtent,
|
||||
),
|
||||
),
|
||||
radius: radius(extent),
|
||||
),
|
||||
banner: banner,
|
||||
details: _buildDetails(source, filter),
|
||||
padding: titlePadding,
|
||||
onTap: onTap,
|
||||
onLongPress: null,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDetails(CollectionSource source, T filter) {
|
||||
final padding = min<double>(8.0, extent / 16);
|
||||
final iconSize = min<double>(14.0, extent / 8);
|
||||
final fontSize = min<double>(14.0, extent / 6);
|
||||
final iconSize = detailIconSize(extent);
|
||||
final fontSize = detailFontSize(extent);
|
||||
return Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
|
@ -238,56 +238,66 @@ class _FilterGridContent<T extends CollectionFilter> extends StatelessWidget {
|
|||
final sectionedListLayoutProvider = ValueListenableBuilder<double>(
|
||||
valueListenable: context.select<TileExtentController, ValueNotifier<double>>((controller) => controller.extentNotifier),
|
||||
builder: (context, tileExtent, child) {
|
||||
return GridTheme(
|
||||
extent: tileExtent,
|
||||
child: Selector<TileExtentController, Tuple3<double, int, double>>(
|
||||
selector: (context, c) => Tuple3(c.viewportSize.width, c.columnCount, c.spacing),
|
||||
builder: (context, c, child) {
|
||||
final scrollableWidth = c.item1;
|
||||
final columnCount = c.item2;
|
||||
final tileSpacing = c.item3;
|
||||
// do not listen for animation delay change
|
||||
final controller = Provider.of<TileExtentController>(context, listen: false);
|
||||
final tileAnimationDelay = controller.getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
|
||||
return SectionedFilterListLayoutProvider<T>(
|
||||
sections: visibleSections,
|
||||
showHeaders: showHeaders,
|
||||
scrollableWidth: scrollableWidth,
|
||||
columnCount: columnCount,
|
||||
spacing: tileSpacing,
|
||||
tileExtent: tileExtent,
|
||||
tileBuilder: (gridItem) {
|
||||
final filter = gridItem.filter;
|
||||
return MetaData(
|
||||
metaData: ScalerMetadata(gridItem),
|
||||
child: FilterChipGridDecorator<T, FilterGridItem<T>>(
|
||||
gridItem: gridItem,
|
||||
extent: tileExtent,
|
||||
child: CoveredFilterChip(
|
||||
key: Key(filter.key),
|
||||
filter: filter,
|
||||
return Selector<TileExtentController, Tuple3<double, int, double>>(
|
||||
selector: (context, c) => Tuple3(c.viewportSize.width, c.columnCount, c.spacing),
|
||||
builder: (context, c, child) {
|
||||
final scrollableWidth = c.item1;
|
||||
final columnCount = c.item2;
|
||||
final tileSpacing = c.item3;
|
||||
// do not listen for animation delay change
|
||||
final tileAnimationDelay = context.read<TileExtentController>().getTileAnimationDelay(Durations.staggeredAnimationPageTarget);
|
||||
return Selector<MediaQueryData, double>(
|
||||
selector: (context, mq) => mq.textScaleFactor,
|
||||
builder: (context, textScaleFactor, child) {
|
||||
final tileHeight = CoveredFilterChip.tileHeight(extent: tileExtent, textScaleFactor: textScaleFactor);
|
||||
return GridTheme(
|
||||
extent: tileExtent,
|
||||
child: SectionedFilterListLayoutProvider<T>(
|
||||
sections: visibleSections,
|
||||
showHeaders: showHeaders,
|
||||
scrollableWidth: scrollableWidth,
|
||||
columnCount: columnCount,
|
||||
spacing: tileSpacing,
|
||||
tileWidth: tileExtent,
|
||||
tileHeight: tileHeight,
|
||||
tileBuilder: (gridItem) {
|
||||
final filter = gridItem.filter;
|
||||
return MetaData(
|
||||
metaData: ScalerMetadata(gridItem),
|
||||
child: FilterChipGridDecorator<T, FilterGridItem<T>>(
|
||||
gridItem: gridItem,
|
||||
extent: tileExtent,
|
||||
pinned: pinnedFilters.contains(filter),
|
||||
banner: newFilters.contains(filter) ? context.l10n.newFilterBanner : null,
|
||||
onTap: onTap,
|
||||
child: CoveredFilterChip(
|
||||
key: Key(filter.key),
|
||||
filter: filter,
|
||||
extent: tileExtent,
|
||||
pinned: pinnedFilters.contains(filter),
|
||||
banner: newFilters.contains(filter) ? context.l10n.newFilterBanner : null,
|
||||
onTap: onTap,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
tileAnimationDelay: tileAnimationDelay,
|
||||
child: _FilterSectionedContent<T>(
|
||||
appBar: appBar,
|
||||
appBarHeightNotifier: _appBarHeightNotifier,
|
||||
visibleSections: visibleSections,
|
||||
sortFactor: sortFactor,
|
||||
selectable: selectable,
|
||||
emptyBuilder: emptyBuilder,
|
||||
scrollController: PrimaryScrollController.of(context)!,
|
||||
);
|
||||
},
|
||||
tileAnimationDelay: tileAnimationDelay,
|
||||
child: child!,
|
||||
),
|
||||
);
|
||||
}),
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
},
|
||||
child: _FilterSectionedContent<T>(
|
||||
appBar: appBar,
|
||||
appBarHeightNotifier: _appBarHeightNotifier,
|
||||
visibleSections: visibleSections,
|
||||
sortFactor: sortFactor,
|
||||
selectable: selectable,
|
||||
emptyBuilder: emptyBuilder,
|
||||
scrollController: PrimaryScrollController.of(context)!,
|
||||
),
|
||||
);
|
||||
return sectionedListLayoutProvider;
|
||||
},
|
||||
|
@ -399,24 +409,26 @@ class _FilterScaler<T extends CollectionFilter> extends StatelessWidget {
|
|||
Widget build(BuildContext context) {
|
||||
final pinnedFilters = settings.pinnedFilters;
|
||||
final tileSpacing = context.select<TileExtentController, double>((controller) => controller.spacing);
|
||||
final textScaleFactor = context.select<MediaQueryData, double>((mq) => mq.textScaleFactor);
|
||||
return GridScaleGestureDetector<FilterGridItem<T>>(
|
||||
scrollableKey: scrollableKey,
|
||||
gridBuilder: (center, extent, child) => CustomPaint(
|
||||
heightForWidth: (width) => CoveredFilterChip.tileHeight(extent: width, textScaleFactor: textScaleFactor),
|
||||
gridBuilder: (center, tileSize, child) => CustomPaint(
|
||||
painter: GridPainter(
|
||||
center: center,
|
||||
extent: extent,
|
||||
tileSize: tileSize,
|
||||
spacing: tileSpacing,
|
||||
borderWidth: AvesFilterChip.outlineWidth,
|
||||
borderRadius: CoveredFilterChip.radius(extent),
|
||||
borderRadius: CoveredFilterChip.radius(tileSize.width),
|
||||
color: Colors.grey.shade700,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
scaledBuilder: (item, extent) {
|
||||
scaledBuilder: (item, tileSize) {
|
||||
final filter = item.filter;
|
||||
return CoveredFilterChip(
|
||||
filter: filter,
|
||||
extent: extent,
|
||||
extent: tileSize.width,
|
||||
thumbnailExtent: context.read<TileExtentController>().effectiveExtentMax,
|
||||
pinned: pinnedFilters.contains(filter),
|
||||
);
|
||||
|
|
|
@ -13,7 +13,8 @@ class SectionedFilterListLayoutProvider<T extends CollectionFilter> extends Sect
|
|||
required double scrollableWidth,
|
||||
required int columnCount,
|
||||
required double spacing,
|
||||
required double tileExtent,
|
||||
required double tileWidth,
|
||||
required double tileHeight,
|
||||
required Widget Function(FilterGridItem<T> gridItem) tileBuilder,
|
||||
required Duration tileAnimationDelay,
|
||||
required Widget child,
|
||||
|
@ -22,7 +23,8 @@ class SectionedFilterListLayoutProvider<T extends CollectionFilter> extends Sect
|
|||
scrollableWidth: scrollableWidth,
|
||||
columnCount: columnCount,
|
||||
spacing: spacing,
|
||||
tileExtent: tileExtent,
|
||||
tileWidth: tileWidth,
|
||||
tileHeight: tileHeight,
|
||||
tileBuilder: tileBuilder,
|
||||
tileAnimationDelay: tileAnimationDelay,
|
||||
child: child,
|
||||
|
|
Loading…
Reference in a new issue