reviewed filter chip layout
This commit is contained in:
parent
ccb9482221
commit
cb21761a47
9 changed files with 74 additions and 56 deletions
|
@ -4,7 +4,8 @@ import 'package:flutter/material.dart';
|
|||
import 'package:provider/provider.dart';
|
||||
|
||||
class FilterBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
static const double preferredHeight = kMinInteractiveDimension;
|
||||
static const double verticalPadding = 16;
|
||||
static const double preferredHeight = AvesFilterChip.minChipHeight + verticalPadding;
|
||||
|
||||
@override
|
||||
final Size preferredSize = const Size.fromHeight(preferredHeight);
|
||||
|
@ -26,7 +27,7 @@ class FilterBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.symmetric(horizontal: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= filters.length) return null;
|
||||
final filter = filters[index];
|
||||
|
|
|
@ -17,6 +17,9 @@ class ExpandableFilterRow extends StatelessWidget {
|
|||
@required this.onPressed,
|
||||
});
|
||||
|
||||
static const double horizontalPadding = 8;
|
||||
static const double verticalPadding = 8;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (filters.isEmpty) return const SizedBox.shrink();
|
||||
|
@ -48,12 +51,13 @@ class ExpandableFilterRow extends StatelessWidget {
|
|||
final filtersList = filters.toList();
|
||||
final wrap = Container(
|
||||
key: ValueKey('wrap$title'),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2 + 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: horizontalPadding),
|
||||
// specify transparent as a workaround to prevent
|
||||
// chip border clipping when the floating app bar is fading
|
||||
color: Colors.transparent,
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
spacing: horizontalPadding,
|
||||
runSpacing: verticalPadding,
|
||||
children: filtersList
|
||||
.map((filter) => AvesFilterChip(
|
||||
filter: filter,
|
||||
|
@ -67,19 +71,17 @@ class ExpandableFilterRow extends StatelessWidget {
|
|||
// specify transparent as a workaround to prevent
|
||||
// chip border clipping when the floating app bar is fading
|
||||
color: Colors.transparent,
|
||||
height: kMinInteractiveDimension,
|
||||
height: AvesFilterChip.minChipHeight,
|
||||
child: ListView.separated(
|
||||
scrollDirection: Axis.horizontal,
|
||||
physics: const BouncingScrollPhysics(),
|
||||
padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.symmetric(horizontal: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: horizontalPadding),
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= filtersList.length) return null;
|
||||
final filter = filtersList[index];
|
||||
return Center(
|
||||
child: AvesFilterChip(
|
||||
filter: filter,
|
||||
onPressed: onPressed,
|
||||
),
|
||||
return AvesFilterChip(
|
||||
filter: filter,
|
||||
onPressed: onPressed,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (context, index) => const SizedBox(width: 8),
|
||||
|
|
|
@ -58,6 +58,7 @@ class ImageSearchDelegate extends SearchDelegate<CollectionFilter> {
|
|||
valueListenable: expandedSectionNotifier,
|
||||
builder: (context, expandedSection, child) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
children: [
|
||||
_buildFilterRow(
|
||||
context: context,
|
||||
|
|
|
@ -13,7 +13,8 @@ class AvesFilterChip extends StatefulWidget {
|
|||
final FilterCallback onPressed;
|
||||
|
||||
static final BorderRadius borderRadius = BorderRadius.circular(32);
|
||||
static const double buttonBorderWidth = 2;
|
||||
static const double outlineWidth = 2;
|
||||
static const double minChipHeight = kMinInteractiveDimension;
|
||||
static const double minChipWidth = 80;
|
||||
static const double maxChipWidth = 160;
|
||||
static const double iconSize = 20;
|
||||
|
@ -116,49 +117,59 @@ class _AvesFilterChipState extends State<AvesFilterChip> {
|
|||
);
|
||||
}
|
||||
|
||||
final shape = RoundedRectangleBorder(
|
||||
borderRadius: AvesFilterChip.borderRadius,
|
||||
);
|
||||
final borderRadius = AvesFilterChip.borderRadius;
|
||||
|
||||
final button = ButtonTheme(
|
||||
minWidth: 0,
|
||||
child: Container(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: AvesFilterChip.minChipWidth,
|
||||
maxWidth: AvesFilterChip.maxChipWidth,
|
||||
),
|
||||
decoration: widget.decoration,
|
||||
child: Tooltip(
|
||||
message: filter.tooltip,
|
||||
preferBelow: false,
|
||||
child: FutureBuilder(
|
||||
future: _colorFuture,
|
||||
builder: (context, AsyncSnapshot<Color> snapshot) {
|
||||
return OutlineButton(
|
||||
onPressed: widget.onPressed != null ? () => widget.onPressed(filter) : null,
|
||||
borderSide: BorderSide(
|
||||
color: snapshot.hasData ? snapshot.data : Colors.transparent,
|
||||
width: AvesFilterChip.buttonBorderWidth,
|
||||
),
|
||||
padding: EdgeInsets.zero,
|
||||
shape: shape,
|
||||
child: content,
|
||||
);
|
||||
},
|
||||
final chip = Container(
|
||||
constraints: const BoxConstraints(
|
||||
minWidth: AvesFilterChip.minChipWidth,
|
||||
maxWidth: AvesFilterChip.maxChipWidth,
|
||||
minHeight: AvesFilterChip.minChipHeight,
|
||||
),
|
||||
decoration: widget.decoration,
|
||||
child: Tooltip(
|
||||
message: filter.tooltip,
|
||||
preferBelow: false,
|
||||
child: Material(
|
||||
color: widget.decoration != null ? Colors.transparent : Theme.of(context).scaffoldBackgroundColor,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: widget.onPressed != null ? () => widget.onPressed(filter) : null,
|
||||
borderRadius: borderRadius,
|
||||
child: FutureBuilder(
|
||||
future: _colorFuture,
|
||||
builder: (context, AsyncSnapshot<Color> snapshot) {
|
||||
final outlineColor = snapshot.hasData ? snapshot.data : Colors.transparent;
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(
|
||||
color: outlineColor,
|
||||
width: AvesFilterChip.outlineWidth,
|
||||
),
|
||||
borderRadius: borderRadius,
|
||||
),
|
||||
position: DecorationPosition.foreground,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: content,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return button;
|
||||
// TODO TLAD how to lerp between chip and grid tile
|
||||
// return Hero(
|
||||
// tag: filter,
|
||||
// flightShuttleBuilder: (flight, animation, direction, fromHeroContext, toHeroContext) {
|
||||
// final toHero = toHeroContext.widget as Hero;
|
||||
// return Center(content: toHero.content);
|
||||
// },
|
||||
// content: button,
|
||||
// );
|
||||
// TODO TLAD only hero between `FilterBar` and chips that are tapped
|
||||
return Hero(
|
||||
tag: filter,
|
||||
flightShuttleBuilder: (flight, animation, direction, fromHeroContext, toHeroContext) {
|
||||
final toHero = toHeroContext.widget as Hero;
|
||||
return Center(child: toHero.child);
|
||||
},
|
||||
child: chip,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -87,7 +87,7 @@ class FilterGridPage extends StatelessWidget {
|
|||
slivers: [
|
||||
appBar,
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(AvesFilterChip.buttonBorderWidth),
|
||||
padding: const EdgeInsets.all(AvesFilterChip.outlineWidth),
|
||||
sliver: SliverGrid(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, i) {
|
||||
|
|
|
@ -57,9 +57,10 @@ class BasicSection extends StatelessWidget {
|
|||
]..sort();
|
||||
if (filters.isEmpty) return const SizedBox.shrink();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.only(top: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: filters
|
||||
.map((filter) => AvesFilterChip(
|
||||
filter: filter,
|
||||
|
|
|
@ -169,7 +169,7 @@ class SectionRow extends StatelessWidget {
|
|||
final buildDivider = () => const SizedBox(
|
||||
width: dim,
|
||||
child: Divider(
|
||||
thickness: AvesFilterChip.buttonBorderWidth,
|
||||
thickness: AvesFilterChip.outlineWidth,
|
||||
color: Colors.white70,
|
||||
),
|
||||
);
|
||||
|
|
|
@ -110,9 +110,10 @@ class _LocationSectionState extends State<LocationSection> {
|
|||
),
|
||||
if (filters.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.buttonBorderWidth / 2) + const EdgeInsets.only(top: 8),
|
||||
padding: const EdgeInsets.symmetric(horizontal: AvesFilterChip.outlineWidth / 2) + const EdgeInsets.only(top: 8),
|
||||
child: Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: filters
|
||||
.map((filter) => AvesFilterChip(
|
||||
filter: filter,
|
||||
|
|
|
@ -36,7 +36,7 @@ class FilterTable extends StatelessWidget {
|
|||
final lineHeight = 16 * textScaleFactor;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsetsDirectional.only(start: AvesFilterChip.buttonBorderWidth / 2 + 6, end: 8),
|
||||
padding: const EdgeInsetsDirectional.only(start: AvesFilterChip.outlineWidth / 2 + 6, end: 8),
|
||||
child: LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final showPercentIndicator = constraints.maxWidth - (chipWidth + countWidth) > percentIndicatorMinWidth;
|
||||
|
@ -48,7 +48,8 @@ class FilterTable extends StatelessWidget {
|
|||
final percent = count / maxCount;
|
||||
return TableRow(
|
||||
children: [
|
||||
Align(
|
||||
Container(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
alignment: AlignmentDirectional.centerStart,
|
||||
child: AvesFilterChip(
|
||||
filter: filter,
|
||||
|
|
Loading…
Reference in a new issue