refactor: replace flutter_reorderable_grid_view with custom _ReorderableGrid implementation

feature/rearrange-buttons-2
idubnori 2025-12-10 09:09:15 +07:00
parent 17361d189c
commit 598e856322
3 changed files with 115 additions and 31 deletions

@ -1,7 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_reorderable_grid_view/widgets/reorderable_builder.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/providers/infrastructure/viewer_quick_action_order.provider.dart';
import 'package:immich_mobile/utils/action_button.utils.dart';
@ -32,9 +31,10 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
super.dispose();
}
void _onReorder(ReorderedListFunction<ActionButtonType> reorder) {
void _onReorder(int oldIndex, int newIndex) {
setState(() {
_order = reorder(_order);
final item = _order.removeAt(oldIndex);
_order.insert(newIndex, item);
_hasLocalChanges = true;
});
}
@ -101,31 +101,22 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
final rows = (_order.length / crossAxisCount).ceil().clamp(1, 4);
final naturalHeight = rows * tileHeight + (rows - 1) * mainAxisSpacing;
final shouldScroll = naturalHeight > constraints.maxHeight;
final horizontalPadding = 8.0; // matches GridView padding
final horizontalPadding = 8.0;
final tileWidth =
(constraints.maxWidth - horizontalPadding - (crossAxisSpacing * (crossAxisCount - 1))) /
crossAxisCount;
final childAspectRatio = tileWidth / tileHeight;
final gridController = shouldScroll ? _scrollController : null;
return ReorderableBuilder<ActionButtonType>(
onReorder: _onReorder,
enableLongPress: false,
return _ReorderableGrid(
scrollController: gridController,
children: [
for (var i = 0; i < _order.length; i++)
_QuickActionTile(key: ValueKey(_order[i].name), index: i, type: _order[i]),
],
builder: (children) => GridView.count(
controller: gridController,
crossAxisCount: crossAxisCount,
crossAxisSpacing: crossAxisSpacing,
mainAxisSpacing: mainAxisSpacing,
// padding: const EdgeInsets.fromLTRB(4, 0, 4, 12),
physics: shouldScroll ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(),
childAspectRatio: childAspectRatio,
children: children,
),
items: _order,
onReorder: _onReorder,
crossAxisCount: crossAxisCount,
crossAxisSpacing: crossAxisSpacing,
mainAxisSpacing: mainAxisSpacing,
childAspectRatio: childAspectRatio,
shouldScroll: shouldScroll,
);
},
),
@ -151,11 +142,113 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
}
}
class _ReorderableGrid extends StatefulWidget {
final ScrollController? scrollController;
final List<ActionButtonType> items;
final Function(int oldIndex, int newIndex) onReorder;
final int crossAxisCount;
final double crossAxisSpacing;
final double mainAxisSpacing;
final double childAspectRatio;
final bool shouldScroll;
const _ReorderableGrid({
required this.scrollController,
required this.items,
required this.onReorder,
required this.crossAxisCount,
required this.crossAxisSpacing,
required this.mainAxisSpacing,
required this.childAspectRatio,
required this.shouldScroll,
});
@override
State<_ReorderableGrid> createState() => _ReorderableGridState();
}
class _ReorderableGridState extends State<_ReorderableGrid> {
int? _draggingIndex;
int? _hoveringIndex;
@override
Widget build(BuildContext context) {
return GridView.builder(
controller: widget.scrollController,
physics: widget.shouldScroll ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(),
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: widget.crossAxisCount,
crossAxisSpacing: widget.crossAxisSpacing,
mainAxisSpacing: widget.mainAxisSpacing,
childAspectRatio: widget.childAspectRatio,
),
itemCount: widget.items.length,
itemBuilder: (context, index) {
final item = widget.items[index];
final isDragging = _draggingIndex == index;
final isHovering = _hoveringIndex == index;
return DragTarget<int>(
onWillAcceptWithDetails: (details) {
if (details.data != index) {
setState(() => _hoveringIndex = index);
}
return details.data != index;
},
onLeave: (_) {
setState(() => _hoveringIndex = null);
},
onAcceptWithDetails: (details) {
final oldIndex = details.data;
if (oldIndex != index) {
widget.onReorder(oldIndex, index);
}
setState(() {
_hoveringIndex = null;
_draggingIndex = null;
});
},
builder: (context, candidateData, rejectedData) {
return LongPressDraggable<int>(
data: index,
feedback: Material(
color: Colors.transparent,
child: Opacity(
opacity: 0.8,
child: Transform.scale(
scale: 1.1,
child: _QuickActionTile(index: index, type: item),
),
),
),
childWhenDragging: Opacity(
opacity: 0.3,
child: _QuickActionTile(index: index, type: item),
),
onDragStarted: () {
setState(() => _draggingIndex = index);
},
onDragEnd: (_) {
setState(() => _draggingIndex = null);
},
child: AnimatedContainer(
duration: const Duration(milliseconds: 200),
transform: isHovering && !isDragging ? Matrix4.translationValues(0, -4, 0) : Matrix4.identity(),
child: _QuickActionTile(index: index, type: item),
),
);
},
);
},
);
}
}
class _QuickActionTile extends StatelessWidget {
final int index;
final ActionButtonType type;
const _QuickActionTile({super.key, required this.index, required this.type});
const _QuickActionTile({required this.index, required this.type});
@override
Widget build(BuildContext context) {

@ -665,14 +665,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.27"
flutter_reorderable_grid_view:
dependency: "direct main"
description:
name: flutter_reorderable_grid_view
sha256: beb85f95325c83515d8953e8612dc70d287a69d1437c14262b7d738070133a87
url: "https://pub.dev"
source: hosted
version: "5.5.2"
flutter_riverpod:
dependency: transitive
description:

@ -38,7 +38,6 @@ dependencies:
flutter_udid: ^4.0.0
flutter_web_auth_2: ^5.0.0-alpha.0
fluttertoast: ^8.2.12
flutter_reorderable_grid_view: ^5.5.2
geolocator: ^14.0.2
home_widget: ^0.8.1
hooks_riverpod: ^2.6.1