[client] super DropRegion, and tree-shakable widget selection to avoid NDK

This commit is contained in:
Abhishek Shroff
2024-11-18 10:36:22 +05:30
parent 31d22d07f0
commit e993f9938d
5 changed files with 205 additions and 101 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/ui/explorer/resource_drop_target.dart';
import 'package:phylum/ui/explorer/resource_drop_and_drop.dart';
import 'package:phylum/ui/explorer/resource_item_gesture_handler.dart';
import 'package:provider/provider.dart';
+1 -1
View File
@@ -6,7 +6,7 @@ import 'package:go_router/go_router.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/ui/explorer/explorer_view_controller.dart';
import 'package:phylum/ui/explorer/resource_drop_target.dart';
import 'package:phylum/ui/explorer/resource_drop_and_drop.dart';
import 'package:provider/provider.dart';
class PathView extends StatefulWidget {
@@ -0,0 +1,198 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:phylum/app_shortcuts.dart';
import 'package:phylum/libphylum/actions/action_resource_move.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/ui/explorer/explorer_view_controller.dart';
import 'package:phylum/ui/explorer/resource_details_row.dart';
import 'package:phylum/ui/explorer/resource_icon_extension.dart';
import 'package:provider/provider.dart';
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
@pragma('vm:platform-const')
final bool _useSuperNative = !Platform.isAndroid && !Platform.isIOS;
class ResourceDropTarget extends StatefulWidget {
final String resourceId;
final Widget Function(String resourceId, bool dropTargetActive) buildItem;
ResourceDropTarget({required this.resourceId, required this.buildItem}) : super(key: ValueKey(resourceId));
@override
State<ResourceDropTarget> createState() => _ResourceDropTargetState();
}
class _ResourceDropTargetState extends State<ResourceDropTarget> {
bool dropTargetActive = false;
@override
Widget build(BuildContext context) {
return _useSuperNative ? buildSuperDropRegion(context) : buildFlutterDragTarget(context);
}
Widget buildFlutterDragTarget(BuildContext context) {
return DragTarget(
builder: (context, candidate, rejected) => widget.buildItem(widget.resourceId, dropTargetActive),
onWillAcceptWithDetails: (details) {
final state = context.read<ExplorerViewState>();
if (state.selectedIds.contains(widget.resourceId) || state.folder?.id == widget.resourceId) {
// Don't accept drops into selected resources or the parent
return false;
}
setState(() => dropTargetActive = true);
return true;
},
onLeave: (data) {
setState(() => dropTargetActive = false);
},
onAcceptWithDetails: (details) async {
final account = context.read<PhylumAccount>();
final selected = context.read<ExplorerViewState>().selected;
for (final res in selected) {
account.addAction(ResourceMoveAction(r: res, parent: widget.resourceId));
}
setState(() => dropTargetActive = false);
},
);
}
Widget buildSuperDropRegion(BuildContext context) {
return DropRegion(
onDropOver: (event) {
final localData = event.session.items.first.localData;
if (localData != null) {
if ((localData as List).contains(widget.resourceId)) {
return DropOperation.none;
}
return DropOperation.copy;
}
return DropOperation.none;
},
onDropEnter: (event) {
setState(() => dropTargetActive = true);
},
onDropLeave: (event) {
setState(() => dropTargetActive = false);
},
onPerformDrop: (event) async {
final account = context.read<PhylumAccount>();
final selected = context.read<ExplorerViewState>().selected;
for (final res in selected) {
account.addAction(ResourceMoveAction(r: res, parent: widget.resourceId));
}
setState(() => dropTargetActive = false);
},
formats: Formats.standardFormats,
child: widget.buildItem(widget.resourceId, dropTargetActive),
);
}
}
class ResourceDragSource extends StatelessWidget {
final int index;
final Resource resource;
final bool dropTargetActive;
const ResourceDragSource({super.key, required this.resource, required this.index, required this.dropTargetActive});
@override
Widget build(BuildContext context) {
return _useSuperNative ? buildSuperDragItem(context) : buildFlutterDraggable(context);
}
Widget buildFlutterDraggable(BuildContext context) {
return Draggable(
data: '__selected',
dragAnchorStrategy: pointerDragAnchorStrategy,
onDragStarted: () {
final controller = context.read<ExplorerViewController>();
if (!context.read<ExplorerViewState>().isSelected(resource.id)) {
controller.updateSelection((_) => index, SelectionMode.single, false);
}
controller.setDragging(true);
},
onDragEnd: (details) {
context.read<ExplorerViewController>().setDragging(false);
},
feedback: Builder(builder: (ctx) {
final theme = Theme.of(context);
final count = context.read<ExplorerViewState>().selectedIds.length;
return Card(
shape: RoundedRectangleBorder(side: BorderSide(color: theme.colorScheme.primary), borderRadius: BorderRadius.circular(4.0)),
elevation: 16.0,
margin: const EdgeInsets.only(left: 16.0),
child: SizedBox(
width: 240,
height: 48,
child: ListTile(
leading: resource.getIcon(),
title: Text(resource.name),
trailing: count == 1 ? null : Badge(label: Text(count.toString(), style: const TextStyle(fontSize: 14))),
),
),
);
}),
child: ResourceDetailsRow(
r: resource,
dropTargetActive: dropTargetActive,
),
);
}
Widget buildSuperDragItem(BuildContext context) {
return DragItemWidget(
allowedOperations: () => [DropOperation.copy, DropOperation.move],
dragItemProvider: (request) {
final controller = context.read<ExplorerViewController>();
if (!context.read<ExplorerViewState>().isSelected(resource.id)) {
controller.updateSelection((_) => index, SelectionMode.single, false);
}
controller.setDragging(true);
request.session.dragCompleted.addListener(() {
if (request.session.dragCompleted.value != null) {
controller.setDragging(false);
}
});
final state = context.read<ExplorerViewState>();
final item = DragItem(
localData: state.selectedIds,
);
return item;
},
canAddItemToExistingSession: false,
dragBuilder: (ctx, child) {
final theme = Theme.of(context);
final state = context.read<ExplorerViewState>();
final count = state.selectedIds.length;
return SnapshotSettings(
constraintsTransform: (constraints) => BoxConstraints(maxWidth: 240, maxHeight: 48),
child: Card(
shape: RoundedRectangleBorder(side: BorderSide(color: theme.colorScheme.primary), borderRadius: BorderRadius.circular(4.0)),
elevation: 16.0,
margin: const EdgeInsets.only(left: 16.0),
child: SizedBox(
width: 240,
height: 48,
child: ListTile(
leading: resource.getIcon(),
title: Text(resource.name),
trailing: count == 1 ? null : Badge(label: Text(count.toString(), style: const TextStyle(fontSize: 14))),
),
),
),
);
},
child: DraggableWidget(
child: ResourceDetailsRow(
r: resource,
dropTargetActive: dropTargetActive,
),
),
);
}
}
@@ -1,47 +0,0 @@
import 'package:flutter/material.dart';
import 'package:phylum/libphylum/actions/action_resource_move.dart';
import 'package:phylum/libphylum/phylum_account.dart';
import 'package:phylum/ui/explorer/explorer_view_controller.dart';
import 'package:provider/provider.dart';
class ResourceDropTarget extends StatefulWidget {
final String resourceId;
final Widget Function(String resourceId, bool dropTargetActive) buildItem;
ResourceDropTarget({required this.resourceId, required this.buildItem}) : super(key: ValueKey(resourceId));
@override
State<ResourceDropTarget> createState() => _ResourceDropTargetState();
}
class _ResourceDropTargetState extends State<ResourceDropTarget> {
bool dropTargetActive = false;
@override
Widget build(BuildContext context) {
return DragTarget(
builder: (context, candidate, rejected) => widget.buildItem(widget.resourceId, dropTargetActive),
onWillAcceptWithDetails: (details) {
final state = context.read<ExplorerViewState>();
if (state.selectedIds.contains(widget.resourceId) || state.folder?.id == widget.resourceId) {
// Don't accept drops into selected resources or the parent
return false;
}
setState(() => dropTargetActive = true);
return true;
},
onLeave: (data) {
setState(() => dropTargetActive = false);
},
onAcceptWithDetails: (details) async {
final account = context.read<PhylumAccount>();
final selected = context.read<ExplorerViewState>().selected;
for (final res in selected) {
account.addAction(ResourceMoveAction(r: res, parent: widget.resourceId));
}
setState(() => dropTargetActive = false);
},
);
}
}
@@ -3,11 +3,9 @@ import 'package:flutter/services.dart';
import 'package:phylum/app_shortcuts.dart';
import 'package:phylum/libphylum/db/db.dart';
import 'package:phylum/ui/explorer/explorer_view_controller.dart';
import 'package:phylum/ui/explorer/resource_details_row.dart';
import 'package:phylum/ui/explorer/resource_icon_extension.dart';
import 'package:phylum/ui/explorer/resource_drop_and_drop.dart';
import 'package:phylum/ui/explorer/resource_options_dialog.dart';
import 'package:provider/provider.dart';
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
class ResourceItemGestureHandler extends StatefulWidget {
final int index;
@@ -90,55 +88,10 @@ class _ResourceItemGestureHandlerState extends State<ResourceItemGestureHandler>
context.read<ExplorerViewController>().updateSelection((_) => widget.index, SelectionMode.single, false);
Actions.maybeInvoke(context, const ActivateIntent());
},
child: DragItemWidget(
allowedOperations: () => [DropOperation.copy, DropOperation.move],
dragItemProvider: (request) {
final controller = context.read<ExplorerViewController>();
if (!context.read<ExplorerViewState>().isSelected(widget.resource.id)) {
controller.updateSelection((_) => widget.index, SelectionMode.single, false);
}
controller.setDragging(true);
request.session.dragCompleted.addListener(() {
if (request.session.dragCompleted.value != null) {
controller.setDragging(false);
}
});
final state = context.read<ExplorerViewState>();
final item = DragItem(
localData: state.selectedIds,
);
return item;
},
canAddItemToExistingSession: false,
dragBuilder: (ctx, child) {
final theme = Theme.of(context);
final state = context.read<ExplorerViewState>();
final count = state.selectedIds.length;
return SnapshotSettings(
constraintsTransform: (constraints) => BoxConstraints(maxWidth: 240, maxHeight: 48),
child: Card(
shape: RoundedRectangleBorder(side: BorderSide(color: theme.colorScheme.primary), borderRadius: BorderRadius.circular(4.0)),
elevation: 16.0,
margin: const EdgeInsets.only(left: 16.0),
child: SizedBox(
width: 240,
height: 48,
child: ListTile(
leading: widget.resource.getIcon(),
title: Text(widget.resource.name),
trailing: count == 1 ? null : Badge(label: Text(count.toString(), style: const TextStyle(fontSize: 14))),
),
),
),
);
},
child: DraggableWidget(
child: ResourceDetailsRow(
r: widget.resource,
dropTargetActive: dropTargetActive,
),
),
child: ResourceDragSource(
resource: widget.resource,
index: widget.index,
dropTargetActive: dropTargetActive,
),
);
}