refactor: replace HorizontalClipListView with ResponsiveWrapView (#2821)

This commit is contained in:
Ankit Bhankharia
2025-11-14 17:32:06 +05:30
committed by GitHub
parent 62f4a13166
commit 03fbd70607
3 changed files with 78 additions and 146 deletions

View File

@@ -25,7 +25,7 @@ import 'package:localsend_app/widget/custom_icon_button.dart';
import 'package:localsend_app/widget/dialogs/add_file_dialog.dart';
import 'package:localsend_app/widget/dialogs/send_mode_help_dialog.dart';
import 'package:localsend_app/widget/file_thumbnail.dart';
import 'package:localsend_app/widget/horizontal_clip_list_view.dart';
import 'package:localsend_app/widget/responsive_wrap_view.dart';
import 'package:localsend_app/widget/list_tile/device_list_tile.dart';
import 'package:localsend_app/widget/list_tile/device_placeholder_list_tile.dart';
import 'package:localsend_app/widget/opacity_slideshow.dart';
@@ -64,7 +64,7 @@ class SendTab extends StatelessWidget {
style: Theme.of(context).textTheme.titleMedium,
),
),
HorizontalClipListView(
ResponsiveWrapView(
outerHorizontalPadding: 15,
outerVerticalPadding: 10,
childPadding: 10,

View File

@@ -1,144 +0,0 @@
import 'package:flutter/material.dart';
/// A horizontal list that adjusts the width if the screen is too small.
/// In this case, the width increases until 10% - 50% of the next button is visible.
/// This is useful to communicate to the user that there are more buttons to the right.
class HorizontalClipListView extends StatelessWidget {
final double outerHorizontalPadding;
final double outerVerticalPadding;
final double childPadding;
final double minChildWidth;
final List<Widget> children;
const HorizontalClipListView({
super.key,
required this.outerHorizontalPadding,
required this.outerVerticalPadding,
required this.childPadding,
required this.minChildWidth,
required this.children,
});
@override
Widget build(BuildContext context) {
return LayoutBuilder(
builder: (context, constraints) {
final childWidth = _calcOptimalButtonWidth(
availableWidth: constraints.maxWidth,
paddingLeft: outerHorizontalPadding,
childrenCount: children.length,
minChildWidth: minChildWidth,
childPadding: childPadding,
);
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Padding(
padding: EdgeInsets.symmetric(
horizontal: outerHorizontalPadding,
vertical: outerVerticalPadding,
),
child: Row(
children: [
for (int i = 0; i < children.length; i++)
i == children.length - 1
? SizedBox(
width: childWidth,
child: children[i],
)
: Padding(
padding: EdgeInsetsDirectional.only(end: childPadding),
child: SizedBox(
width: childWidth,
child: children[i],
),
),
],
),
),
);
},
);
}
}
double _calcOptimalButtonWidth({
required double availableWidth,
required double paddingLeft,
required int childrenCount,
required double minChildWidth,
required double childPadding,
}) {
int childWidth = minChildWidth.toInt();
while (true) {
if (_fitsOnScreen(
availableWidth: availableWidth,
paddingLeft: paddingLeft,
childrenCount: childrenCount,
childWidth: childWidth.toDouble(),
childPadding: childPadding,
) ||
_fitsPartially(
availableWidth: availableWidth,
paddingLeft: paddingLeft,
childrenCount: childrenCount,
childWidth: childWidth.toDouble(),
childPadding: childPadding,
)) {
return childWidth.toDouble();
}
childWidth++;
}
}
bool _fitsOnScreen({
required double availableWidth,
required double paddingLeft,
required int childrenCount,
required double childWidth,
required double childPadding,
}) {
return paddingLeft + childrenCount * childWidth + (childrenCount - 1) * childPadding <= availableWidth;
}
bool _fitsPartially({
required double availableWidth,
required double paddingLeft,
required int childrenCount,
required double childWidth,
required double childPadding,
}) {
for (int i = 2; i <= childrenCount; i++) {
final minWidth = _calcTotalWidthWithPartialLastItem(
paddingLeft: paddingLeft,
childrenCount: i,
childWidth: childWidth,
childPadding: childPadding,
lastItemPercentage: 0.1,
);
final maxWidth = _calcTotalWidthWithPartialLastItem(
paddingLeft: paddingLeft,
childrenCount: i,
childWidth: childWidth,
childPadding: childPadding,
lastItemPercentage: 0.5,
);
if (minWidth <= availableWidth && maxWidth > availableWidth) {
return true;
}
}
return false;
}
@pragma('vm:prefer-inline')
@pragma('dart2js:tryInline')
double _calcTotalWidthWithPartialLastItem({
required double paddingLeft,
required int childrenCount,
required double childWidth,
required double childPadding,
required double lastItemPercentage,
}) {
return paddingLeft + (childrenCount - 1) * childWidth + childWidth * lastItemPercentage + (childrenCount - 1) * childPadding;
}

View File

@@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
/// A widget that displays a list of children in a responsive, wrapping grid.
///
/// It lays out children horizontally, and when a row is full, it wraps
/// to the next line. It calculates an optimal, equal width for all children
/// to make them fill the available width, while ensuring they are never
/// smaller than [minChildWidth].
class ResponsiveWrapView extends StatelessWidget {
final double outerHorizontalPadding;
final double outerVerticalPadding;
final double childPadding;
final double minChildWidth;
final List<Widget> children;
const ResponsiveWrapView({
super.key,
required this.outerHorizontalPadding,
required this.outerVerticalPadding,
required this.childPadding,
required this.minChildWidth,
required this.children,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: EdgeInsets.symmetric(
horizontal: outerHorizontalPadding,
vertical: outerVerticalPadding,
),
child: LayoutBuilder(
builder: (context, constraints) {
final double availableWidth = constraints.maxWidth;
if (availableWidth < minChildWidth) {
return Wrap(
runSpacing: childPadding,
children: children
.map(
(child) => SizedBox(
width: availableWidth,
child: child,
),
)
.toList(),
);
}
// Calculate how many items *can* fit in a row at their minimum size.
// Formula: n * minChildWidth + (n - 1) * childPadding <= availableWidth
// This simplifies to: n <= (availableWidth + childPadding) / (minChildWidth + childPadding)
final int numItemsPerRow = ((availableWidth + childPadding) / (minChildWidth + childPadding)).floor();
// Calculate the *actual* width to make `numItemsPerRow` items
// fill the `availableWidth` perfectly.
// Formula: numItemsPerRow * actualWidth + (numItemsPerRow - 1) * childPadding = availableWidth
final double actualChildWidth = (availableWidth - (numItemsPerRow - 1) * childPadding) / numItemsPerRow;
return Wrap(
spacing: childPadding,
runSpacing: childPadding,
children: children
.map(
(child) => SizedBox(
width: actualChildWidth,
child: child,
),
)
.toList(),
);
},
),
);
}
}