mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
refactor: replace flutter_reorderable_grid_view with custom _ReorderableGrid implementation
This commit is contained in:
+115
-22
@@ -1,7 +1,6 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.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:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:immich_mobile/providers/infrastructure/viewer_quick_action_order.provider.dart';
|
import 'package:immich_mobile/providers/infrastructure/viewer_quick_action_order.provider.dart';
|
||||||
import 'package:immich_mobile/utils/action_button.utils.dart';
|
import 'package:immich_mobile/utils/action_button.utils.dart';
|
||||||
@@ -32,9 +31,10 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onReorder(ReorderedListFunction<ActionButtonType> reorder) {
|
void _onReorder(int oldIndex, int newIndex) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_order = reorder(_order);
|
final item = _order.removeAt(oldIndex);
|
||||||
|
_order.insert(newIndex, item);
|
||||||
_hasLocalChanges = true;
|
_hasLocalChanges = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -101,31 +101,22 @@ class _QuickActionConfiguratorState extends ConsumerState<QuickActionConfigurato
|
|||||||
final rows = (_order.length / crossAxisCount).ceil().clamp(1, 4);
|
final rows = (_order.length / crossAxisCount).ceil().clamp(1, 4);
|
||||||
final naturalHeight = rows * tileHeight + (rows - 1) * mainAxisSpacing;
|
final naturalHeight = rows * tileHeight + (rows - 1) * mainAxisSpacing;
|
||||||
final shouldScroll = naturalHeight > constraints.maxHeight;
|
final shouldScroll = naturalHeight > constraints.maxHeight;
|
||||||
final horizontalPadding = 8.0; // matches GridView padding
|
final horizontalPadding = 8.0;
|
||||||
final tileWidth =
|
final tileWidth =
|
||||||
(constraints.maxWidth - horizontalPadding - (crossAxisSpacing * (crossAxisCount - 1))) /
|
(constraints.maxWidth - horizontalPadding - (crossAxisSpacing * (crossAxisCount - 1))) /
|
||||||
crossAxisCount;
|
crossAxisCount;
|
||||||
final childAspectRatio = tileWidth / tileHeight;
|
final childAspectRatio = tileWidth / tileHeight;
|
||||||
final gridController = shouldScroll ? _scrollController : null;
|
final gridController = shouldScroll ? _scrollController : null;
|
||||||
|
|
||||||
return ReorderableBuilder<ActionButtonType>(
|
return _ReorderableGrid(
|
||||||
onReorder: _onReorder,
|
|
||||||
enableLongPress: false,
|
|
||||||
scrollController: gridController,
|
scrollController: gridController,
|
||||||
children: [
|
items: _order,
|
||||||
for (var i = 0; i < _order.length; i++)
|
onReorder: _onReorder,
|
||||||
_QuickActionTile(key: ValueKey(_order[i].name), index: i, type: _order[i]),
|
crossAxisCount: crossAxisCount,
|
||||||
],
|
crossAxisSpacing: crossAxisSpacing,
|
||||||
builder: (children) => GridView.count(
|
mainAxisSpacing: mainAxisSpacing,
|
||||||
controller: gridController,
|
childAspectRatio: childAspectRatio,
|
||||||
crossAxisCount: crossAxisCount,
|
shouldScroll: shouldScroll,
|
||||||
crossAxisSpacing: crossAxisSpacing,
|
|
||||||
mainAxisSpacing: mainAxisSpacing,
|
|
||||||
// padding: const EdgeInsets.fromLTRB(4, 0, 4, 12),
|
|
||||||
physics: shouldScroll ? const BouncingScrollPhysics() : const NeverScrollableScrollPhysics(),
|
|
||||||
childAspectRatio: childAspectRatio,
|
|
||||||
children: children,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -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 {
|
class _QuickActionTile extends StatelessWidget {
|
||||||
final int index;
|
final int index;
|
||||||
final ActionButtonType type;
|
final ActionButtonType type;
|
||||||
|
|
||||||
const _QuickActionTile({super.key, required this.index, required this.type});
|
const _QuickActionTile({required this.index, required this.type});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -665,14 +665,6 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.27"
|
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:
|
flutter_riverpod:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ dependencies:
|
|||||||
flutter_udid: ^4.0.0
|
flutter_udid: ^4.0.0
|
||||||
flutter_web_auth_2: ^5.0.0-alpha.0
|
flutter_web_auth_2: ^5.0.0-alpha.0
|
||||||
fluttertoast: ^8.2.12
|
fluttertoast: ^8.2.12
|
||||||
flutter_reorderable_grid_view: ^5.5.2
|
|
||||||
geolocator: ^14.0.2
|
geolocator: ^14.0.2
|
||||||
home_widget: ^0.8.1
|
home_widget: ^0.8.1
|
||||||
hooks_riverpod: ^2.6.1
|
hooks_riverpod: ^2.6.1
|
||||||
|
|||||||
Reference in New Issue
Block a user