board.dart 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  1. import 'package:flutter/material.dart';
  2. import 'package:provider/provider.dart';
  3. import 'board_column/board_column.dart';
  4. import 'board_column/board_column_data.dart';
  5. import 'board_data.dart';
  6. import 'reorder_flex/drag_target_interceptor.dart';
  7. import 'reorder_flex/reorder_flex.dart';
  8. import 'reorder_phantom/phantom_controller.dart';
  9. import '../rendering/board_overlay.dart';
  10. class AFBoardConfig {
  11. final double cornerRadius;
  12. final EdgeInsets columnPadding;
  13. final EdgeInsets columnItemPadding;
  14. final EdgeInsets footerPadding;
  15. final EdgeInsets headerPadding;
  16. final EdgeInsets cardPadding;
  17. final Color columnBackgroundColor;
  18. const AFBoardConfig({
  19. this.cornerRadius = 6.0,
  20. this.columnPadding = const EdgeInsets.symmetric(horizontal: 8),
  21. this.columnItemPadding = const EdgeInsets.symmetric(horizontal: 12),
  22. this.footerPadding = const EdgeInsets.symmetric(horizontal: 12),
  23. this.headerPadding = const EdgeInsets.symmetric(horizontal: 16),
  24. this.cardPadding = const EdgeInsets.symmetric(horizontal: 3, vertical: 4),
  25. this.columnBackgroundColor = Colors.transparent,
  26. });
  27. }
  28. class AFBoard extends StatelessWidget {
  29. /// The direction to use as the main axis.
  30. final Axis direction = Axis.vertical;
  31. ///
  32. final Widget? background;
  33. ///
  34. final AFBoardColumnCardBuilder cardBuilder;
  35. ///
  36. final AFBoardColumnHeaderBuilder? headerBuilder;
  37. ///
  38. final AFBoardColumnFooterBuilder? footBuilder;
  39. ///
  40. final AFBoardDataController dataController;
  41. final BoxConstraints columnConstraints;
  42. ///
  43. final BoardPhantomController phantomController;
  44. final ScrollController? scrollController;
  45. final AFBoardConfig config;
  46. AFBoard({
  47. required this.dataController,
  48. required this.cardBuilder,
  49. this.background,
  50. this.footBuilder,
  51. this.headerBuilder,
  52. this.scrollController,
  53. this.columnConstraints = const BoxConstraints(maxWidth: 200),
  54. this.config = const AFBoardConfig(),
  55. Key? key,
  56. }) : phantomController = BoardPhantomController(delegate: dataController),
  57. super(key: key);
  58. @override
  59. Widget build(BuildContext context) {
  60. return ChangeNotifierProvider.value(
  61. value: dataController,
  62. child: Consumer<AFBoardDataController>(
  63. builder: (context, notifier, child) {
  64. return BoardContent(
  65. config: config,
  66. dataController: dataController,
  67. scrollController: scrollController,
  68. background: background,
  69. delegate: phantomController,
  70. columnConstraints: columnConstraints,
  71. cardBuilder: cardBuilder,
  72. footBuilder: footBuilder,
  73. headerBuilder: headerBuilder,
  74. phantomController: phantomController,
  75. onReorder: dataController.moveColumn,
  76. );
  77. },
  78. ),
  79. );
  80. }
  81. }
  82. class BoardContent extends StatefulWidget {
  83. final ScrollController? scrollController;
  84. final OnDragStarted? onDragStarted;
  85. final OnReorder onReorder;
  86. final OnDragEnded? onDragEnded;
  87. final AFBoardDataController dataController;
  88. final Widget? background;
  89. final AFBoardConfig config;
  90. final ReorderFlexConfig reorderFlexConfig;
  91. final BoxConstraints columnConstraints;
  92. ///
  93. final AFBoardColumnCardBuilder cardBuilder;
  94. ///
  95. final AFBoardColumnHeaderBuilder? headerBuilder;
  96. ///
  97. final AFBoardColumnFooterBuilder? footBuilder;
  98. final OverlapDragTargetDelegate delegate;
  99. final BoardPhantomController phantomController;
  100. const BoardContent({
  101. required this.config,
  102. required this.onReorder,
  103. required this.delegate,
  104. required this.dataController,
  105. this.onDragStarted,
  106. this.onDragEnded,
  107. this.scrollController,
  108. this.background,
  109. required this.columnConstraints,
  110. required this.cardBuilder,
  111. this.footBuilder,
  112. this.headerBuilder,
  113. required this.phantomController,
  114. Key? key,
  115. }) : reorderFlexConfig = const ReorderFlexConfig(),
  116. super(key: key);
  117. @override
  118. State<BoardContent> createState() => _BoardContentState();
  119. }
  120. class _BoardContentState extends State<BoardContent> {
  121. final GlobalKey _columnContainerOverlayKey =
  122. GlobalKey(debugLabel: '$BoardContent overlay key');
  123. late BoardOverlayEntry _overlayEntry;
  124. @override
  125. void initState() {
  126. _overlayEntry = BoardOverlayEntry(
  127. builder: (BuildContext context) {
  128. final interceptor = OverlappingDragTargetInterceptor(
  129. reorderFlexId: widget.dataController.identifier,
  130. acceptedReorderFlexId: widget.dataController.columnIds,
  131. delegate: widget.delegate,
  132. );
  133. final reorderFlex = ReorderFlex(
  134. key: widget.key,
  135. config: widget.reorderFlexConfig,
  136. scrollController: widget.scrollController,
  137. onDragStarted: widget.onDragStarted,
  138. onReorder: widget.onReorder,
  139. onDragEnded: widget.onDragEnded,
  140. dataSource: widget.dataController,
  141. direction: Axis.horizontal,
  142. interceptor: interceptor,
  143. children: _buildColumns(interceptor.columnKeys),
  144. );
  145. return Stack(
  146. alignment: AlignmentDirectional.topStart,
  147. children: [
  148. if (widget.background != null)
  149. Container(
  150. clipBehavior: Clip.hardEdge,
  151. decoration: BoxDecoration(
  152. borderRadius:
  153. BorderRadius.circular(widget.config.cornerRadius),
  154. ),
  155. child: widget.background,
  156. ),
  157. reorderFlex,
  158. ],
  159. );
  160. },
  161. opaque: false,
  162. );
  163. super.initState();
  164. }
  165. @override
  166. Widget build(BuildContext context) {
  167. return BoardOverlay(
  168. key: _columnContainerOverlayKey,
  169. initialEntries: [_overlayEntry],
  170. );
  171. }
  172. List<Widget> _buildColumns(List<ColumnKey> columnKeys) {
  173. final List<Widget> children =
  174. widget.dataController.columnDatas.asMap().entries.map(
  175. (item) {
  176. final columnData = item.value;
  177. final columnIndex = item.key;
  178. final dataSource = _BoardColumnDataSourceImpl(
  179. columnId: columnData.id,
  180. dataController: widget.dataController,
  181. );
  182. return ChangeNotifierProvider.value(
  183. key: ValueKey(columnData.id),
  184. value: widget.dataController.getColumnController(columnData.id),
  185. child: Consumer<AFBoardColumnDataController>(
  186. builder: (context, value, child) {
  187. final boardColumn = AFBoardColumnWidget(
  188. margin: _marginFromIndex(columnIndex),
  189. itemMargin: widget.config.columnItemPadding,
  190. headerBuilder: _buildHeader,
  191. footBuilder: widget.footBuilder,
  192. cardBuilder: widget.cardBuilder,
  193. dataSource: dataSource,
  194. scrollController: ScrollController(),
  195. phantomController: widget.phantomController,
  196. onReorder: widget.dataController.moveColumnItem,
  197. cornerRadius: widget.config.cornerRadius,
  198. backgroundColor: widget.config.columnBackgroundColor,
  199. );
  200. // columnKeys
  201. // .removeWhere((element) => element.columnId == columnData.id);
  202. // columnKeys.add(
  203. // ColumnKey(
  204. // columnId: columnData.id,
  205. // key: boardColumn.columnGlobalKey,
  206. // ),
  207. // );
  208. return ConstrainedBox(
  209. constraints: widget.columnConstraints,
  210. child: boardColumn,
  211. );
  212. },
  213. ),
  214. );
  215. },
  216. ).toList();
  217. return children;
  218. }
  219. Widget? _buildHeader(
  220. BuildContext context, AFBoardColumnHeaderData headerData) {
  221. if (widget.headerBuilder == null) {
  222. return null;
  223. }
  224. return Selector<AFBoardColumnDataController, AFBoardColumnHeaderData>(
  225. selector: (context, controller) => controller.columnData.headerData,
  226. builder: (context, headerData, _) {
  227. return widget.headerBuilder!(context, headerData)!;
  228. },
  229. );
  230. }
  231. EdgeInsets _marginFromIndex(int index) {
  232. if (widget.dataController.columnDatas.isEmpty) {
  233. return widget.config.columnPadding;
  234. }
  235. if (index == 0) {
  236. return EdgeInsets.only(right: widget.config.columnPadding.right);
  237. }
  238. if (index == widget.dataController.columnDatas.length - 1) {
  239. return EdgeInsets.only(left: widget.config.columnPadding.left);
  240. }
  241. return widget.config.columnPadding;
  242. }
  243. }
  244. class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
  245. String columnId;
  246. final AFBoardDataController dataController;
  247. _BoardColumnDataSourceImpl({
  248. required this.columnId,
  249. required this.dataController,
  250. });
  251. @override
  252. AFBoardColumnData get columnData =>
  253. dataController.getColumnController(columnId)!.columnData;
  254. @override
  255. List<String> get acceptedColumnIds => dataController.columnIds;
  256. }