mirror of
https://github.com/localsend/localsend.git
synced 2025-12-23 23:28:22 -05:00
refactor: replace HorizontalClipListView with ResponsiveWrapView (#2821)
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
76
app/lib/widget/responsive_wrap_view.dart
Normal file
76
app/lib/widget/responsive_wrap_view.dart
Normal 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(),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user