From e5a1ef57eed169fecc42b4d8a11055af0aefa9d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A3=B9=E9=9B=B6?= Date: Wed, 18 Dec 2024 10:23:52 +0800 Subject: [PATCH 1/4] fix: edge size/position error --- lib/flow_compose.dart | 5 +- lib/src/board.dart | 79 +++------------ lib/src/controller.dart | 26 +++++ lib/src/fishbone.dart | 154 ----------------------------- lib/src/{ => nodes}/base_node.dart | 16 --- lib/src/nodes/fishbone.dart | 49 +++++++++ lib/src/nodes/nodes.dart | 2 + lib/src/paints/arrow.dart | 38 +++++++ lib/src/paints/bezier.dart | 19 ++++ lib/src/paints/fishbone_paint.dart | 71 +++++++++++++ lib/src/paints/paints.dart | 3 + lib/src/state.dart | 34 +++++++ 12 files changed, 258 insertions(+), 238 deletions(-) create mode 100644 lib/src/controller.dart delete mode 100644 lib/src/fishbone.dart rename lib/src/{ => nodes}/base_node.dart (91%) create mode 100644 lib/src/nodes/fishbone.dart create mode 100644 lib/src/nodes/nodes.dart create mode 100644 lib/src/paints/arrow.dart create mode 100644 lib/src/paints/bezier.dart create mode 100644 lib/src/paints/fishbone_paint.dart create mode 100644 lib/src/paints/paints.dart create mode 100644 lib/src/state.dart diff --git a/lib/flow_compose.dart b/lib/flow_compose.dart index 4c3455b..bf0dc90 100644 --- a/lib/flow_compose.dart +++ b/lib/flow_compose.dart @@ -1,5 +1,6 @@ library; export 'src/board.dart'; -export 'src/fishbone.dart'; -export 'src/base_node.dart'; +export 'src/nodes/base_node.dart'; +export 'src/controller.dart'; +export 'src/state.dart'; diff --git a/lib/src/board.dart b/lib/src/board.dart index 8f6f7a9..0352c63 100644 --- a/lib/src/board.dart +++ b/lib/src/board.dart @@ -3,62 +3,8 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; -class BoardState { - final double scaleFactor; - final Offset dragOffset; - final List data; - final List edges; - - BoardState({ - this.scaleFactor = 1.0, - this.dragOffset = Offset.zero, - this.data = const [], - this.edges = const [], - }); - - BoardState copyWith({ - double? scaleFactor, - Offset? dragOffset, - List? data, - List? edges, - }) { - return BoardState( - scaleFactor: scaleFactor ?? this.scaleFactor, - dragOffset: dragOffset ?? this.dragOffset, - data: data ?? this.data, - edges: edges ?? this.edges, - ); - } - - @override - String toString() { - return 'BoardState{scaleFactor: $scaleFactor, dragOffset: $dragOffset}'; - } -} - -class BoardController { - final ValueNotifier state; - - BoardController({ - BoardState? initialState, - }) : state = ValueNotifier(initialState ?? BoardState()); - - BoardState get value => state.value; - - set value(BoardState newValue) { - state.value = newValue; - } - - void reCenter() { - state.value = state.value.copyWith( - dragOffset: Offset.zero, - ); - } - - void dispose() { - state.dispose(); - } -} +import 'nodes/fishbone.dart'; +import 'paints/paints.dart'; class InfiniteDrawingBoard extends StatefulWidget { const InfiniteDrawingBoard({super.key, this.controller}); @@ -88,9 +34,8 @@ class _InfiniteDrawingBoardState extends State { } void _handleDragUpdate(Offset offset) { - boardNotifier.value = boardNotifier.value.copyWith( - dragOffset: boardNotifier.value.dragOffset + offset, - ); + boardNotifier.value = boardNotifier.value + .copyWith(dragOffset: boardNotifier.value.dragOffset + offset); } // ignore: avoid_init_to_null @@ -107,7 +52,8 @@ class _InfiniteDrawingBoardState extends State { ) .firstOrNull; if (fakeEdge != null) { - fakeEdge = fakeEdge.copyWith(end: fakeEdge.end + offset); + fakeEdge = fakeEdge.copyWith( + end: fakeEdge.end + offset * 1 / boardNotifier.value.scaleFactor); boardNotifier.value = boardNotifier.value.copyWith( edges: (boardNotifier.value.edges as List).map((e) { if (e.uuid == fakeEdge!.uuid) { @@ -260,19 +206,20 @@ class InfiniteCanvasPainter extends CustomPainter { canvas.restore(); - if (data.isNotEmpty) { - if (data[0] is FishboneNode) { - paintFishbone(canvas, size, data as List); - } - } + // if (data.isNotEmpty) { + // if (data[0] is FishboneNode) { + // paintFishbone(canvas, size, data as List); + // } + // } if (edges.isNotEmpty) { for (Edge e in edges as List) { - paintBezierEdge(canvas, scale, e.start, e.end); + paintBezierEdge(canvas, scale, e.start, e.end, offset); } } } + @Deprecated("for test") void paintFishbone(Canvas canvas, Size size, List data) { paintMain(canvas, size, data, offset, scale); } diff --git a/lib/src/controller.dart b/lib/src/controller.dart new file mode 100644 index 0000000..9f0c9a2 --- /dev/null +++ b/lib/src/controller.dart @@ -0,0 +1,26 @@ +import 'package:flow_compose/src/state.dart'; +import 'package:flutter/material.dart'; + +class BoardController { + final ValueNotifier state; + + BoardController({ + BoardState? initialState, + }) : state = ValueNotifier(initialState ?? BoardState()); + + BoardState get value => state.value; + + set value(BoardState newValue) { + state.value = newValue; + } + + void reCenter() { + state.value = state.value.copyWith( + dragOffset: Offset.zero, + ); + } + + void dispose() { + state.dispose(); + } +} diff --git a/lib/src/fishbone.dart b/lib/src/fishbone.dart deleted file mode 100644 index 664d8cf..0000000 --- a/lib/src/fishbone.dart +++ /dev/null @@ -1,154 +0,0 @@ -import 'package:flutter/material.dart'; -// ignore: library_prefixes -import 'dart:math' as Math; - -typedef HiddenBuilder = Widget Function(BuildContext ctx, String uuid); - -enum NodePosition { up, down } - -class FishboneNode { - final String label; - final double angle; - final double distance; - final List children; - final String uuid; - final bool isHidden; - final int depth; - final NodePosition position; - HiddenBuilder? hiddenBuilder; - - FishboneNode( - {required this.label, - required this.uuid, - this.angle = 0.0, - this.distance = 100.0, - this.children = const [], - this.isHidden = false, - this.hiddenBuilder, - required this.depth, - this.position = NodePosition.up}); - - static List fake() { - return [ - FishboneNode( - label: "1-1", - uuid: "1-1", - depth: 1, - position: NodePosition.up, - children: [ - FishboneNode(label: "1-1-1", uuid: "1-1-1", depth: 2, children: []) - ]), - FishboneNode( - label: "2-2", - uuid: "2-2", - depth: 1, - position: NodePosition.down, - children: [ - FishboneNode(label: "2-2-1", uuid: "2-2-1", depth: 2, children: []) - ]), - ]; - } -} - -void paintMain(Canvas canvas, Size size, List nodes, - Offset offset, double scale) { - // print("size $size"); - final Paint paint = Paint() - ..color = Colors.black - ..strokeWidth = 20 * scale - ..style = PaintingStyle.stroke; - - // 绘制主骨干 - final Offset start = Offset(-500, 500) * scale + offset; - final Offset end = Offset(500, 500) * scale + offset; - - canvas.drawLine(start, end, paint); - drawArrow(canvas, end, scale); - - // [for test] - for (final node in nodes) { - paintNode(canvas, size, node, start + Offset(node.distance, 0) * scale, - scale, 3.14 / 180 * 45); - } -} - -void paintNode(Canvas canvas, Size _, FishboneNode node, Offset offset, - double scale, double rotation, - {double parentRotation = 0}) { - // 创建画笔 - final Paint paint = Paint() - ..color = Colors.black - ..strokeWidth = 20 / (node.depth + 1) * scale - ..style = PaintingStyle.stroke; - - if (node.position == NodePosition.down) { - // 计算起点和终点的位置 - final double lineLength = 100 * scale; // 线段长度,可以根据需求调整 - final Offset start = - offset + Offset(0, (lineLength + 20) * Math.sin(rotation)); - final Offset end = Offset( - offset.dx + lineLength * Math.cos(-rotation), - offset.dy + 25 * scale, - ); - - // 绘制线段 - canvas.drawLine(start, end, paint); - - // 在终点绘制箭头 - drawArrow(canvas, end, scale, - arrowSize: 40 / (node.depth + 1), rotation: -rotation); - } else { - // 计算起点和终点的位置 - final double lineLength = 100 * scale; // 线段长度,可以根据需求调整 - final Offset start = - offset - Offset(0, (lineLength + 20) * Math.sin(rotation)); - final Offset end = Offset( - offset.dx + lineLength * Math.cos(rotation), - offset.dy - 25 * scale, - ); - - // 绘制线段 - canvas.drawLine(start, end, paint); - - // 在终点绘制箭头 - drawArrow(canvas, end, scale, - arrowSize: 40 / (node.depth + 1), rotation: rotation); - } -} - -void drawArrow(Canvas canvas, Offset end, double scale, - {double arrowSize = 40, double rotation = 0}) { - final double arrowWidth = arrowSize * scale; // 箭头宽度 - final double arrowHeight = arrowSize * scale; // 箭头高度 - - // 保存画布的当前状态 - canvas.save(); - - // 移动画布到箭头绘制的终点(旋转中心) - canvas.translate(end.dx, end.dy); - - // 应用旋转变换 - canvas.rotate(rotation); - - // 计算箭头的三个顶点(相对于旋转中心) - final Offset tip = Offset(arrowWidth, 0); // 尖端 - final Offset left = Offset(0, -arrowHeight / 2); // 左下角 - final Offset right = Offset(0, arrowHeight / 2); // 左上角 - - // 使用 Path 绘制三角形 - final Path arrowPath = Path() - ..moveTo(tip.dx, tip.dy) // 移动到尖端 - ..lineTo(left.dx, left.dy) // 画线到左下角 - ..lineTo(right.dx, right.dy) // 画线到左上角 - ..close(); // 闭合路径 - - final Paint arrowPaint = Paint() - ..color = Colors.black - ..style = PaintingStyle.fill; // 填充三角形 - - // 绘制箭头 - canvas.drawPath(arrowPath, arrowPaint); - - // 恢复画布的状态 - canvas.restore(); -} diff --git a/lib/src/base_node.dart b/lib/src/nodes/base_node.dart similarity index 91% rename from lib/src/base_node.dart rename to lib/src/nodes/base_node.dart index 882f186..f66c698 100644 --- a/lib/src/base_node.dart +++ b/lib/src/nodes/base_node.dart @@ -191,19 +191,3 @@ class Edge { ); } } - -void paintBezierEdge(Canvas canvas, double scale, Offset start, Offset end) { - // print("paintBezierEdge $start $end"); - - var controlPoint = Offset(start.dx + (end.dx - start.dx) / 2, start.dy); - final paint = Paint() - ..color = Colors.blue - ..style = PaintingStyle.stroke - ..strokeWidth = 4.0; - - final path = Path() - ..moveTo(start.dx, start.dy) - ..quadraticBezierTo(controlPoint.dx, controlPoint.dy, end.dx, end.dy); - - canvas.drawPath(path, paint); -} diff --git a/lib/src/nodes/fishbone.dart b/lib/src/nodes/fishbone.dart new file mode 100644 index 0000000..1865fc8 --- /dev/null +++ b/lib/src/nodes/fishbone.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; + +typedef HiddenBuilder = Widget Function(BuildContext ctx, String uuid); + +enum NodePosition { up, down } + +class FishboneNode { + final String label; + final double angle; + final double distance; + final List children; + final String uuid; + final bool isHidden; + final int depth; + final NodePosition position; + HiddenBuilder? hiddenBuilder; + + FishboneNode( + {required this.label, + required this.uuid, + this.angle = 0.0, + this.distance = 100.0, + this.children = const [], + this.isHidden = false, + this.hiddenBuilder, + required this.depth, + this.position = NodePosition.up}); + + static List fake() { + return [ + FishboneNode( + label: "1-1", + uuid: "1-1", + depth: 1, + position: NodePosition.up, + children: [ + FishboneNode(label: "1-1-1", uuid: "1-1-1", depth: 2, children: []) + ]), + FishboneNode( + label: "2-2", + uuid: "2-2", + depth: 1, + position: NodePosition.down, + children: [ + FishboneNode(label: "2-2-1", uuid: "2-2-1", depth: 2, children: []) + ]), + ]; + } +} diff --git a/lib/src/nodes/nodes.dart b/lib/src/nodes/nodes.dart new file mode 100644 index 0000000..76bc1cc --- /dev/null +++ b/lib/src/nodes/nodes.dart @@ -0,0 +1,2 @@ +export './base_node.dart'; +export './fishbone.dart'; diff --git a/lib/src/paints/arrow.dart b/lib/src/paints/arrow.dart new file mode 100644 index 0000000..9b5f999 --- /dev/null +++ b/lib/src/paints/arrow.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; + +void paintArrow(Canvas canvas, Offset end, double scale, + {double arrowSize = 40, double rotation = 0}) { + final double arrowWidth = arrowSize * scale; // 箭头宽度 + final double arrowHeight = arrowSize * scale; // 箭头高度 + + // 保存画布的当前状态 + canvas.save(); + + // 移动画布到箭头绘制的终点(旋转中心) + canvas.translate(end.dx, end.dy); + + // 应用旋转变换 + canvas.rotate(rotation); + + // 计算箭头的三个顶点(相对于旋转中心) + final Offset tip = Offset(arrowWidth, 0); // 尖端 + final Offset left = Offset(0, -arrowHeight / 2); // 左下角 + final Offset right = Offset(0, arrowHeight / 2); // 左上角 + + // 使用 Path 绘制三角形 + final Path arrowPath = Path() + ..moveTo(tip.dx, tip.dy) // 移动到尖端 + ..lineTo(left.dx, left.dy) // 画线到左下角 + ..lineTo(right.dx, right.dy) // 画线到左上角 + ..close(); // 闭合路径 + + final Paint arrowPaint = Paint() + ..color = Colors.black + ..style = PaintingStyle.fill; // 填充三角形 + + // 绘制箭头 + canvas.drawPath(arrowPath, arrowPaint); + + // 恢复画布的状态 + canvas.restore(); +} diff --git a/lib/src/paints/bezier.dart b/lib/src/paints/bezier.dart new file mode 100644 index 0000000..bbcdb1e --- /dev/null +++ b/lib/src/paints/bezier.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +void paintBezierEdge( + Canvas canvas, double scale, Offset start, Offset end, Offset offset) { + var s = (start * scale + offset); + var e = (end * scale + offset); + + var controlPoint = Offset(s.dx + (e.dx - s.dx) / 2, s.dy); + final paint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke + ..strokeWidth = 4.0; + + final path = Path() + ..moveTo(s.dx, s.dy) + ..quadraticBezierTo(controlPoint.dx, controlPoint.dy, e.dx, e.dy); + + canvas.drawPath(path, paint); +} diff --git a/lib/src/paints/fishbone_paint.dart b/lib/src/paints/fishbone_paint.dart new file mode 100644 index 0000000..ea1ac41 --- /dev/null +++ b/lib/src/paints/fishbone_paint.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +// ignore: library_prefixes +import 'dart:math' as Math; +import '../nodes/fishbone.dart'; +import 'arrow.dart'; + +void paintMain(Canvas canvas, Size size, List nodes, + Offset offset, double scale) { + // print("size $size"); + final Paint paint = Paint() + ..color = Colors.black + ..strokeWidth = 20 * scale + ..style = PaintingStyle.stroke; + + // 绘制主骨干 + final Offset start = Offset(-500, 500) * scale + offset; + final Offset end = Offset(500, 500) * scale + offset; + + canvas.drawLine(start, end, paint); + paintArrow(canvas, end, scale); + + // [for test] + for (final node in nodes) { + paintNode(canvas, size, node, start + Offset(node.distance, 0) * scale, + scale, 3.14 / 180 * 45); + } +} + +void paintNode(Canvas canvas, Size _, FishboneNode node, Offset offset, + double scale, double rotation, + {double parentRotation = 0}) { + // 创建画笔 + final Paint paint = Paint() + ..color = Colors.black + ..strokeWidth = 20 / (node.depth + 1) * scale + ..style = PaintingStyle.stroke; + + if (node.position == NodePosition.down) { + // 计算起点和终点的位置 + final double lineLength = 100 * scale; // 线段长度,可以根据需求调整 + final Offset start = + offset + Offset(0, (lineLength + 20) * Math.sin(rotation)); + final Offset end = Offset( + offset.dx + lineLength * Math.cos(-rotation), + offset.dy + 25 * scale, + ); + + // 绘制线段 + canvas.drawLine(start, end, paint); + + // 在终点绘制箭头 + paintArrow(canvas, end, scale, + arrowSize: 40 / (node.depth + 1), rotation: -rotation); + } else { + // 计算起点和终点的位置 + final double lineLength = 100 * scale; // 线段长度,可以根据需求调整 + final Offset start = + offset - Offset(0, (lineLength + 20) * Math.sin(rotation)); + final Offset end = Offset( + offset.dx + lineLength * Math.cos(rotation), + offset.dy - 25 * scale, + ); + + // 绘制线段 + canvas.drawLine(start, end, paint); + + // 在终点绘制箭头 + paintArrow(canvas, end, scale, + arrowSize: 40 / (node.depth + 1), rotation: rotation); + } +} diff --git a/lib/src/paints/paints.dart b/lib/src/paints/paints.dart new file mode 100644 index 0000000..2f51131 --- /dev/null +++ b/lib/src/paints/paints.dart @@ -0,0 +1,3 @@ +export './bezier.dart'; +export './arrow.dart'; +export './fishbone_paint.dart'; diff --git a/lib/src/state.dart b/lib/src/state.dart new file mode 100644 index 0000000..8a0b3d9 --- /dev/null +++ b/lib/src/state.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; + +class BoardState { + final double scaleFactor; + final Offset dragOffset; + final List data; + final List edges; + + BoardState({ + this.scaleFactor = 1.0, + this.dragOffset = Offset.zero, + this.data = const [], + this.edges = const [], + }); + + BoardState copyWith({ + double? scaleFactor, + Offset? dragOffset, + List? data, + List? edges, + }) { + return BoardState( + scaleFactor: scaleFactor ?? this.scaleFactor, + dragOffset: dragOffset ?? this.dragOffset, + data: data ?? this.data, + edges: edges ?? this.edges, + ); + } + + @override + String toString() { + return 'BoardState{scaleFactor: $scaleFactor, dragOffset: $dragOffset}'; + } +} From 40f08d6586f056fae7dc2df7fe3484b282b084f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A3=B9=E9=9B=B6?= Date: Wed, 18 Dec 2024 11:20:41 +0800 Subject: [PATCH 2/4] feat: edge with arrow --- lib/src/annotation.dart | 19 ++++++++ lib/src/board.dart | 58 +++++++++++++++++++---- lib/src/nodes/base_node.dart | 42 ++++++++++++----- lib/src/nodes/fishbone.dart | 2 + lib/src/paints/bezier.dart | 63 +++++++++++++++++++++++++ test/edge_test.dart | 57 +++++++++++++++++++++++ test/edge_test_2.dart | 89 ++++++++++++++++++++++++++++++++++++ 7 files changed, 311 insertions(+), 19 deletions(-) create mode 100644 lib/src/annotation.dart create mode 100644 test/edge_test.dart create mode 100644 test/edge_test_2.dart diff --git a/lib/src/annotation.dart b/lib/src/annotation.dart new file mode 100644 index 0000000..a38f1d1 --- /dev/null +++ b/lib/src/annotation.dart @@ -0,0 +1,19 @@ +class NotReady { + final String info; + + const NotReady({this.info = "This feature is not ready yet!"}); +} + +enum FeaturesType { + boardDrag, + boardScaleChange, + nodeDrag, + nodeDelete, + all, +} + +class Features { + final List features; + + const Features({required this.features}); +} diff --git a/lib/src/board.dart b/lib/src/board.dart index 0352c63..8cfc312 100644 --- a/lib/src/board.dart +++ b/lib/src/board.dart @@ -1,4 +1,5 @@ import 'package:flow_compose/flow_compose.dart'; +import 'package:flow_compose/src/annotation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; @@ -38,10 +39,33 @@ class _InfiniteDrawingBoardState extends State { .copyWith(dragOffset: boardNotifier.value.dragOffset + offset); } + void _paintEdgeFromAToB(String a, String b) { + BaseNode? aNode = boardNotifier.value.data + .where((element) => element.uuid == a) + .firstOrNull as BaseNode?; + + BaseNode? bNode = boardNotifier.value.data + .where((element) => element.uuid == b) + .firstOrNull as BaseNode?; + + if (aNode != null && bNode != null) { + Edge edge = Edge( + uuid: uuid.v4(), + source: aNode.uuid, + target: bNode.uuid, + start: aNode.outputPoint, + end: bNode.inputPoint); + List edges = boardNotifier.value.edges as List; + edges.add(edge); + boardNotifier.value = boardNotifier.value.copyWith(edges: edges); + } + } + // ignore: avoid_init_to_null String? currentUuid = null; var uuid = Uuid(); + @Features(features: [FeaturesType.all]) void _modifyFakeEdge(BaseNode start, Offset offset) { currentUuid ??= uuid.v4(); // print("start.outputPoint ${start.outputPoint}"); @@ -87,14 +111,29 @@ class _InfiniteDrawingBoardState extends State { } void _handleNodeDrag(String uuid, Offset offset, double factor) { - boardNotifier.value = boardNotifier.value.copyWith( - data: (boardNotifier.value.data as List).map((e) { - if (e.uuid == uuid) { - return e.copyWith(offset: e.offset + offset * 1 / factor); + var data = boardNotifier.value.data as List; + data = data.map((e) { + if (e.uuid == uuid) { + return e.copyWith(offset: e.offset + offset * 1 / factor); + } + return e; + }).toList(); + + var edges = boardNotifier.value.edges as List; + if (edges.isNotEmpty) { + edges = edges.map((e) { + if (e.source == uuid) { + return e.copyWith(start: e.start + offset * 1 / factor); + } + if (e.target == uuid) { + return e.copyWith(end: e.end + offset * 1 / factor); } return e; - }).toList(), - ); + }).toList(); + } + + boardNotifier.value = + boardNotifier.value.copyWith(data: data, edges: edges); } @override @@ -137,12 +176,15 @@ class _InfiniteDrawingBoardState extends State { _handleNodeDrag( e.uuid, offset, state.scaleFactor); }, - onNodeEdgeCreate: (offset) { + onNodeEdgeCreateOrModify: (offset) { _modifyFakeEdge(e, offset); }, onNodeEdgeCancel: () { _handleNodeEdgeCancel(); }, + onEdgeAccept: (from, to) { + _paintEdgeFromAToB(from, to); + }, ); }) ], @@ -214,7 +256,7 @@ class InfiniteCanvasPainter extends CustomPainter { if (edges.isNotEmpty) { for (Edge e in edges as List) { - paintBezierEdge(canvas, scale, e.start, e.end, offset); + paintBezierEdgeWithArrow(canvas, scale, e.start, e.end, offset); } } } diff --git a/lib/src/nodes/base_node.dart b/lib/src/nodes/base_node.dart index f66c698..faca6be 100644 --- a/lib/src/nodes/base_node.dart +++ b/lib/src/nodes/base_node.dart @@ -2,7 +2,9 @@ import 'package:flutter/material.dart'; typedef OnNodeDrag = void Function(Offset offset); // typedef OnBoardSizeChange = void Function(double factor); -typedef OnNodeEdgeCreate = void Function(Offset offset); +typedef OnNodeEdgeCreateOrModify = void Function(Offset offset); + +typedef OnEdgeAccept = void Function(String from, String to); class BaseNode { final double width; @@ -36,6 +38,14 @@ class BaseNode { label: "1-1", uuid: "1-1", depth: 1, + offset: Offset(0, 0), + children: []), + BaseNode( + width: 300, + height: 400, + label: "2-2", + uuid: "2-2", + depth: 1, offset: Offset(500, 500), children: []) ]; @@ -49,8 +59,9 @@ class BaseNode { required Offset dragOffset, required double factor, required OnNodeDrag onNodeDrag, - required OnNodeEdgeCreate onNodeEdgeCreate, + required OnNodeEdgeCreateOrModify onNodeEdgeCreateOrModify, required VoidCallback onNodeEdgeCancel, + required OnEdgeAccept onEdgeAccept, }) { return Positioned( left: offset.dx * factor + dragOffset.dx, @@ -80,7 +91,7 @@ class BaseNode { top: 0, child: GestureDetector( onTap: () { - print("delete $uuid"); + debugPrint("delete $uuid"); }, child: Container( width: 24, @@ -97,9 +108,10 @@ class BaseNode { right: 0, top: 0.5 * height * factor, child: Draggable( + data: uuid, onDragUpdate: (details) { // print(details); - onNodeEdgeCreate(details.delta); + onNodeEdgeCreateOrModify(details.delta); }, onDragEnd: (details) { onNodeEdgeCancel(); @@ -120,13 +132,21 @@ class BaseNode { Positioned( left: 0, top: 0.5 * height * factor, - child: Container( - width: 24, - height: 24, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - ), - child: Icon(Icons.input), + child: DragTarget( + builder: (c, _, __) { + return Container( + width: 24, + height: 24, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + child: Icon(Icons.input), + ); + }, + onAcceptWithDetails: (data) { + debugPrint("accept ${data.data} this is $uuid"); + onEdgeAccept(data.data, uuid); + }, )) ], ), diff --git a/lib/src/nodes/fishbone.dart b/lib/src/nodes/fishbone.dart index 1865fc8..278f523 100644 --- a/lib/src/nodes/fishbone.dart +++ b/lib/src/nodes/fishbone.dart @@ -1,9 +1,11 @@ +import 'package:flow_compose/src/annotation.dart'; import 'package:flutter/material.dart'; typedef HiddenBuilder = Widget Function(BuildContext ctx, String uuid); enum NodePosition { up, down } +@NotReady() class FishboneNode { final String label; final double angle; diff --git a/lib/src/paints/bezier.dart b/lib/src/paints/bezier.dart index bbcdb1e..760ddf4 100644 --- a/lib/src/paints/bezier.dart +++ b/lib/src/paints/bezier.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; void paintBezierEdge( @@ -17,3 +19,64 @@ void paintBezierEdge( canvas.drawPath(path, paint); } + +void paintBezierEdgeWithArrow( + Canvas canvas, double scale, Offset start, Offset end, Offset offset) { + var s = (start * scale + offset); + var e = (end * scale + offset); + + // var controlPoint = Offset(s.dx + (e.dx - s.dx) / 2, s.dy); + // 计算控制点位置(动态生成控制点) + final controlPoint = Offset( + (s.dx + e.dx) / 2, // 控制点 x 为起点和终点的中点 + min(s.dy, e.dy) - 50, // 控制点 y 位于两点之上,使曲线弯曲 + ); + + final paint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke + ..strokeWidth = 4.0; + + final arrowPaint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.fill; + + final path = Path(); + path.moveTo(s.dx, s.dy); + path.quadraticBezierTo( + controlPoint.dx, + controlPoint.dy, + e.dx, + e.dy, + ); + canvas.drawPath(path, paint); + + // 计算箭头的方向 + final arrowAngle = pi / 6; // 箭头的开口角度 + final arrowLength = 12.0; // 箭头的长度 + + // 曲线的切线方向(根据终点和控制点计算) + final tangent = Offset( + e.dx - controlPoint.dx, + e.dy - controlPoint.dy, + ).direction; + + // 箭头的两个点 + final arrowPoint1 = Offset( + e.dx - arrowLength * cos(tangent - arrowAngle), + e.dy - arrowLength * sin(tangent - arrowAngle), + ); + final arrowPoint2 = Offset( + e.dx - arrowLength * cos(tangent + arrowAngle), + e.dy - arrowLength * sin(tangent + arrowAngle), + ); + + // 绘制箭头 + final arrowPath = Path() + ..moveTo(e.dx, e.dy) + ..lineTo(arrowPoint1.dx, arrowPoint1.dy) + ..lineTo(arrowPoint2.dx, arrowPoint2.dy) + ..close(); + + canvas.drawPath(arrowPath, arrowPaint); +} diff --git a/test/edge_test.dart b/test/edge_test.dart new file mode 100644 index 0000000..4e5ac8a --- /dev/null +++ b/test/edge_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('曲线绘制示例')), + body: Center( + child: CustomPaint( + size: Size(300, 200), // 画布大小 + painter: HorizontalCurvePainter(), + ), + ), + ), + ); + } +} + +class HorizontalCurvePainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + + final path = Path(); + + // 起始水平线 + path.moveTo(0, size.height / 2); + path.lineTo(size.width * 0.2, size.height / 2); + + // 中间曲线 + path.quadraticBezierTo( + size.width * 0.5, // 控制点 x + size.height * 0.1, // 控制点 y + size.width * 0.8, // 结束点 x + size.height / 2, // 结束点 y + ); + + // 终止水平线 + path.lineTo(size.width, size.height / 2); + + // 绘制路径 + canvas.drawPath(path, paint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/test/edge_test_2.dart b/test/edge_test_2.dart new file mode 100644 index 0000000..cf3a914 --- /dev/null +++ b/test/edge_test_2.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'dart:math'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar(title: const Text('工作流连接曲线')), + body: Center( + child: CustomPaint( + size: Size(400, 300), // 画布大小 + painter: WorkflowConnectionPainter(), + ), + ), + ), + ); + } +} + +class WorkflowConnectionPainter extends CustomPainter { + @override + void paint(Canvas canvas, Size size) { + final paint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.stroke + ..strokeWidth = 2; + + final arrowPaint = Paint() + ..color = Colors.blue + ..style = PaintingStyle.fill; + + // 起点和终点 + final start = Offset(size.width * 0.2, size.height * 0.5); + final end = Offset(size.width * 0.8, size.height * 0.3); + + // 控制点(决定曲线形状) + final controlPoint = Offset(size.width * 0.5, size.height * 0.1); + + // 绘制平滑曲线 + final path = Path(); + path.moveTo(start.dx, start.dy); + path.quadraticBezierTo( + controlPoint.dx, + controlPoint.dy, + end.dx, + end.dy, + ); + canvas.drawPath(path, paint); + + // 计算箭头的方向 + final arrowAngle = pi / 6; // 箭头的开口角度 + final arrowLength = 12.0; // 箭头的长度 + + // 曲线的切线方向(近似通过控制点和终点计算) + final tangent = Offset( + end.dx - controlPoint.dx, + end.dy - controlPoint.dy, + ).direction; + + // 箭头的两个点 + final arrowPoint1 = Offset( + end.dx - arrowLength * cos(tangent - arrowAngle), + end.dy - arrowLength * sin(tangent - arrowAngle), + ); + final arrowPoint2 = Offset( + end.dx - arrowLength * cos(tangent + arrowAngle), + end.dy - arrowLength * sin(tangent + arrowAngle), + ); + + // 绘制箭头 + final arrowPath = Path() + ..moveTo(end.dx, end.dy) + ..lineTo(arrowPoint1.dx, arrowPoint1.dy) + ..lineTo(arrowPoint2.dx, arrowPoint2.dy) + ..close(); + + canvas.drawPath(arrowPath, arrowPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} From 5d8740a3039da186424fd7e7dc15da45bc66ee95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A3=B9=E9=9B=B6?= Date: Wed, 18 Dec 2024 12:26:48 +0800 Subject: [PATCH 3/4] feat: update state --- CHANGELOG.md | 5 +- example/lib/main.dart | 2 +- lib/src/board.dart | 33 ++++---- lib/src/nodes/base_node.dart | 14 ++++ lib/src/nodes/node_widget.dart | 147 +++++++++++++++++++++++++++++++++ lib/src/nodes/nodes.dart | 1 + lib/src/state.dart | 6 +- pubspec.yaml | 2 +- 8 files changed, 188 insertions(+), 22 deletions(-) create mode 100644 lib/src/nodes/node_widget.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 41cc7d8..300069d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.0.1+1 +* Fix and new features. + ## 0.0.1 -* TODO: Describe initial release. +* initial release. diff --git a/example/lib/main.dart b/example/lib/main.dart index 1109972..04d6de0 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -32,7 +32,7 @@ class MyHomePage extends StatefulWidget { class _MyHomePageState extends State { final controller = BoardController( initialState: - BoardState(data: BaseNode.fake(), edges: [])); + BoardState(data: BaseNode.fake(), edges: {})); @override Widget build(BuildContext context) { // This method is rerun every time setState is called, for instance as done diff --git a/lib/src/board.dart b/lib/src/board.dart index 8cfc312..a98b18c 100644 --- a/lib/src/board.dart +++ b/lib/src/board.dart @@ -1,10 +1,10 @@ import 'package:flow_compose/flow_compose.dart'; import 'package:flow_compose/src/annotation.dart'; +import 'package:flow_compose/src/nodes/nodes.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:uuid/uuid.dart'; -import 'nodes/fishbone.dart'; import 'paints/paints.dart'; class InfiniteDrawingBoard extends StatefulWidget { @@ -55,9 +55,9 @@ class _InfiniteDrawingBoardState extends State { target: bNode.uuid, start: aNode.outputPoint, end: bNode.inputPoint); - List edges = boardNotifier.value.edges as List; + Set edges = boardNotifier.value.edges as Set; edges.add(edge); - boardNotifier.value = boardNotifier.value.copyWith(edges: edges); + boardNotifier.value = boardNotifier.value.copyWith(edges: edges.toSet()); } } @@ -70,7 +70,7 @@ class _InfiniteDrawingBoardState extends State { currentUuid ??= uuid.v4(); // print("start.outputPoint ${start.outputPoint}"); - Edge? fakeEdge = (boardNotifier.value.edges as List) + Edge? fakeEdge = (boardNotifier.value.edges as Set) .where( (element) => element.uuid == currentUuid, ) @@ -79,12 +79,12 @@ class _InfiniteDrawingBoardState extends State { fakeEdge = fakeEdge.copyWith( end: fakeEdge.end + offset * 1 / boardNotifier.value.scaleFactor); boardNotifier.value = boardNotifier.value.copyWith( - edges: (boardNotifier.value.edges as List).map((e) { + edges: (boardNotifier.value.edges as Set).map((e) { if (e.uuid == fakeEdge!.uuid) { return fakeEdge; } return e; - }).toList(), + }).toSet(), ); } else { fakeEdge = Edge( @@ -93,19 +93,19 @@ class _InfiniteDrawingBoardState extends State { uuid: currentUuid!, start: start.outputPoint, ); - List edges = boardNotifier.value.edges as List; + Set edges = boardNotifier.value.edges as Set; edges.add(fakeEdge); boardNotifier.value = boardNotifier.value.copyWith( - edges: edges, + edges: edges.toSet(), ); } } void _handleNodeEdgeCancel() { - List edges = boardNotifier.value.edges as List; + Set edges = boardNotifier.value.edges as Set; edges.removeWhere((element) => element.uuid == currentUuid); boardNotifier.value = boardNotifier.value.copyWith( - edges: edges, + edges: edges.toSet(), ); currentUuid = null; } @@ -119,7 +119,7 @@ class _InfiniteDrawingBoardState extends State { return e; }).toList(); - var edges = boardNotifier.value.edges as List; + var edges = boardNotifier.value.edges as Set; if (edges.isNotEmpty) { edges = edges.map((e) { if (e.source == uuid) { @@ -129,11 +129,11 @@ class _InfiniteDrawingBoardState extends State { return e.copyWith(end: e.end + offset * 1 / factor); } return e; - }).toList(); + }).toSet(); } boardNotifier.value = - boardNotifier.value.copyWith(data: data, edges: edges); + boardNotifier.value.copyWith(data: data, edges: edges.toSet()); } @override @@ -169,7 +169,8 @@ class _InfiniteDrawingBoardState extends State { height: double.infinity, ), ...state.data.map((e) { - return (e as BaseNode).build( + return NodeWidget( + node: e, dragOffset: state.dragOffset, factor: state.scaleFactor, onNodeDrag: (offset) { @@ -209,7 +210,7 @@ class InfiniteCanvasPainter extends CustomPainter { final Offset offset; final double scale; final List data; - final List edges; + final Set edges; InfiniteCanvasPainter( {required this.offset, @@ -255,7 +256,7 @@ class InfiniteCanvasPainter extends CustomPainter { // } if (edges.isNotEmpty) { - for (Edge e in edges as List) { + for (Edge e in edges as Set) { paintBezierEdgeWithArrow(canvas, scale, e.start, e.end, offset); } } diff --git a/lib/src/nodes/base_node.dart b/lib/src/nodes/base_node.dart index faca6be..d679830 100644 --- a/lib/src/nodes/base_node.dart +++ b/lib/src/nodes/base_node.dart @@ -55,6 +55,7 @@ class BaseNode { Offset get inputPoint => Offset(offset.dx, offset.dy + 0.5 * height); + @Deprecated("use [NodeWidget] instead") Widget build({ required Offset dragOffset, required double factor, @@ -183,6 +184,11 @@ class Edge { final Offset start; final Offset end; + @override + bool operator ==(Object other) { + return other is Edge && other.source == source && other.target == target; + } + Edge( {required this.uuid, required this.source, @@ -210,4 +216,12 @@ class Edge { end: end ?? this.end, ); } + + @override + int get hashCode => + uuid.hashCode ^ + source.hashCode ^ + target.hashCode ^ + start.hashCode ^ + end.hashCode; } diff --git a/lib/src/nodes/node_widget.dart b/lib/src/nodes/node_widget.dart new file mode 100644 index 0000000..794d834 --- /dev/null +++ b/lib/src/nodes/node_widget.dart @@ -0,0 +1,147 @@ +import 'package:flow_compose/src/nodes/base_node.dart'; +import 'package:flutter/material.dart'; + +class NodeWidget extends StatefulWidget { + const NodeWidget( + {super.key, + required this.node, + required this.dragOffset, + required this.factor, + required this.onNodeDrag, + required this.onNodeEdgeCreateOrModify, + required this.onNodeEdgeCancel, + required this.onEdgeAccept}); + final T node; + final Offset dragOffset; + final double factor; + final OnNodeDrag onNodeDrag; + final OnNodeEdgeCreateOrModify onNodeEdgeCreateOrModify; + final VoidCallback onNodeEdgeCancel; + final OnEdgeAccept onEdgeAccept; + + @override + State createState() => _NodeWidgetState(); +} + +class _NodeWidgetState extends State { + bool willAccept = false; + + @override + Widget build(BuildContext context) { + late T node = widget.node as T; + late Offset offset = node.offset; + late double factor = widget.factor; + late Offset dragOffset = widget.dragOffset; + + return Positioned( + left: offset.dx * factor + dragOffset.dx, + top: offset.dy * factor + dragOffset.dy, + child: Material( + elevation: 4, + borderRadius: BorderRadius.circular(4), + child: Stack( + children: [ + GestureDetector( + onPanUpdate: (details) { + // print(details); + widget.onNodeDrag(details.delta); + }, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: Colors.white, + ), + width: node.width * factor, + height: node.height * factor, + alignment: Alignment.center, + child: Text(node.label), + )), + Positioned( + right: 0, + top: 0, + child: GestureDetector( + onTap: () { + debugPrint("delete ${node.uuid}"); + }, + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.delete, + color: Colors.red, + ), + ))), + Positioned( + right: 0, + top: 0.5 * node.height * factor, + child: Draggable( + data: node.uuid, + onDragUpdate: (details) { + // print(details); + widget.onNodeEdgeCreateOrModify(details.delta); + }, + onDragEnd: (details) { + widget.onNodeEdgeCancel(); + }, + feedback: Container( + width: 5, + height: 5, + color: Colors.red, + ), + child: Container( + width: 24, + height: 24, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + child: Icon(Icons.output), + ))), + Positioned( + left: 0, + top: 0.5 * node.height * factor, + child: DragTarget( + builder: (c, _, __) { + return Container( + width: 24, + height: 24, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + ), + child: Icon( + Icons.input, + color: willAccept ? Colors.green : Colors.black, + ), + ); + }, + onWillAcceptWithDetails: (details) { + if (details.data == node.uuid) { + return false; + } + + setState(() { + willAccept = true; + }); + return true; + }, + onLeave: (details) { + setState(() { + willAccept = false; + }); + }, + onAcceptWithDetails: (data) { + debugPrint("accept ${data.data} this is ${node.uuid}"); + widget.onEdgeAccept(data.data, node.uuid); + setState(() { + willAccept = false; + }); + }, + )) + ], + ), + ), + ); + } +} diff --git a/lib/src/nodes/nodes.dart b/lib/src/nodes/nodes.dart index 76bc1cc..ee090fc 100644 --- a/lib/src/nodes/nodes.dart +++ b/lib/src/nodes/nodes.dart @@ -1,2 +1,3 @@ export './base_node.dart'; export './fishbone.dart'; +export './node_widget.dart'; diff --git a/lib/src/state.dart b/lib/src/state.dart index 8a0b3d9..fb5d8b6 100644 --- a/lib/src/state.dart +++ b/lib/src/state.dart @@ -4,20 +4,20 @@ class BoardState { final double scaleFactor; final Offset dragOffset; final List data; - final List edges; + final Set edges; BoardState({ this.scaleFactor = 1.0, this.dragOffset = Offset.zero, this.data = const [], - this.edges = const [], + this.edges = const {}, }); BoardState copyWith({ double? scaleFactor, Offset? dragOffset, List? data, - List? edges, + Set? edges, }) { return BoardState( scaleFactor: scaleFactor ?? this.scaleFactor, diff --git a/pubspec.yaml b/pubspec.yaml index 6d76ccb..5b14f03 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flow_compose description: "flow chart for flutter (FOR FUN)." -version: 0.0.1 +version: 0.0.1+1 homepage: https://github.com/guchengxi1994/flow_compose environment: From d6fc3bf69bb74b265e4b2cd199add7dce3b61b7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A3=B9=E9=9B=B6?= Date: Wed, 18 Dec 2024 13:24:23 +0800 Subject: [PATCH 4/4] update workflow --- .github/workflows/flutter_web.yml | 17 +- .github/workflows/publish.yml | 25 +++ .gitignore | 2 + example/pubspec.lock | 260 ------------------------------ 4 files changed, 43 insertions(+), 261 deletions(-) create mode 100644 .github/workflows/publish.yml delete mode 100644 example/pubspec.lock diff --git a/.github/workflows/flutter_web.yml b/.github/workflows/flutter_web.yml index f4723d5..6f87e11 100644 --- a/.github/workflows/flutter_web.yml +++ b/.github/workflows/flutter_web.yml @@ -15,6 +15,13 @@ jobs: with: flutter-version: 3.24.5 + - name: Bump build number + run: | + flutter pub global activate cider + + - name: Set tag name + run: echo "tag_name=v$(cider version)" >> $GITHUB_ENV + - name: "Web Build 🔧" run: | cd example/ @@ -26,4 +33,12 @@ jobs: uses: JamesIves/github-pages-deploy-action@v4.7.2 with: branch: gh-pages - folder: ./example/build/web \ No newline at end of file + folder: ./example/build/web + + - name: Publish release + uses: ncipollo/release-action@v1 + with: + allowUpdates: true + generateReleaseNotes: true + tag: ${{ env.tag_name }} + token: ${{ secrets.RELEASE_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..4d111b3 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,25 @@ +name: Publish to Pub.dev + +# 流程触发时机,create当有标签tag创建时触发,如 v1.0.0。当然也可以选择别的触发时机,如 push,release 等 +on: create +# push: +# branches: +# - master + +jobs: + publishing: + runs-on: ubuntu-latest + steps: + # 拉取仓库代码 + - name: "Checkout" + uses: actions/checkout@v3 + # 发布插件 + - name: Dart and Flutter Package Publisher + uses: k-paxian/dart-package-publisher@master + with: + accessToken: ${{ secrets.OAUTH_ACCESS_TOKEN }} + refreshToken: ${{ secrets.OAUTH_REFRESH_TOKEN }} + suppressBuildRunner: true + flutter: true + skipTests: true + force: true diff --git a/.gitignore b/.gitignore index 49a187c..292c0a9 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,5 @@ migrate_working_dir/ **/doc/api/ .dart_tool/ build/ + +/example/pubspec.lock \ No newline at end of file diff --git a/example/pubspec.lock b/example/pubspec.lock deleted file mode 100644 index 9fc5aca..0000000 --- a/example/pubspec.lock +++ /dev/null @@ -1,260 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - async: - dependency: transitive - description: - name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.11.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.1" - characters: - dependency: transitive - description: - name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.0" - clock: - dependency: transitive - description: - name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.1" - collection: - dependency: transitive - description: - name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.18.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.6" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.0.8" - fake_async: - dependency: transitive - description: - name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.3.1" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.1.1" - flow_compose: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.0.1" - flutter: - dependency: "direct main" - description: flutter - source: sdk - version: "0.0.0" - flutter_lints: - dependency: "direct dev" - description: - name: flutter_lints - sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - leak_tracker: - dependency: transitive - description: - name: leak_tracker - sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" - url: "https://pub.flutter-io.cn" - source: hosted - version: "10.0.5" - leak_tracker_flutter_testing: - dependency: transitive - description: - name: leak_tracker_flutter_testing - sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.5" - leak_tracker_testing: - dependency: transitive - description: - name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" - url: "https://pub.flutter-io.cn" - source: hosted - version: "3.0.1" - lints: - dependency: transitive - description: - name: lints - sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235" - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.0.0" - matcher: - dependency: transitive - description: - name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.12.16+1" - material_color_utilities: - dependency: transitive - description: - name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.11.1" - meta: - dependency: transitive - description: - name: meta - sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.15.0" - path: - dependency: transitive - description: - name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.9.0" - sky_engine: - dependency: transitive - description: flutter - source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.10.0" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.flutter-io.cn" - source: hosted - version: "7.0.0" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.11.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.2" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.2.1" - test_api: - dependency: transitive - description: - name: test_api - sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" - url: "https://pub.flutter-io.cn" - source: hosted - version: "0.7.2" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.flutter-io.cn" - source: hosted - version: "1.4.0" - uuid: - dependency: transitive - description: - name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff - url: "https://pub.flutter-io.cn" - source: hosted - version: "4.5.1" - vector_math: - dependency: transitive - description: - name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" - url: "https://pub.flutter-io.cn" - source: hosted - version: "2.1.4" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" - url: "https://pub.flutter-io.cn" - source: hosted - version: "14.2.5" -sdks: - dart: ">=3.5.4 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54"