baumths / flutter_tree_view

A Flutter collection of widgets and slivers that helps bringing your hierarchical data to life.
https://baumths.github.io/flutter_tree_view
MIT License
175 stars 62 forks source link

Feature request: Support for drag handlers #74

Closed devon closed 5 months ago

devon commented 11 months ago

It would be nice to add a drag handler to TreeDraggable. That way, we can easily scroll the tree and use the handler to drag nodes.

Screen Shot 2023-10-23 at 8 34 04 AM
baumths commented 10 months ago

Hey @devon, thank you for using this package!

A drag handle approach should be doable already. You'd have to move the TreeDraggable from around your tree tile down the widget tree to wrap your drag handle icon.

I intend to update the example app to be more mobile friendly (i.e., long press to drag or a drag handle as requested) when I get some spare time.

devon commented 10 months ago

Hey @devon, thank you for using this package!

A drag handle approach should be doable already. You'd have to move the TreeDraggable from around your tree tile down the widget tree to wrap your drag handle icon.

I intend to update the example app to be more mobile friendly (i.e., long press to drag or a drag handle as requested) when I get some spare time.

The mobile friendly example would be great. Thank you very much. I am starting to use this widget in a new app and it works very well. Love it.

For mobile drag-and-drop outlining (tree), the best app I have tried so far is Cloud Outliner. It supports dragging and dropping multiple items at once.

urvashi-k-7span commented 7 months ago

I am facing the same issue for scrolling and dragging. Any update on this?

baumths commented 5 months ago

Hey folks, sorry for taking so long to look into this. I just pushed a fix to the demo app which is now requiring a long press to start dragging on mobile platforms, this should fix the scrolling issue.

About the drag handle, I wrote a quick and unpolished version for demonstration purposes, I've tested it using the current DnD example from the demo app. It's currently a bit wonky on RTL directionality e.g., the feedback must be translated in TreeDraggable.dragAnchorStrategy by the width of the TreeTile's content.

class DragAndDropTreeTile extends StatelessWidget {
  const DragAndDropTreeTile({
    super.key,
    required this.entry,
    this.onFolderPressed,
  });

  final TreeEntry<Node> entry;
  final VoidCallback? onFolderPressed;

  @override
  Widget build(BuildContext context) {
    final hoveredColor = Theme.of(context).colorScheme.primary.withOpacity(.3);
    return TreeDragTarget(
      node: entry.node,
      onNodeAccepted: (TreeDragAndDropDetails details) {},
      builder: (BuildContext context, TreeDragAndDropDetails? details) {
        if (details != null) {
          return ColoredBox(
            color: hoveredColor,
            child: TreeTile(
              entry: entry,
            ),
          );
        }
        return TreeTile(
          entry: entry,
          onFolderPressed: onFolderPressed,
        );
      },
    );
  }
}

class TreeTile extends StatefulWidget {
  const TreeTile({
    super.key,
    required this.entry,
    this.onFolderPressed,
    this.isFeedback = false,
  });

  final TreeEntry<Node> entry;
  final VoidCallback? onFolderPressed;
  final bool isFeedback;

  @override
  State<TreeTile> createState() => _TreeTileState();
}

class _TreeTileState extends State<TreeTile> {
  bool isDragging = false;

  void onDragStarted() {
    setState(() => isDragging = true);
  }

  void onDragEnd(DraggableDetails details) {
    setState(() => isDragging = false);
  }

  @override
  Widget build(BuildContext context) {
    final Widget content = Opacity(
      opacity: isDragging ? .5 : 1,
      child: Padding(
        padding: const EdgeInsetsDirectional.only(end: 8),
        child: Row(
          children: [
            if (widget.isFeedback)
              const Padding(
                padding: EdgeInsetsDirectional.fromSTEB(8, 8, 0, 8),
                child: Icon(Icons.drag_handle),
              )
            else
              TreeDraggable(
                node: widget.entry.node,
                dragAnchorStrategy: childDragAnchorStrategy,
                onDragStarted: onDragStarted,
                onDragEnd: onDragEnd,
                feedback: IntrinsicWidth(
                  child: Material(
                    elevation: 4,
                    child: TreeTile(
                      entry: widget.entry,
                      isFeedback: true,
                    ),
                  ),
                ),
                child: const Padding(
                  padding: EdgeInsetsDirectional.fromSTEB(8, 8, 0, 8),
                  child: Icon(Icons.drag_handle),
                ),
              ),
            FolderButton(
              isOpen: widget.entry.isExpanded,
              onPressed: widget.onFolderPressed,
            ),
            Expanded(
              child: Text('Node ${widget.entry.node.id}'),
            ),
          ],
        ),
      ),
    );

    if (widget.isFeedback) {
      return content;
    }

    return TreeIndentation(
      entry: widget.entry,
      child: content,
    );
  }
}