diff --git a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart index 11f4cf6910..903df92f78 100644 --- a/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart +++ b/frontend/app_flowy/lib/plugins/board/application/board_bloc.dart @@ -22,7 +22,7 @@ part 'board_bloc.freezed.dart'; class BoardBloc extends Bloc { final BoardDataController _gridDataController; - late final AFBoardDataController boardController; + late final AppFlowyBoardController boardController; final MoveRowFFIService _rowService; LinkedHashMap groupControllers = LinkedHashMap(); @@ -34,8 +34,8 @@ class BoardBloc extends Bloc { : _rowService = MoveRowFFIService(gridId: view.id), _gridDataController = BoardDataController(view: view), super(BoardState.initial(view.id)) { - boardController = AFBoardDataController( - onMoveColumn: ( + boardController = AppFlowyBoardController( + onMoveGroup: ( fromColumnId, fromIndex, toColumnId, @@ -43,7 +43,7 @@ class BoardBloc extends Bloc { ) { _moveGroup(fromColumnId, toColumnId); }, - onMoveColumnItem: ( + onMoveGroupItem: ( columnId, fromIndex, toIndex, @@ -52,15 +52,15 @@ class BoardBloc extends Bloc { final toRow = groupControllers[columnId]?.rowAtIndex(toIndex); _moveRow(fromRow, columnId, toRow); }, - onMoveColumnItemToColumn: ( - fromColumnId, + onMoveGroupItemToGroup: ( + fromGroupId, fromIndex, - toColumnId, + toGroupId, toIndex, ) { - final fromRow = groupControllers[fromColumnId]?.rowAtIndex(fromIndex); - final toRow = groupControllers[toColumnId]?.rowAtIndex(toIndex); - _moveRow(fromRow, toColumnId, toRow); + final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex); + final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex); + _moveRow(fromRow, toGroupId, toRow); }, ); @@ -165,10 +165,10 @@ class BoardBloc extends Bloc { boardController.clear(); // - List columns = groups + List columns = groups .where((group) => fieldController.getField(group.fieldId) != null) .map((group) { - return AFBoardColumnData( + return AppFlowyGroupData( id: group.groupId, name: group.desc, items: _buildRows(group), @@ -178,7 +178,7 @@ class BoardBloc extends Bloc { ), ); }).toList(); - boardController.addColumns(columns); + boardController.addGroups(columns); for (final group in groups) { final delegate = GroupControllerDelegateImpl( @@ -227,8 +227,8 @@ class BoardBloc extends Bloc { if (isClosed) return; for (final group in updatedGroups) { final columnController = - boardController.getColumnController(group.groupId); - columnController?.updateColumnName(group.desc); + boardController.getGroupController(group.groupId); + columnController?.updateGroupName(group.desc); } }, onError: (err) { @@ -243,13 +243,13 @@ class BoardBloc extends Bloc { ); } - List _buildRows(GroupPB group) { + List _buildRows(GroupPB group) { final items = group.rows.map((row) { final fieldContext = fieldController.getField(group.fieldId); return BoardColumnItem(row: row, fieldContext: fieldContext!); }).toList(); - return [...items]; + return [...items]; } Future _loadGrid(Emitter emit) async { @@ -335,7 +335,7 @@ class GridFieldEquatable extends Equatable { UnmodifiableListView get value => UnmodifiableListView(_fields); } -class BoardColumnItem extends AFColumnItem { +class BoardColumnItem extends AppFlowyGroupItem { final RowPB row; final GridFieldContext fieldContext; @@ -350,7 +350,7 @@ class BoardColumnItem extends AFColumnItem { class GroupControllerDelegateImpl extends GroupControllerDelegate { final GridFieldController fieldController; - final AFBoardDataController controller; + final AppFlowyBoardController controller; final void Function(String, RowPB, int?) onNewColumnItem; GroupControllerDelegateImpl({ @@ -369,16 +369,16 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate { if (index != null) { final item = BoardColumnItem(row: row, fieldContext: fieldContext); - controller.insertColumnItem(group.groupId, index, item); + controller.insertGroupItem(group.groupId, index, item); } else { final item = BoardColumnItem(row: row, fieldContext: fieldContext); - controller.addColumnItem(group.groupId, item); + controller.addGroupItem(group.groupId, item); } } @override void removeRow(GroupPB group, String rowId) { - controller.removeColumnItem(group.groupId, rowId); + controller.removeGroupItem(group.groupId, rowId); } @override @@ -388,7 +388,7 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate { Log.warn("FieldContext should not be null"); return; } - controller.updateColumnItem( + controller.updateGroupItem( group.groupId, BoardColumnItem(row: row, fieldContext: fieldContext), ); @@ -404,9 +404,9 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate { final item = BoardColumnItem(row: row, fieldContext: fieldContext); if (index != null) { - controller.insertColumnItem(group.groupId, index, item); + controller.insertGroupItem(group.groupId, index, item); } else { - controller.addColumnItem(group.groupId, item); + controller.addGroupItem(group.groupId, item); } onNewColumnItem(group.groupId, row, index); } diff --git a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart index 233917ec8c..d40a2f5ea9 100644 --- a/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart +++ b/frontend/app_flowy/lib/plugins/board/presentation/board_page.dart @@ -62,17 +62,15 @@ class BoardContent extends StatefulWidget { } class _BoardContentState extends State { - late ScrollController scrollController; - late AFBoardScrollManager scrollManager; + late AppFlowyBoardScrollController scrollManager; - final config = AFBoardConfig( - columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + final config = AppFlowyBoardConfig( + groupBackgroundColor: HexColor.fromHex('#F7F8FC'), ); @override void initState() { - scrollController = ScrollController(); - scrollManager = AFBoardScrollManager(); + scrollManager = AppFlowyBoardScrollController(); super.initState(); } @@ -101,10 +99,10 @@ class _BoardContentState extends State { Expanded _buildBoard(BuildContext context) { return Expanded( - child: AFBoard( - scrollManager: scrollManager, - scrollController: scrollController, - dataController: context.read().boardController, + child: AppFlowyBoard( + boardScrollController: scrollManager, + scrollController: ScrollController(), + controller: context.read().boardController, headerBuilder: _buildHeader, footerBuilder: _buildFooter, cardBuilder: (_, column, columnItem) => _buildCard( @@ -112,9 +110,9 @@ class _BoardContentState extends State { column, columnItem, ), - columnConstraints: const BoxConstraints.tightFor(width: 300), - config: AFBoardConfig( - columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + groupConstraints: const BoxConstraints.tightFor(width: 300), + config: AppFlowyBoardConfig( + groupBackgroundColor: HexColor.fromHex('#F7F8FC'), ), ), ); @@ -143,20 +141,19 @@ class _BoardContentState extends State { @override void dispose() { - scrollController.dispose(); super.dispose(); } Widget _buildHeader( BuildContext context, - AFBoardColumnData columnData, + AppFlowyGroupData columnData, ) { final boardCustomData = columnData.customData as BoardCustomData; - return AppFlowyColumnHeader( + return AppFlowyGroupHeader( title: Flexible( fit: FlexFit.tight, child: FlowyText.medium( - columnData.headerData.columnName, + columnData.headerData.groupName, fontSize: 14, overflow: TextOverflow.clip, color: context.read().textColor, @@ -181,14 +178,14 @@ class _BoardContentState extends State { ); } - Widget _buildFooter(BuildContext context, AFBoardColumnData columnData) { + Widget _buildFooter(BuildContext context, AppFlowyGroupData columnData) { final boardCustomData = columnData.customData as BoardCustomData; final group = boardCustomData.group; if (group.isDefault) { return const SizedBox(); } else { - return AppFlowyColumnFooter( + return AppFlowyGroupFooter( icon: SizedBox( height: 20, width: 20, @@ -215,8 +212,8 @@ class _BoardContentState extends State { Widget _buildCard( BuildContext context, - AFBoardColumnData column, - AFColumnItem columnItem, + AppFlowyGroupData column, + AppFlowyGroupItem columnItem, ) { final boardColumnItem = columnItem as BoardColumnItem; final rowPB = boardColumnItem.row; @@ -242,7 +239,7 @@ class _BoardContentState extends State { }, ); - return AppFlowyColumnItemCard( + return AppFlowyGroupCard( key: ValueKey(columnItem.id), margin: config.cardPadding, decoration: _makeBoxDecoration(context), diff --git a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md index d8eceeefee..56115c698b 100644 --- a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md +++ b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md @@ -1,3 +1,6 @@ +# 0.0.7 +* Rename some classes +* Add documentation # 0.0.6 * Support scroll to bottom * Fix some bugs diff --git a/frontend/app_flowy/packages/appflowy_board/README.md b/frontend/app_flowy/packages/appflowy_board/README.md index 8da1064699..7d879a491b 100644 --- a/frontend/app_flowy/packages/appflowy_board/README.md +++ b/frontend/app_flowy/packages/appflowy_board/README.md @@ -9,6 +9,7 @@ Twitter

+

@@ -30,30 +31,101 @@ Add the AppFlowy Board [Flutter package](https://docs.flutter.dev/development/pa With Flutter: ```dart flutter pub add appflowy_board +flutter pub get ``` -This will add a line like this to your package's pubspec.yaml (and run an implicit flutter pub get): +This will add a line like this to your package's pubspec.yaml: ```dart dependencies: appflowy_board: ^0.0.6 ``` -Import the package in your Dart file: +## Create your first board + +Initialize an `AppFlowyBoardController` for the board. It contains the data used by the board. You can +register callbacks to receive the changes of the board. + ```dart -import 'package:appflowy_board/appflowy_board.dart'; + +final AppFlowyBoardController controller = AppFlowyBoardController( + onMoveGroup: (fromGroupId, fromIndex, toGroupId, toIndex) { + debugPrint('Move item from $fromIndex to $toIndex'); + }, + onMoveGroupItem: (groupId, fromIndex, toIndex) { + debugPrint('Move $groupId:$fromIndex to $groupId:$toIndex'); + }, + onMoveGroupItemToGroup: (fromGroupId, fromIndex, toGroupId, toIndex) { + debugPrint('Move $fromGroupId:$fromIndex to $toGroupId:$toIndex'); + }, +); +``` + +Provide an initial value of the board by initializing the `AppFlowyGroupData`. It represents a group data and contains list of items. Each item displayed in the group requires to implement the `AppFlowyGroupItem` class. + +```dart + +void initState() { + final group1 = AppFlowyGroupData(id: "To Do", items: [ + TextItem("Card 1"), + TextItem("Card 2"), + ]); + final group2 = AppFlowyGroupData(id: "In Progress", items: [ + TextItem("Card 3"), + TextItem("Card 4"), + ]); + + final group3 = AppFlowyGroupData(id: "Done", items: []); + + controller.addGroup(group1); + controller.addGroup(group2); + controller.addGroup(group3); + super.initState(); +} + +class TextItem extends AppFlowyGroupItem { + final String s; + TextItem(this.s); + + @override + String get id => s; +} + +``` + +Finally, return a `AppFlowyBoard` widget in the build method. + +```dart + +@override +Widget build(BuildContext context) { + return AppFlowyBoard( + controller: controller, + cardBuilder: (context, group, groupItem) { + final textItem = groupItem as TextItem; + return AppFlowyGroupCard( + key: ObjectKey(textItem), + child: Text(textItem.s), + ); + }, + groupConstraints: const BoxConstraints.tightFor(width: 240), + ); +} + ``` ## Usage Example To quickly grasp how it can be used, look at the /example/lib folder. First, run main.dart to play with the demo. + Second, let's delve into multi_board_list_example.dart to understand a few key components: -* A Board widget is created via instantiating an AFBoard() object. -* In the AFBoard() object, you can find: - * AFBoardDataController, which is defined in board_data.dart, is feeded with prepopulated mock data. It also contains callback functions to materialize future user data. - * Three builders: AppFlowyColumnHeader, AppFlowyColumnFooter, AppFlowyColumnItemCard. See below image for what they are used for. +* A Board widget is created via instantiating an `AppFlowyBoard` object. +* In the `AppFlowyBoard` object, you can find the `AppFlowyBoardController`, which is defined in board_data.dart, is fed with pre-populated mock data. It also contains callback functions to materialize future user data. +* Three builders: AppFlowyBoardHeaderBuilder, AppFlowyBoardFooterBuilder, AppFlowyBoardCardBuilder. See below image for what they are used for. + +

- +

## Glossary @@ -66,6 +138,3 @@ Please look at [CONTRIBUTING.md](https://appflowy.gitbook.io/docs/essential-docu ## License Distributed under the AGPLv3 License. See [LICENSE](https://github.com/AppFlowy-IO/AppFlowy-Docs/blob/main/LICENSE) for more information. - - - diff --git a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart index c0fc62f8e2..8bd7ae98e3 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart @@ -9,21 +9,21 @@ class MultiBoardListExample extends StatefulWidget { } class _MultiBoardListExampleState extends State { - final AFBoardDataController boardDataController = AFBoardDataController( - onMoveColumn: (fromColumnId, fromIndex, toColumnId, toIndex) { - // debugPrint('Move column from $fromIndex to $toIndex'); + final AppFlowyBoardController controller = AppFlowyBoardController( + onMoveGroup: (fromGroupId, fromIndex, toGroupId, toIndex) { + debugPrint('Move item from $fromIndex to $toIndex'); }, - onMoveColumnItem: (columnId, fromIndex, toIndex) { - // debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex'); + onMoveGroupItem: (groupId, fromIndex, toIndex) { + debugPrint('Move $groupId:$fromIndex to $groupId:$toIndex'); }, - onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) { - // debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex'); + onMoveGroupItemToGroup: (fromGroupId, fromIndex, toGroupId, toIndex) { + debugPrint('Move $fromGroupId:$fromIndex to $toGroupId:$toIndex'); }, ); @override void initState() { - List a = [ + final group1 = AppFlowyGroupData(id: "To Do", name: "To Do", items: [ TextItem("Card 1"), TextItem("Card 2"), RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'), @@ -33,84 +33,74 @@ class _MultiBoardListExampleState extends State { RichTextItem(title: "Card 7", subtitle: 'Aug 1, 2020 4:05 PM'), RichTextItem(title: "Card 8", subtitle: 'Aug 1, 2020 4:05 PM'), TextItem("Card 9"), - ]; + ]); - final column1 = AFBoardColumnData(id: "To Do", name: "To Do", items: a); - final column2 = AFBoardColumnData( + final group2 = AppFlowyGroupData( id: "In Progress", name: "In Progress", - items: [ + items: [ RichTextItem(title: "Card 10", subtitle: 'Aug 1, 2020 4:05 PM'), TextItem("Card 11"), ], ); - final column3 = - AFBoardColumnData(id: "Done", name: "Done", items: []); + final group3 = AppFlowyGroupData( + id: "Done", name: "Done", items: []); - boardDataController.addColumn(column1); - boardDataController.addColumn(column2); - boardDataController.addColumn(column3); + controller.addGroup(group1); + controller.addGroup(group2); + controller.addGroup(group3); super.initState(); } @override Widget build(BuildContext context) { - final config = AFBoardConfig( - columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + final config = AppFlowyBoardConfig( + groupBackgroundColor: HexColor.fromHex('#F7F8FC'), ); - return Container( - color: Colors.white, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), - child: AFBoard( - dataController: boardDataController, - footerBuilder: (context, columnData) { - return AppFlowyColumnFooter( - icon: const Icon(Icons.add, size: 20), - title: const Text('New'), - height: 50, - margin: config.columnItemPadding, - ); - }, - headerBuilder: (context, columnData) { - return AppFlowyColumnHeader( - icon: const Icon(Icons.lightbulb_circle), - title: SizedBox( - width: 60, - child: TextField( - controller: TextEditingController() - ..text = columnData.headerData.columnName, - onSubmitted: (val) { - boardDataController - .getColumnController(columnData.headerData.columnId)! - .updateColumnName(val); - }, - ), + return AppFlowyBoard( + controller: controller, + cardBuilder: (context, group, groupItem) { + return AppFlowyGroupCard( + key: ValueKey(groupItem.id), + child: _buildCard(groupItem), + ); + }, + footerBuilder: (context, columnData) { + return AppFlowyGroupFooter( + icon: const Icon(Icons.add, size: 20), + title: const Text('New'), + height: 50, + margin: config.groupItemPadding, + ); + }, + headerBuilder: (context, columnData) { + return AppFlowyGroupHeader( + icon: const Icon(Icons.lightbulb_circle), + title: SizedBox( + width: 60, + child: TextField( + controller: TextEditingController() + ..text = columnData.headerData.groupName, + onSubmitted: (val) { + controller + .getGroupController(columnData.headerData.groupId)! + .updateGroupName(val); + }, ), - addIcon: const Icon(Icons.add, size: 20), - moreIcon: const Icon(Icons.more_horiz, size: 20), - height: 50, - margin: config.columnItemPadding, - ); - }, - cardBuilder: (context, column, columnItem) { - return AppFlowyColumnItemCard( - key: ValueKey(columnItem.id), - child: _buildCard(columnItem), - ); - }, - columnConstraints: const BoxConstraints.tightFor(width: 240), - config: AFBoardConfig( - columnBackgroundColor: HexColor.fromHex('#F7F8FC'), - ), - ), - ), - ); + ), + addIcon: const Icon(Icons.add, size: 20), + moreIcon: const Icon(Icons.more_horiz, size: 20), + height: 50, + margin: config.groupItemPadding, + ); + }, + groupConstraints: const BoxConstraints.tightFor(width: 240), + config: config); } - Widget _buildCard(AFColumnItem item) { + Widget _buildCard(AppFlowyGroupItem item) { if (item is TextItem) { return Align( alignment: Alignment.centerLeft, @@ -172,7 +162,7 @@ class _RichTextCardState extends State { } } -class TextItem extends AFColumnItem { +class TextItem extends AppFlowyGroupItem { final String s; TextItem(this.s); @@ -181,7 +171,7 @@ class TextItem extends AFColumnItem { String get id => s; } -class RichTextItem extends AFColumnItem { +class RichTextItem extends AppFlowyGroupItem { final String title; final String subtitle; diff --git a/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart index f22c562343..c222856efa 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart @@ -9,11 +9,11 @@ class SingleBoardListExample extends StatefulWidget { } class _SingleBoardListExampleState extends State { - final AFBoardDataController boardData = AFBoardDataController(); + final AppFlowyBoardController boardData = AppFlowyBoardController(); @override void initState() { - final column = AFBoardColumnData( + final column = AppFlowyGroupData( id: "1", name: "1", items: [ @@ -24,14 +24,14 @@ class _SingleBoardListExampleState extends State { ], ); - boardData.addColumn(column); + boardData.addGroup(column); super.initState(); } @override Widget build(BuildContext context) { - return AFBoard( - dataController: boardData, + return AppFlowyBoard( + controller: boardData, cardBuilder: (context, column, columnItem) { return _RowWidget( item: columnItem as TextItem, key: ObjectKey(columnItem)); @@ -55,7 +55,7 @@ class _RowWidget extends StatelessWidget { } } -class TextItem extends AFColumnItem { +class TextItem extends AppFlowyGroupItem { final String s; TextItem(this.s); diff --git a/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart b/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart index fc8f3c662f..6125cf29e6 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart @@ -1,6 +1,7 @@ +/// AppFlowyBoard library library appflowy_board; -export 'src/widgets/board_column/board_column_data.dart'; +export 'src/widgets/board_group/group_data.dart'; export 'src/widgets/board_data.dart'; -export 'src/widgets/styled_widgets/appflowy_styled_widgets.dart'; +export 'src/widgets/styled_widgets/widgets.dart'; export 'src/widgets/board.dart'; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart index 96874a1425..4a71595ea6 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart @@ -1,121 +1,147 @@ import 'package:appflowy_board/src/utils/log.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import 'board_column/board_column.dart'; -import 'board_column/board_column_data.dart'; import 'board_data.dart'; +import 'board_group/group.dart'; +import 'board_group/group_data.dart'; import 'reorder_flex/drag_state.dart'; import 'reorder_flex/drag_target_interceptor.dart'; import 'reorder_flex/reorder_flex.dart'; import 'reorder_phantom/phantom_controller.dart'; import '../rendering/board_overlay.dart'; -class AFBoardScrollManager { - BoardColumnsState? _columnState; +class AppFlowyBoardScrollController { + AppFlowyBoardState? _groupState; - // AFBoardScrollManager(); - - void scrollToBottom(String columnId, VoidCallback? completed) { - _columnState - ?.getReorderFlexState(columnId: columnId) + void scrollToBottom(String groupId, VoidCallback? completed) { + _groupState + ?.getReorderFlexState(groupId: groupId) ?.scrollToBottom(completed); } } -class AFBoardConfig { +class AppFlowyBoardConfig { final double cornerRadius; - final EdgeInsets columnPadding; - final EdgeInsets columnItemPadding; + final EdgeInsets groupPadding; + final EdgeInsets groupItemPadding; final EdgeInsets footerPadding; final EdgeInsets headerPadding; final EdgeInsets cardPadding; - final Color columnBackgroundColor; + final Color groupBackgroundColor; - const AFBoardConfig({ + const AppFlowyBoardConfig({ this.cornerRadius = 6.0, - this.columnPadding = const EdgeInsets.symmetric(horizontal: 8), - this.columnItemPadding = const EdgeInsets.symmetric(horizontal: 12), + this.groupPadding = const EdgeInsets.symmetric(horizontal: 8), + this.groupItemPadding = const EdgeInsets.symmetric(horizontal: 12), this.footerPadding = const EdgeInsets.symmetric(horizontal: 12), this.headerPadding = const EdgeInsets.symmetric(horizontal: 16), this.cardPadding = const EdgeInsets.symmetric(horizontal: 3, vertical: 4), - this.columnBackgroundColor = Colors.transparent, + this.groupBackgroundColor = Colors.transparent, }); } -class AFBoard extends StatelessWidget { +class AppFlowyBoard extends StatelessWidget { /// The direction to use as the main axis. final Axis direction = Axis.vertical; - /// + /// The widget that will be rendered as the background of the board. final Widget? background; + /// The [cardBuilder] function which will be invoked on each card build. + /// The [cardBuilder] takes the [BuildContext],[AppFlowyGroupData] and + /// the corresponding [AppFlowyGroupItem]. /// - final AFBoardColumnCardBuilder cardBuilder; + /// must return a widget. + final AppFlowyBoardCardBuilder cardBuilder; + /// The [headerBuilder] function which will be invoked on each group build. + /// The [headerBuilder] takes the [BuildContext] and [AppFlowyGroupData]. /// - final AFBoardColumnHeaderBuilder? headerBuilder; + /// must return a widget. + final AppFlowyBoardHeaderBuilder? headerBuilder; + /// The [footerBuilder] function which will be invoked on each group build. + /// The [footerBuilder] takes the [BuildContext] and [AppFlowyGroupData]. /// - final AFBoardColumnFooterBuilder? footerBuilder; + /// must return a widget. + final AppFlowyBoardFooterBuilder? footerBuilder; + /// A controller for [AppFlowyBoard] widget. /// - final AFBoardDataController dataController; - - final BoxConstraints columnConstraints; - + /// A [AppFlowyBoardController] can be used to provide an initial value of + /// the board by calling `addGroup` method with the passed in parameter + /// [AppFlowyGroupData]. A [AppFlowyGroupData] represents one + /// group data. Whenever the user modifies the board, this controller will + /// update the corresponding group data. /// - late final BoardPhantomController phantomController; + /// Also, you can register the callbacks that receive the changes. Check out + /// the [AppFlowyBoardController] for more information. + /// + final AppFlowyBoardController controller; + /// A constraints applied to [AppFlowyBoardGroup] widget. + final BoxConstraints groupConstraints; + + /// A controller is used by the [ReorderFlex]. + /// + /// The [ReorderFlex] will used the primary scrollController of the current + /// [BuildContext] by using PrimaryScrollController.of(context). + /// If the primary scrollController is null, we will assign a new [ScrollController]. final ScrollController? scrollController; - final AFBoardConfig config; + /// + final AppFlowyBoardConfig config; - final AFBoardScrollManager? scrollManager; + /// A controller is used to control each group scroll actions. + /// + final AppFlowyBoardScrollController? boardScrollController; - final BoardColumnsState _columnState = BoardColumnsState(); + final AppFlowyBoardState _groupState = AppFlowyBoardState(); - AFBoard({ - required this.dataController, + late final BoardPhantomController _phantomController; + + AppFlowyBoard({ + required this.controller, required this.cardBuilder, this.background, this.footerBuilder, this.headerBuilder, this.scrollController, - this.scrollManager, - this.columnConstraints = const BoxConstraints(maxWidth: 200), - this.config = const AFBoardConfig(), + this.boardScrollController, + this.groupConstraints = const BoxConstraints(maxWidth: 200), + this.config = const AppFlowyBoardConfig(), Key? key, }) : super(key: key) { - phantomController = BoardPhantomController( - delegate: dataController, - columnsState: _columnState, + _phantomController = BoardPhantomController( + delegate: controller, + groupsState: _groupState, ); } @override Widget build(BuildContext context) { return ChangeNotifierProvider.value( - value: dataController, - child: Consumer( + value: controller, + child: Consumer( builder: (context, notifier, child) { - if (scrollManager != null) { - scrollManager!._columnState = _columnState; + if (boardScrollController != null) { + boardScrollController!._groupState = _groupState; } - return AFBoardContent( + return _AppFlowyBoardContent( config: config, - dataController: dataController, + dataController: controller, scrollController: scrollController, - scrollManager: scrollManager, - columnsState: _columnState, + scrollManager: boardScrollController, + columnsState: _groupState, background: background, - delegate: phantomController, - columnConstraints: columnConstraints, + delegate: _phantomController, + groupConstraints: groupConstraints, cardBuilder: cardBuilder, - footBuilder: footerBuilder, + footerBuilder: footerBuilder, headerBuilder: headerBuilder, - phantomController: phantomController, - onReorder: dataController.moveColumn, + phantomController: _phantomController, + onReorder: controller.moveGroup, ); }, ), @@ -123,46 +149,34 @@ class AFBoard extends StatelessWidget { } } -class AFBoardContent extends StatefulWidget { +class _AppFlowyBoardContent extends StatefulWidget { final ScrollController? scrollController; - final OnDragStarted? onDragStarted; final OnReorder onReorder; - final OnDragEnded? onDragEnded; - final AFBoardDataController dataController; + final AppFlowyBoardController dataController; final Widget? background; - final AFBoardConfig config; + final AppFlowyBoardConfig config; final ReorderFlexConfig reorderFlexConfig; - final BoxConstraints columnConstraints; - final AFBoardScrollManager? scrollManager; - final BoardColumnsState columnsState; - - /// - final AFBoardColumnCardBuilder cardBuilder; - - /// - final AFBoardColumnHeaderBuilder? headerBuilder; - - /// - final AFBoardColumnFooterBuilder? footBuilder; - + final BoxConstraints groupConstraints; + final AppFlowyBoardScrollController? scrollManager; + final AppFlowyBoardState columnsState; + final AppFlowyBoardCardBuilder cardBuilder; + final AppFlowyBoardHeaderBuilder? headerBuilder; + final AppFlowyBoardFooterBuilder? footerBuilder; final OverlapDragTargetDelegate delegate; - final BoardPhantomController phantomController; - const AFBoardContent({ + const _AppFlowyBoardContent({ required this.config, required this.onReorder, required this.delegate, required this.dataController, required this.scrollManager, required this.columnsState, - this.onDragStarted, - this.onDragEnded, this.scrollController, this.background, - required this.columnConstraints, + required this.groupConstraints, required this.cardBuilder, - this.footBuilder, + this.footerBuilder, this.headerBuilder, required this.phantomController, Key? key, @@ -170,21 +184,23 @@ class AFBoardContent extends StatefulWidget { super(key: key); @override - State createState() => _AFBoardContentState(); + State<_AppFlowyBoardContent> createState() => _AppFlowyBoardContentState(); } -class _AFBoardContentState extends State { +class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> { final GlobalKey _boardContentKey = - GlobalKey(debugLabel: '$AFBoardContent overlay key'); + GlobalKey(debugLabel: '$_AppFlowyBoardContent overlay key'); late BoardOverlayEntry _overlayEntry; + final Map _reorderFlexKeys = {}; + @override void initState() { _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { final interceptor = OverlappingDragTargetInterceptor( reorderFlexId: widget.dataController.identifier, - acceptedReorderFlexId: widget.dataController.columnIds, + acceptedReorderFlexId: widget.dataController.groupIds, delegate: widget.delegate, columnsState: widget.columnsState, ); @@ -192,9 +208,7 @@ class _AFBoardContentState extends State { final reorderFlex = ReorderFlex( config: widget.reorderFlexConfig, scrollController: widget.scrollController, - onDragStarted: widget.onDragStarted, onReorder: widget.onReorder, - onDragEnded: widget.onDragEnded, dataSource: widget.dataController, direction: Axis.horizontal, interceptor: interceptor, @@ -233,42 +247,47 @@ class _AFBoardContentState extends State { List _buildColumns() { final List children = - widget.dataController.columnDatas.asMap().entries.map( + widget.dataController.groupDatas.asMap().entries.map( (item) { final columnData = item.value; final columnIndex = item.key; - final dataSource = _BoardColumnDataSourceImpl( - columnId: columnData.id, + final dataSource = _BoardGroupDataSourceImpl( + groupId: columnData.id, dataController: widget.dataController, ); + if (_reorderFlexKeys[columnData.id] == null) { + _reorderFlexKeys[columnData.id] = GlobalObjectKey(columnData.id); + } + + GlobalObjectKey reorderFlexKey = _reorderFlexKeys[columnData.id]!; return ChangeNotifierProvider.value( key: ValueKey(columnData.id), - value: widget.dataController.getColumnController(columnData.id), - child: Consumer( + value: widget.dataController.getGroupController(columnData.id), + child: Consumer( builder: (context, value, child) { - final boardColumn = AFBoardColumnWidget( + final boardColumn = AppFlowyBoardGroup( + reorderFlexKey: reorderFlexKey, // key: PageStorageKey(columnData.id), margin: _marginFromIndex(columnIndex), - itemMargin: widget.config.columnItemPadding, + itemMargin: widget.config.groupItemPadding, headerBuilder: _buildHeader, - footBuilder: widget.footBuilder, + footerBuilder: widget.footerBuilder, cardBuilder: widget.cardBuilder, dataSource: dataSource, scrollController: ScrollController(), phantomController: widget.phantomController, - onReorder: widget.dataController.moveColumnItem, + onReorder: widget.dataController.moveGroupItem, cornerRadius: widget.config.cornerRadius, - backgroundColor: widget.config.columnBackgroundColor, + backgroundColor: widget.config.groupBackgroundColor, dragStateStorage: widget.columnsState, dragTargetIndexKeyStorage: widget.columnsState, ); - widget.columnsState.addColumn(columnData.id, boardColumn); - + widget.columnsState.addGroup(columnData.id, boardColumn); return ConstrainedBox( - constraints: widget.columnConstraints, + constraints: widget.groupConstraints, child: boardColumn, ); }, @@ -282,79 +301,82 @@ class _AFBoardContentState extends State { Widget? _buildHeader( BuildContext context, - AFBoardColumnData columnData, + AppFlowyGroupData groupData, ) { if (widget.headerBuilder == null) { return null; } - return Selector( - selector: (context, controller) => controller.columnData.headerData, + return Selector( + selector: (context, controller) => controller.groupData.headerData, builder: (context, headerData, _) { - return widget.headerBuilder!(context, columnData)!; + return widget.headerBuilder!(context, groupData)!; }, ); } EdgeInsets _marginFromIndex(int index) { - if (widget.dataController.columnDatas.isEmpty) { - return widget.config.columnPadding; + if (widget.dataController.groupDatas.isEmpty) { + return widget.config.groupPadding; } if (index == 0) { - return EdgeInsets.only(right: widget.config.columnPadding.right); + return EdgeInsets.only(right: widget.config.groupPadding.right); } - if (index == widget.dataController.columnDatas.length - 1) { - return EdgeInsets.only(left: widget.config.columnPadding.left); + if (index == widget.dataController.groupDatas.length - 1) { + return EdgeInsets.only(left: widget.config.groupPadding.left); } - return widget.config.columnPadding; + return widget.config.groupPadding; } } -class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource { - String columnId; - final AFBoardDataController dataController; +class _BoardGroupDataSourceImpl extends AppFlowyGroupDataDataSource { + String groupId; + final AppFlowyBoardController dataController; - _BoardColumnDataSourceImpl({ - required this.columnId, + _BoardGroupDataSourceImpl({ + required this.groupId, required this.dataController, }); @override - AFBoardColumnData get columnData => - dataController.getColumnController(columnId)!.columnData; + AppFlowyGroupData get groupData => + dataController.getGroupController(groupId)!.groupData; @override - List get acceptedColumnIds => dataController.columnIds; + List get acceptedGroupIds => dataController.groupIds; } -class BoardColumnContext { - GlobalKey? columnKey; +/// A context contains the group states including the draggingState. +/// +/// [draggingState] represents the dragging state of the group. +class AppFlowyGroupContext { DraggingState? draggingState; } -class BoardColumnsState extends DraggingStateStorage +class AppFlowyBoardState extends DraggingStateStorage with ReorderDragTargetIndexKeyStorage { - /// Quick access to the [AFBoardColumnWidget] - final Map columnKeys = {}; - final Map columnDragStates = {}; - final Map> columnDragDragTargets = {}; + /// Quick access to the [AppFlowyBoardGroup], the [GlobalKey] is bind to the + /// AppFlowyBoardGroup's [ReorderFlex] widget. + final Map groupReorderFlexKeys = {}; + final Map groupDragStates = {}; + final Map> groupDragDragTargets = {}; - void addColumn(String columnId, AFBoardColumnWidget columnWidget) { - columnKeys[columnId] = columnWidget.globalKey; + void addGroup(String groupId, AppFlowyBoardGroup groupWidget) { + groupReorderFlexKeys[groupId] = groupWidget.reorderFlexKey; } - ReorderFlexState? getReorderFlexState({required String columnId}) { - final flexGlobalKey = columnKeys[columnId]; + ReorderFlexState? getReorderFlexState({required String groupId}) { + final flexGlobalKey = groupReorderFlexKeys[groupId]; if (flexGlobalKey == null) return null; if (flexGlobalKey.currentState is! ReorderFlexState) return null; final state = flexGlobalKey.currentState as ReorderFlexState; return state; } - ReorderFlex? getReorderFlex({required String columnId}) { - final flexGlobalKey = columnKeys[columnId]; + ReorderFlex? getReorderFlex({required String groupId}) { + final flexGlobalKey = groupReorderFlexKeys[groupId]; if (flexGlobalKey == null) return null; if (flexGlobalKey.currentWidget is! ReorderFlex) return null; final widget = flexGlobalKey.currentWidget as ReorderFlex; @@ -363,18 +385,18 @@ class BoardColumnsState extends DraggingStateStorage @override DraggingState? read(String reorderFlexId) { - return columnDragStates[reorderFlexId]; + return groupDragStates[reorderFlexId]; } @override void write(String reorderFlexId, DraggingState state) { Log.trace('$reorderFlexId Write dragging state: $state'); - columnDragStates[reorderFlexId] = state; + groupDragStates[reorderFlexId] = state; } @override void remove(String reorderFlexId) { - columnDragStates.remove(reorderFlexId); + groupDragStates.remove(reorderFlexId); } @override @@ -383,20 +405,20 @@ class BoardColumnsState extends DraggingStateStorage String key, GlobalObjectKey> value, ) { - Map? column = columnDragDragTargets[reorderFlexId]; - if (column == null) { - column = {}; - columnDragDragTargets[reorderFlexId] = column; + Map? group = groupDragDragTargets[reorderFlexId]; + if (group == null) { + group = {}; + groupDragDragTargets[reorderFlexId] = group; } - column[key] = value; + group[key] = value; } @override GlobalObjectKey>? readKey( String reorderFlexId, String key) { - Map? column = columnDragDragTargets[reorderFlexId]; - if (column != null) { - return column[key]; + Map? group = groupDragDragTargets[reorderFlexId]; + if (group != null) { + return group[key]; } else { return null; } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart deleted file mode 100644 index bc442acd2a..0000000000 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart +++ /dev/null @@ -1,192 +0,0 @@ -import 'dart:collection'; - -import 'package:equatable/equatable.dart'; -import 'package:flutter/material.dart'; -import '../../utils/log.dart'; -import '../reorder_flex/reorder_flex.dart'; - -abstract class AFColumnItem extends ReoderFlexItem { - bool get isPhantom => false; - - @override - String toString() => id; -} - -/// [AFBoardColumnDataController] is used to handle the [AFBoardColumnData]. -/// * Remove an item by calling [removeAt] method. -/// * Move item to another position by calling [move] method. -/// * Insert item to index by calling [insert] method -/// * Replace item at index by calling [replace] method. -/// -/// All there operations will notify listeners by default. -/// -class AFBoardColumnDataController extends ChangeNotifier with EquatableMixin { - final AFBoardColumnData columnData; - - AFBoardColumnDataController({ - required this.columnData, - }); - - @override - List get props => columnData.props; - - /// Returns the readonly List - UnmodifiableListView get items => - UnmodifiableListView(columnData.items); - - void updateColumnName(String newName) { - if (columnData.headerData.columnName != newName) { - columnData.headerData.columnName = newName; - notifyListeners(); - } - } - - /// Remove the item at [index]. - /// * [index] the index of the item you want to remove - /// * [notify] the default value of [notify] is true, it will notify the - /// listener. Set to [false] if you do not want to notify the listeners. - /// - AFColumnItem removeAt(int index, {bool notify = true}) { - assert(index >= 0); - - Log.debug( - '[$AFBoardColumnDataController] $columnData remove item at $index'); - final item = columnData._items.removeAt(index); - if (notify) { - notifyListeners(); - } - return item; - } - - void removeWhere(bool Function(AFColumnItem) condition) { - final index = items.indexWhere(condition); - if (index != -1) { - removeAt(index); - } - } - - /// Move the item from [fromIndex] to [toIndex]. It will do nothing if the - /// [fromIndex] equal to the [toIndex]. - bool move(int fromIndex, int toIndex) { - assert(fromIndex >= 0); - assert(toIndex >= 0); - - if (fromIndex == toIndex) { - return false; - } - Log.debug( - '[$AFBoardColumnDataController] $columnData move item from $fromIndex to $toIndex'); - final item = columnData._items.removeAt(fromIndex); - columnData._items.insert(toIndex, item); - notifyListeners(); - return true; - } - - /// Insert an item to [index] and notify the listen if the value of [notify] - /// is true. - /// - /// The default value of [notify] is true. - bool insert(int index, AFColumnItem item, {bool notify = true}) { - assert(index >= 0); - Log.debug( - '[$AFBoardColumnDataController] $columnData insert $item at $index'); - - if (_containsItem(item)) { - return false; - } else { - if (columnData._items.length > index) { - columnData._items.insert(index, item); - } else { - columnData._items.add(item); - } - - if (notify) notifyListeners(); - return true; - } - } - - bool add(AFColumnItem item, {bool notify = true}) { - if (_containsItem(item)) { - return false; - } else { - columnData._items.add(item); - if (notify) notifyListeners(); - return true; - } - } - - /// Replace the item at index with the [newItem]. - void replace(int index, AFColumnItem newItem) { - if (columnData._items.isEmpty) { - columnData._items.add(newItem); - Log.debug('[$AFBoardColumnDataController] $columnData add $newItem'); - } else { - if (index >= columnData._items.length) { - return; - } - - final removedItem = columnData._items.removeAt(index); - columnData._items.insert(index, newItem); - Log.debug( - '[$AFBoardColumnDataController] $columnData replace $removedItem with $newItem at $index'); - } - - notifyListeners(); - } - - void replaceOrInsertItem(AFColumnItem newItem) { - final index = columnData._items.indexWhere((item) => item.id == newItem.id); - if (index != -1) { - columnData._items.removeAt(index); - columnData._items.insert(index, newItem); - notifyListeners(); - } else { - columnData._items.add(newItem); - notifyListeners(); - } - } - - bool _containsItem(AFColumnItem item) { - return columnData._items.indexWhere((element) => element.id == item.id) != - -1; - } -} - -/// [AFBoardColumnData] represents the data of each Column of the Board. -class AFBoardColumnData extends ReoderFlexItem with EquatableMixin { - @override - final String id; - AFBoardColumnHeaderData headerData; - final List _items; - final CustomData? customData; - - AFBoardColumnData({ - this.customData, - required this.id, - required String name, - List items = const [], - }) : _items = items, - headerData = AFBoardColumnHeaderData( - columnId: id, - columnName: name, - ); - - /// Returns the readonly List - UnmodifiableListView get items => - UnmodifiableListView([..._items]); - - @override - List get props => [id, ..._items]; - - @override - String toString() { - return 'Column:[$id]'; - } -} - -class AFBoardColumnHeaderData { - String columnId; - String columnName; - - AFBoardColumnHeaderData({required this.columnId, required this.columnName}); -} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart index 47e74b37f5..9b2222af59 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart @@ -1,220 +1,288 @@ import 'dart:collection'; +import 'package:appflowy_board/src/widgets/board_group/group_data.dart'; import 'package:equatable/equatable.dart'; import '../utils/log.dart'; -import 'board_column/board_column_data.dart'; import 'reorder_flex/reorder_flex.dart'; import 'package:flutter/material.dart'; import 'reorder_phantom/phantom_controller.dart'; -typedef OnMoveColumn = void Function( - String fromColumnId, +typedef OnMoveGroup = void Function( + String fromGroupId, int fromIndex, - String toColumnId, + String toGroupId, int toIndex, ); -typedef OnMoveColumnItem = void Function( - String columnId, +typedef OnMoveGroupItem = void Function( + String groupId, int fromIndex, int toIndex, ); -typedef OnMoveColumnItemToColumn = void Function( - String fromColumnId, +typedef OnMoveGroupItemToGroup = void Function( + String fromGroupId, int fromIndex, - String toColumnId, + String toGroupId, int toIndex, ); -class AFBoardDataController extends ChangeNotifier +/// A controller for [AppFlowyBoard] widget. +/// +/// A [AppFlowyBoardController] can be used to provide an initial value of +/// the board by calling `addGroup` method with the passed in parameter +/// [AppFlowyGroupData]. A [AppFlowyGroupData] represents one +/// group data. Whenever the user modifies the board, this controller will +/// update the corresponding group data. +/// +/// Also, you can register the callbacks that receive the changes. +/// [onMoveGroup] will get called when moving the group from one position to +/// another. +/// +/// [onMoveGroupItem] will get called when moving the group's items. +/// +/// [onMoveGroupItemToGroup] will get called when moving the group's item from +/// one group to another group. +class AppFlowyBoardController extends ChangeNotifier with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlexDataSource { - final List _columnDatas = []; - final OnMoveColumn? onMoveColumn; - final OnMoveColumnItem? onMoveColumnItem; - final OnMoveColumnItemToColumn? onMoveColumnItemToColumn; + final List _groupDatas = []; - List get columnDatas => _columnDatas; + /// [onMoveGroup] will get called when moving the group from one position to + /// another. + final OnMoveGroup? onMoveGroup; - List get columnIds => - _columnDatas.map((columnData) => columnData.id).toList(); + /// [onMoveGroupItem] will get called when moving the group's items. + final OnMoveGroupItem? onMoveGroupItem; - final LinkedHashMap _columnControllers = + /// [onMoveGroupItemToGroup] will get called when moving the group's item from + /// one group to another group. + final OnMoveGroupItemToGroup? onMoveGroupItemToGroup; + + /// Returns the unmodifiable list of [AppFlowyGroupData] + UnmodifiableListView get groupDatas => + UnmodifiableListView(_groupDatas); + + /// Returns list of group id + List get groupIds => + _groupDatas.map((groupData) => groupData.id).toList(); + + final LinkedHashMap _groupControllers = LinkedHashMap(); - AFBoardDataController({ - this.onMoveColumn, - this.onMoveColumnItem, - this.onMoveColumnItemToColumn, + AppFlowyBoardController({ + this.onMoveGroup, + this.onMoveGroupItem, + this.onMoveGroupItemToGroup, }); - void addColumn(AFBoardColumnData columnData, {bool notify = true}) { - if (_columnControllers[columnData.id] != null) return; + /// Adds a new group to the end of the current group list. + /// + /// If you don't want to notify the listener after adding a new group, the + /// [notify] should set to false. Default value is true. + void addGroup(AppFlowyGroupData groupData, {bool notify = true}) { + if (_groupControllers[groupData.id] != null) return; - final controller = AFBoardColumnDataController(columnData: columnData); - _columnDatas.add(columnData); - _columnControllers[columnData.id] = controller; + final controller = AppFlowyGroupController(groupData: groupData); + _groupDatas.add(groupData); + _groupControllers[groupData.id] = controller; if (notify) notifyListeners(); } - void addColumns(List columns, {bool notify = true}) { - for (final column in columns) { - addColumn(column, notify: false); + /// Adds a list of groups to the end of the current group list. + /// + /// If you don't want to notify the listener after adding the groups, the + /// [notify] should set to false. Default value is true. + void addGroups(List groups, {bool notify = true}) { + for (final column in groups) { + addGroup(column, notify: false); } - if (columns.isNotEmpty && notify) notifyListeners(); + if (groups.isNotEmpty && notify) notifyListeners(); } - void removeColumn(String columnId, {bool notify = true}) { - final index = _columnDatas.indexWhere((column) => column.id == columnId); + /// Removes the group with id [groupId] + /// + /// If you don't want to notify the listener after removing the group, the + /// [notify] should set to false. Default value is true. + void removeGroup(String groupId, {bool notify = true}) { + final index = _groupDatas.indexWhere((group) => group.id == groupId); if (index == -1) { Log.warn( - 'Try to remove Column:[$columnId] failed. Column:[$columnId] not exist'); + 'Try to remove Group:[$groupId] failed. Group:[$groupId] not exist'); } if (index != -1) { - _columnDatas.removeAt(index); - _columnControllers.remove(columnId); + _groupDatas.removeAt(index); + _groupControllers.remove(groupId); if (notify) notifyListeners(); } } - void removeColumns(List columnIds, {bool notify = true}) { - for (final columnId in columnIds) { - removeColumn(columnId, notify: false); + /// Removes a list of groups + /// + /// If you don't want to notify the listener after removing the groups, the + /// [notify] should set to false. Default value is true. + void removeGroups(List groupIds, {bool notify = true}) { + for (final groupId in groupIds) { + removeGroup(groupId, notify: false); } - if (columnIds.isNotEmpty && notify) notifyListeners(); + if (groupIds.isNotEmpty && notify) notifyListeners(); } + /// Remove all the groups controller. + /// + /// This method should get called when you want to remove all the current + /// groups or get ready to reinitialize the [AppFlowyBoard]. void clear() { - _columnDatas.clear(); - _columnControllers.clear(); + _groupDatas.clear(); + _groupControllers.clear(); notifyListeners(); } - AFBoardColumnDataController? getColumnController(String columnId) { - final columnController = _columnControllers[columnId]; - if (columnController == null) { - Log.warn('Column:[$columnId] \'s controller is not exist'); + /// Returns the [AppFlowyGroupController] with id [groupId]. + AppFlowyGroupController? getGroupController(String groupId) { + final groupController = _groupControllers[groupId]; + if (groupController == null) { + Log.warn('Group:[$groupId] \'s controller is not exist'); } - return columnController; + return groupController; } - void moveColumn(int fromIndex, int toIndex, {bool notify = true}) { - final toColumnData = _columnDatas[toIndex]; - final fromColumnData = _columnDatas.removeAt(fromIndex); + /// Moves the group controller from [fromIndex] to [toIndex] and notify the + /// listeners. + /// + /// If you don't want to notify the listener after moving the group, the + /// [notify] should set to false. Default value is true. + void moveGroup(int fromIndex, int toIndex, {bool notify = true}) { + final toGroupData = _groupDatas[toIndex]; + final fromGroupData = _groupDatas.removeAt(fromIndex); - _columnDatas.insert(toIndex, fromColumnData); - onMoveColumn?.call(fromColumnData.id, fromIndex, toColumnData.id, toIndex); + _groupDatas.insert(toIndex, fromGroupData); + onMoveGroup?.call(fromGroupData.id, fromIndex, toGroupData.id, toIndex); if (notify) notifyListeners(); } - void moveColumnItem(String columnId, int fromIndex, int toIndex) { - if (getColumnController(columnId)?.move(fromIndex, toIndex) ?? false) { - onMoveColumnItem?.call(columnId, fromIndex, toIndex); + /// Moves the group's item from [fromIndex] to [toIndex] + /// If the group with id [groupId] is not exist, this method will do nothing. + void moveGroupItem(String groupId, int fromIndex, int toIndex) { + if (getGroupController(groupId)?.move(fromIndex, toIndex) ?? false) { + onMoveGroupItem?.call(groupId, fromIndex, toIndex); } } - void addColumnItem(String columnId, AFColumnItem item) { - getColumnController(columnId)?.add(item); + /// Adds the [AppFlowyGroupItem] to the end of the group + /// + /// If the group with id [groupId] is not exist, this method will do nothing. + void addGroupItem(String groupId, AppFlowyGroupItem item) { + getGroupController(groupId)?.add(item); } - void insertColumnItem(String columnId, int index, AFColumnItem item) { - getColumnController(columnId)?.insert(index, item); + /// Inserts the [AppFlowyGroupItem] at [index] in the group + /// + /// It will do nothing if the group with id [groupId] is not exist + void insertGroupItem(String groupId, int index, AppFlowyGroupItem item) { + getGroupController(groupId)?.insert(index, item); } - void removeColumnItem(String columnId, String itemId) { - getColumnController(columnId)?.removeWhere((item) => item.id == itemId); + /// Removes the item with id [itemId] from the group + /// + /// It will do nothing if the group with id [groupId] is not exist + void removeGroupItem(String groupId, String itemId) { + getGroupController(groupId)?.removeWhere((item) => item.id == itemId); } - void updateColumnItem(String columnId, AFColumnItem item) { - getColumnController(columnId)?.replaceOrInsertItem(item); + /// Replaces or inserts the [AppFlowyGroupItem] to the end of the group. + /// + /// If the group with id [groupId] is not exist, this method will do nothing. + void updateGroupItem(String groupId, AppFlowyGroupItem item) { + getGroupController(groupId)?.replaceOrInsertItem(item); } + /// Moves the item at [fromGroupIndex] in group with id [fromGroupId] to + /// group with id [toGroupId] at [toGroupIndex] @override @protected - void swapColumnItem( - String fromColumnId, - int fromColumnIndex, - String toColumnId, - int toColumnIndex, + void moveGroupItemToAnotherGroup( + String fromGroupId, + int fromGroupIndex, + String toGroupId, + int toGroupIndex, ) { - final fromColumnController = getColumnController(fromColumnId)!; - final toColumnController = getColumnController(toColumnId)!; - final item = fromColumnController.removeAt(fromColumnIndex); - if (toColumnController.items.length > toColumnIndex) { - assert(toColumnController.items[toColumnIndex] is PhantomColumnItem); + final fromGroupController = getGroupController(fromGroupId)!; + final toGroupController = getGroupController(toGroupId)!; + final fromGroupItem = fromGroupController.removeAt(fromGroupIndex); + if (toGroupController.items.length > toGroupIndex) { + assert(toGroupController.items[toGroupIndex] is PhantomGroupItem); } - toColumnController.replace(toColumnIndex, item); - onMoveColumnItemToColumn?.call( - fromColumnId, - fromColumnIndex, - toColumnId, - toColumnIndex, + toGroupController.replace(toGroupIndex, fromGroupItem); + onMoveGroupItemToGroup?.call( + fromGroupId, + fromGroupIndex, + toGroupId, + toGroupIndex, ); } @override List get props { - return [_columnDatas]; + return [_groupDatas]; } @override - AFBoardColumnDataController? controller(String columnId) { - return _columnControllers[columnId]; + AppFlowyGroupController? controller(String groupId) { + return _groupControllers[groupId]; } @override - String get identifier => '$AFBoardDataController'; + String get identifier => '$AppFlowyBoardController'; @override UnmodifiableListView get items => - UnmodifiableListView(_columnDatas); + UnmodifiableListView(_groupDatas); @override @protected - bool removePhantom(String columnId) { - final columnController = getColumnController(columnId); - if (columnController == null) { - Log.warn('Can not find the column controller with columnId: $columnId'); + bool removePhantom(String groupId) { + final groupController = getGroupController(groupId); + if (groupController == null) { + Log.warn('Can not find the group controller with groupId: $groupId'); return false; } - final index = columnController.items.indexWhere((item) => item.isPhantom); + final index = groupController.items.indexWhere((item) => item.isPhantom); final isExist = index != -1; if (isExist) { - columnController.removeAt(index); + groupController.removeAt(index); Log.debug( - '[$AFBoardDataController] Column:[$columnId] remove phantom, current count: ${columnController.items.length}'); + '[$AppFlowyBoardController] Group:[$groupId] remove phantom, current count: ${groupController.items.length}'); } return isExist; } @override @protected - void updatePhantom(String columnId, int newIndex) { - final columnDataController = getColumnController(columnId)!; - final index = - columnDataController.items.indexWhere((item) => item.isPhantom); + void updatePhantom(String groupId, int newIndex) { + final groupController = getGroupController(groupId)!; + final index = groupController.items.indexWhere((item) => item.isPhantom); if (index != -1) { if (index != newIndex) { Log.trace( - '[$BoardPhantomController] update $columnId:$index to $columnId:$newIndex'); - final item = columnDataController.removeAt(index, notify: false); - columnDataController.insert(newIndex, item, notify: false); + '[$BoardPhantomController] update $groupId:$index to $groupId:$newIndex'); + final item = groupController.removeAt(index, notify: false); + groupController.insert(newIndex, item, notify: false); } } } @override @protected - void insertPhantom(String columnId, int index, PhantomColumnItem item) { - getColumnController(columnId)!.insert(index, item); + void insertPhantom(String groupId, int index, PhantomGroupItem item) { + getGroupController(groupId)!.insert(index, item); } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group.dart similarity index 57% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group.dart index ce998b365e..a4b130f6ec 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group.dart @@ -7,51 +7,51 @@ import '../../utils/log.dart'; import '../reorder_phantom/phantom_controller.dart'; import '../reorder_flex/reorder_flex.dart'; import '../reorder_flex/drag_target_interceptor.dart'; -import 'board_column_data.dart'; +import 'group_data.dart'; -typedef OnColumnDragStarted = void Function(int index); +typedef OnGroupDragStarted = void Function(int index); -typedef OnColumnDragEnded = void Function(String listId); +typedef OnGroupDragEnded = void Function(String groupId); -typedef OnColumnReorder = void Function( - String listId, +typedef OnGroupReorder = void Function( + String groupId, int fromIndex, int toIndex, ); -typedef OnColumnDeleted = void Function(String listId, int deletedIndex); +typedef OnGroupDeleted = void Function(String groupId, int deletedIndex); -typedef OnColumnInserted = void Function(String listId, int insertedIndex); +typedef OnGroupInserted = void Function(String groupId, int insertedIndex); -typedef AFBoardColumnCardBuilder = Widget Function( +typedef AppFlowyBoardCardBuilder = Widget Function( BuildContext context, - AFBoardColumnData columnData, - AFColumnItem item, + AppFlowyGroupData groupData, + AppFlowyGroupItem item, ); -typedef AFBoardColumnHeaderBuilder = Widget? Function( +typedef AppFlowyBoardHeaderBuilder = Widget? Function( BuildContext context, - AFBoardColumnData columnData, + AppFlowyGroupData groupData, ); -typedef AFBoardColumnFooterBuilder = Widget Function( +typedef AppFlowyBoardFooterBuilder = Widget Function( BuildContext context, - AFBoardColumnData columnData, + AppFlowyGroupData groupData, ); -abstract class AFBoardColumnDataDataSource extends ReoderFlexDataSource { - AFBoardColumnData get columnData; +abstract class AppFlowyGroupDataDataSource extends ReoderFlexDataSource { + AppFlowyGroupData get groupData; - List get acceptedColumnIds; + List get acceptedGroupIds; @override - String get identifier => columnData.id; + String get identifier => groupData.id; @override - UnmodifiableListView get items => columnData.items; + UnmodifiableListView get items => groupData.items; void debugPrint() { - String msg = '[$AFBoardColumnDataDataSource] $columnData data: '; + String msg = '[$AppFlowyGroupDataDataSource] $groupData data: '; for (var element in items) { msg = '$msg$element,'; } @@ -60,25 +60,25 @@ abstract class AFBoardColumnDataDataSource extends ReoderFlexDataSource { } } -/// [AFBoardColumnWidget] represents the column of the Board. +/// A [AppFlowyBoardGroup] represents the group UI of the Board. /// -class AFBoardColumnWidget extends StatefulWidget { - final AFBoardColumnDataDataSource dataSource; +class AppFlowyBoardGroup extends StatefulWidget { + final AppFlowyGroupDataDataSource dataSource; final ScrollController? scrollController; final ReorderFlexConfig config; - final OnColumnDragStarted? onDragStarted; - final OnColumnReorder onReorder; - final OnColumnDragEnded? onDragEnded; + final OnGroupDragStarted? onDragStarted; + final OnGroupReorder onReorder; + final OnGroupDragEnded? onDragEnded; final BoardPhantomController phantomController; - String get columnId => dataSource.columnData.id; + String get groupId => dataSource.groupData.id; - final AFBoardColumnCardBuilder cardBuilder; + final AppFlowyBoardCardBuilder cardBuilder; - final AFBoardColumnHeaderBuilder? headerBuilder; + final AppFlowyBoardHeaderBuilder? headerBuilder; - final AFBoardColumnFooterBuilder? footBuilder; + final AppFlowyBoardFooterBuilder? footerBuilder; final EdgeInsets margin; @@ -92,12 +92,13 @@ class AFBoardColumnWidget extends StatefulWidget { final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage; - final GlobalObjectKey globalKey; + final GlobalObjectKey reorderFlexKey; - AFBoardColumnWidget({ + const AppFlowyBoardGroup({ Key? key, + required this.reorderFlexKey, this.headerBuilder, - this.footBuilder, + this.footerBuilder, required this.cardBuilder, required this.onReorder, required this.dataSource, @@ -111,59 +112,58 @@ class AFBoardColumnWidget extends StatefulWidget { this.itemMargin = EdgeInsets.zero, this.cornerRadius = 0.0, this.backgroundColor = Colors.transparent, - }) : globalKey = GlobalObjectKey(dataSource.columnData.id), - config = const ReorderFlexConfig(setStateWhenEndDrag: false), + }) : config = const ReorderFlexConfig(setStateWhenEndDrag: false), super(key: key); @override - State createState() => _AFBoardColumnWidgetState(); + State createState() => _AppFlowyBoardGroupState(); } -class _AFBoardColumnWidgetState extends State { +class _AppFlowyBoardGroupState extends State { final GlobalKey _columnOverlayKey = - GlobalKey(debugLabel: '$AFBoardColumnWidget overlay key'); + GlobalKey(debugLabel: '$AppFlowyBoardGroup overlay key'); late BoardOverlayEntry _overlayEntry; @override void initState() { _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { - final children = widget.dataSource.columnData.items + final children = widget.dataSource.groupData.items .map((item) => _buildWidget(context, item)) .toList(); final header = - widget.headerBuilder?.call(context, widget.dataSource.columnData); + widget.headerBuilder?.call(context, widget.dataSource.groupData); final footer = - widget.footBuilder?.call(context, widget.dataSource.columnData); + widget.footerBuilder?.call(context, widget.dataSource.groupData); final interceptor = CrossReorderFlexDragTargetInterceptor( - reorderFlexId: widget.columnId, + reorderFlexId: widget.groupId, delegate: widget.phantomController, - acceptedReorderFlexIds: widget.dataSource.acceptedColumnIds, + acceptedReorderFlexIds: widget.dataSource.acceptedGroupIds, draggableTargetBuilder: PhantomDraggableBuilder(), ); Widget reorderFlex = ReorderFlex( - key: widget.globalKey, + key: widget.reorderFlexKey, dragStateStorage: widget.dragStateStorage, dragTargetIndexKeyStorage: widget.dragTargetIndexKeyStorage, scrollController: widget.scrollController, config: widget.config, onDragStarted: (index) { - widget.phantomController.columnStartDragging(widget.columnId); + widget.phantomController.groupStartDragging(widget.groupId); widget.onDragStarted?.call(index); }, onReorder: ((fromIndex, toIndex) { - if (widget.phantomController.isFromColumn(widget.columnId)) { - widget.onReorder(widget.columnId, fromIndex, toIndex); + if (widget.phantomController.isFromGroup(widget.groupId)) { + widget.onReorder(widget.groupId, fromIndex, toIndex); widget.phantomController.transformIndex(fromIndex, toIndex); } }), onDragEnded: () { - widget.phantomController.columnEndDragging(widget.columnId); - widget.onDragEnded?.call(widget.columnId); + widget.phantomController.groupEndDragging(widget.groupId); + widget.onDragEnded?.call(widget.groupId); widget.dataSource.debugPrint(); }, dataSource: widget.dataSource, @@ -171,6 +171,10 @@ class _AFBoardColumnWidgetState extends State { children: children, ); + reorderFlex = Expanded( + child: Padding(padding: widget.itemMargin, child: reorderFlex), + ); + return Container( margin: widget.margin, clipBehavior: Clip.hardEdge, @@ -181,9 +185,7 @@ class _AFBoardColumnWidgetState extends State { child: Column( children: [ if (header != null) header, - Expanded( - child: Padding(padding: widget.itemMargin, child: reorderFlex), - ), + reorderFlex, if (footer != null) footer, ], ), @@ -202,15 +204,15 @@ class _AFBoardColumnWidgetState extends State { ); } - Widget _buildWidget(BuildContext context, AFColumnItem item) { - if (item is PhantomColumnItem) { + Widget _buildWidget(BuildContext context, AppFlowyGroupItem item) { + if (item is PhantomGroupItem) { return PassthroughPhantomWidget( key: UniqueKey(), opacity: widget.config.draggingWidgetOpacity, passthroughPhantomContext: item.phantomContext, ); } else { - return widget.cardBuilder(context, widget.dataSource.columnData, item); + return widget.cardBuilder(context, widget.dataSource.groupData, item); } } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group_data.dart new file mode 100644 index 0000000000..7bc3bf8657 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_group/group_data.dart @@ -0,0 +1,194 @@ +import 'dart:collection'; + +import 'package:appflowy_board/src/utils/log.dart'; +import 'package:appflowy_board/src/widgets/reorder_flex/reorder_flex.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; + +/// A item represents the generic data model of each group card. +/// +/// Each item displayed in the group required to implement this class. +abstract class AppFlowyGroupItem extends ReoderFlexItem { + bool get isPhantom => false; + + @override + String toString() => id; +} + +/// [AppFlowyGroupController] is used to handle the [AppFlowyGroupData]. +/// +/// * Remove an item by calling [removeAt] method. +/// * Move item to another position by calling [move] method. +/// * Insert item to index by calling [insert] method +/// * Replace item at index by calling [replace] method. +/// +/// All there operations will notify listeners by default. +/// +class AppFlowyGroupController extends ChangeNotifier with EquatableMixin { + final AppFlowyGroupData groupData; + + AppFlowyGroupController({ + required this.groupData, + }); + + @override + List get props => groupData.props; + + /// Returns the readonly List + UnmodifiableListView get items => + UnmodifiableListView(groupData.items); + + void updateGroupName(String newName) { + if (groupData.headerData.groupName != newName) { + groupData.headerData.groupName = newName; + notifyListeners(); + } + } + + /// Remove the item at [index]. + /// * [index] the index of the item you want to remove + /// * [notify] the default value of [notify] is true, it will notify the + /// listener. Set to false if you do not want to notify the listeners. + /// + AppFlowyGroupItem removeAt(int index, {bool notify = true}) { + assert(index >= 0); + + Log.debug('[$AppFlowyGroupController] $groupData remove item at $index'); + final item = groupData._items.removeAt(index); + if (notify) { + notifyListeners(); + } + return item; + } + + void removeWhere(bool Function(AppFlowyGroupItem) condition) { + final index = items.indexWhere(condition); + if (index != -1) { + removeAt(index); + } + } + + /// Move the item from [fromIndex] to [toIndex]. It will do nothing if the + /// [fromIndex] equal to the [toIndex]. + bool move(int fromIndex, int toIndex) { + assert(fromIndex >= 0); + assert(toIndex >= 0); + + if (fromIndex == toIndex) { + return false; + } + Log.debug( + '[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex'); + final item = groupData._items.removeAt(fromIndex); + groupData._items.insert(toIndex, item); + notifyListeners(); + return true; + } + + /// Insert an item to [index] and notify the listen if the value of [notify] + /// is true. + /// + /// The default value of [notify] is true. + bool insert(int index, AppFlowyGroupItem item, {bool notify = true}) { + assert(index >= 0); + Log.debug('[$AppFlowyGroupController] $groupData insert $item at $index'); + + if (_containsItem(item)) { + return false; + } else { + if (groupData._items.length > index) { + groupData._items.insert(index, item); + } else { + groupData._items.add(item); + } + + if (notify) notifyListeners(); + return true; + } + } + + bool add(AppFlowyGroupItem item, {bool notify = true}) { + if (_containsItem(item)) { + return false; + } else { + groupData._items.add(item); + if (notify) notifyListeners(); + return true; + } + } + + /// Replace the item at index with the [newItem]. + void replace(int index, AppFlowyGroupItem newItem) { + if (groupData._items.isEmpty) { + groupData._items.add(newItem); + Log.debug('[$AppFlowyGroupController] $groupData add $newItem'); + } else { + if (index >= groupData._items.length) { + return; + } + + final removedItem = groupData._items.removeAt(index); + groupData._items.insert(index, newItem); + Log.debug( + '[$AppFlowyGroupController] $groupData replace $removedItem with $newItem at $index'); + } + + notifyListeners(); + } + + void replaceOrInsertItem(AppFlowyGroupItem newItem) { + final index = groupData._items.indexWhere((item) => item.id == newItem.id); + if (index != -1) { + groupData._items.removeAt(index); + groupData._items.insert(index, newItem); + notifyListeners(); + } else { + groupData._items.add(newItem); + notifyListeners(); + } + } + + bool _containsItem(AppFlowyGroupItem item) { + return groupData._items.indexWhere((element) => element.id == item.id) != + -1; + } +} + +/// [AppFlowyGroupData] represents the data of each group of the Board. +class AppFlowyGroupData extends ReoderFlexItem with EquatableMixin { + @override + final String id; + AppFlowyGroupHeaderData headerData; + final List _items; + final CustomData? customData; + + AppFlowyGroupData({ + this.customData, + required this.id, + required String name, + List items = const [], + }) : _items = items, + headerData = AppFlowyGroupHeaderData( + groupId: id, + groupName: name, + ); + + /// Returns the readonly List + UnmodifiableListView get items => + UnmodifiableListView([..._items]); + + @override + List get props => [id, ..._items]; + + @override + String toString() { + return 'Group:[$id]'; + } +} + +class AppFlowyGroupHeaderData { + String groupId; + String groupName; + + AppFlowyGroupHeaderData({required this.groupId, required this.groupName}); +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart index 20f66c0921..bce94502bd 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_interceptor.dart @@ -55,7 +55,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor { final String reorderFlexId; final List acceptedReorderFlexId; final OverlapDragTargetDelegate delegate; - final BoardColumnsState columnsState; + final AppFlowyBoardState columnsState; Timer? _delayOperation; OverlappingDragTargetInterceptor({ @@ -81,7 +81,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor { delegate.cancel(); } else { // Ignore the event if the dragTarget overlaps with the other column's dragTargets. - final columnKeys = columnsState.columnDragDragTargets[dragTargetId]; + final columnKeys = columnsState.groupDragDragTargets[dragTargetId]; if (columnKeys != null) { final keys = columnKeys.values.toList(); if (dragTargetData.isOverlapWithWidgets(keys)) { @@ -102,7 +102,7 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor { delegate.moveTo(dragTargetId, dragTargetData, index); columnsState - .getReorderFlexState(columnId: dragTargetId) + .getReorderFlexState(groupId: dragTargetId) ?.resetDragTargetIndex(index); } }); @@ -165,13 +165,13 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor { @override void onAccept(FlexDragTargetData dragTargetData) { Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column:[$reorderFlexId] on onAccept'); + '[$CrossReorderFlexDragTargetInterceptor] Group:[$reorderFlexId] on onAccept'); } @override void onLeave(FlexDragTargetData dragTargetData) { Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column:[$reorderFlexId] on leave'); + '[$CrossReorderFlexDragTargetInterceptor] Group:[$reorderFlexId] on leave'); } @override diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart index bff76cfe52..dda8aa7011 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart @@ -49,8 +49,8 @@ class ReorderFlexConfig { /// Determines if setSatte method needs to be called when the drag is complete. /// Default value is [true]. /// - /// If the [ReorderFlex] will be rebuild after the [ReorderFlex]'s children - /// were changed, then the [setStateWhenEndDrag] should set to [false]. + /// If the [ReorderFlex] needs to be rebuild after the [ReorderFlex] end dragging, + /// the [setStateWhenEndDrag] should set to [true]. final bool setStateWhenEndDrag; final bool useMoveAnimation; @@ -252,7 +252,7 @@ class ReorderFlexState extends State } Log.trace( - 'Rebuild: Column:[${dragState.reorderFlexId}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex'); + 'Rebuild: Group:[${dragState.reorderFlexId}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex'); final currentIndex = dragState.currentIndex; final dragPhantomIndex = dragState.phantomIndex; @@ -368,7 +368,7 @@ class ReorderFlexState extends State ), onDragStarted: (draggingWidget, draggingIndex, size) { Log.debug( - "[DragTarget] Column:[${widget.dataSource.identifier}] start dragging item at $draggingIndex"); + "[DragTarget] Group:[${widget.dataSource.identifier}] start dragging item at $draggingIndex"); _startDragging(draggingWidget, draggingIndex, size); widget.onDragStarted?.call(draggingIndex); widget.dragStateStorage?.remove(widget.reorderFlexId); @@ -379,7 +379,7 @@ class ReorderFlexState extends State onDragEnded: (dragTargetData) { if (!mounted) return; Log.debug( - "[DragTarget]: Column:[${widget.dataSource.identifier}] end dragging"); + "[DragTarget]: Group:[${widget.dataSource.identifier}] end dragging"); _notifier.updateDragTargetIndex(-1); onDragEnded() { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart index 0f63266e51..29c469c4a1 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart @@ -8,30 +8,30 @@ import '../reorder_flex/drag_target_interceptor.dart'; import 'phantom_state.dart'; abstract class BoardPhantomControllerDelegate { - AFBoardColumnDataController? controller(String columnId); + AppFlowyGroupController? controller(String groupId); - bool removePhantom(String columnId); + bool removePhantom(String groupId); - /// Insert the phantom into column + /// Insert the phantom into the group /// - /// * [toColumnId] id of the column - /// * [phantomIndex] the phantom occupies index + /// * [groupId] id of the group + /// * [index] the phantom occupies index void insertPhantom( - String columnId, + String groupId, int index, - PhantomColumnItem item, + PhantomGroupItem item, ); - /// Update the column's phantom index if it exists. - /// [toColumnId] the id of the column - /// [dragTargetIndex] the index of the dragTarget - void updatePhantom(String columnId, int newIndex); + /// Update the group's phantom index if it exists. + /// [toGroupId] the id of the group + /// [newIndex] the index of the dragTarget + void updatePhantom(String groupId, int newIndex); - void swapColumnItem( - String fromColumnId, - int fromColumnIndex, - String toColumnId, - int toColumnIndex, + void moveGroupItemToAnotherGroup( + String fromGroupId, + int fromGroupIndex, + String toGroupId, + int toGroupIndex, ); } @@ -39,16 +39,16 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorderFlexDragTargetDelegate { PhantomRecord? phantomRecord; final BoardPhantomControllerDelegate delegate; - final BoardColumnsState columnsState; - final phantomState = ColumnPhantomState(); + final AppFlowyBoardState groupsState; + final phantomState = GroupPhantomState(); BoardPhantomController({ required this.delegate, - required this.columnsState, + required this.groupsState, }); - bool isFromColumn(String columnId) { + bool isFromGroup(String groupId) { if (phantomRecord != null) { - return phantomRecord!.fromColumnId == columnId; + return phantomRecord!.fromGroupId == groupId; } else { return true; } @@ -58,32 +58,32 @@ class BoardPhantomController extends OverlapDragTargetDelegate if (phantomRecord == null) { return; } - assert(phantomRecord!.fromColumnIndex == fromIndex); - phantomRecord?.updateFromColumnIndex(toIndex); + assert(phantomRecord!.fromGroupIndex == fromIndex); + phantomRecord?.updateFromGroupIndex(toIndex); } - void columnStartDragging(String columnId) { - phantomState.setColumnIsDragging(columnId, true); + void groupStartDragging(String groupId) { + phantomState.setGroupIsDragging(groupId, true); } - /// Remove the phantom in the column when the column is end dragging. - void columnEndDragging(String columnId) { - phantomState.setColumnIsDragging(columnId, false); + /// Remove the phantom in the group when the group is end dragging. + void groupEndDragging(String groupId) { + phantomState.setGroupIsDragging(groupId, false); if (phantomRecord == null) return; - final fromColumnId = phantomRecord!.fromColumnId; - final toColumnId = phantomRecord!.toColumnId; - if (fromColumnId == columnId) { - phantomState.notifyDidRemovePhantom(toColumnId); + final fromGroupId = phantomRecord!.fromGroupId; + final toGroupId = phantomRecord!.toGroupId; + if (fromGroupId == groupId) { + phantomState.notifyDidRemovePhantom(toGroupId); } - if (phantomRecord!.toColumnId == columnId) { - delegate.swapColumnItem( - fromColumnId, - phantomRecord!.fromColumnIndex, - toColumnId, - phantomRecord!.toColumnIndex, + if (phantomRecord!.toGroupId == groupId) { + delegate.moveGroupItemToAnotherGroup( + fromGroupId, + phantomRecord!.fromGroupIndex, + toGroupId, + phantomRecord!.toGroupIndex, ); // Log.debug( @@ -92,16 +92,16 @@ class BoardPhantomController extends OverlapDragTargetDelegate } } - /// Remove the phantom in the column if it contains phantom - void _removePhantom(String columnId) { - if (delegate.removePhantom(columnId)) { - phantomState.notifyDidRemovePhantom(columnId); - phantomState.removeColumnListener(columnId); + /// Remove the phantom in the group if it contains phantom + void _removePhantom(String groupId) { + if (delegate.removePhantom(groupId)) { + phantomState.notifyDidRemovePhantom(groupId); + phantomState.removeGroupListener(groupId); } } void _insertPhantom( - String toColumnId, + String toGroupId, FlexDragTargetData dragTargetData, int phantomIndex, ) { @@ -109,38 +109,38 @@ class BoardPhantomController extends OverlapDragTargetDelegate index: phantomIndex, dragTargetData: dragTargetData, ); - phantomState.addColumnListener(toColumnId, phantomContext); + phantomState.addGroupListener(toGroupId, phantomContext); delegate.insertPhantom( - toColumnId, + toGroupId, phantomIndex, - PhantomColumnItem(phantomContext), + PhantomGroupItem(phantomContext), ); - phantomState.notifyDidInsertPhantom(toColumnId, phantomIndex); + phantomState.notifyDidInsertPhantom(toGroupId, phantomIndex); } /// Reset or initial the [PhantomRecord] /// /// - /// * [columnId] the id of the column + /// * [groupId] the id of the group /// * [dragTargetData] /// * [dragTargetIndex] /// void _resetPhantomRecord( - String columnId, + String groupId, FlexDragTargetData dragTargetData, int dragTargetIndex, ) { // Log.debug( - // '[$BoardPhantomController] move Column:[${dragTargetData.reorderFlexId}]:${dragTargetData.draggingIndex} ' - // 'to Column:[$columnId]:$dragTargetIndex'); + // '[$BoardPhantomController] move Group:[${dragTargetData.reorderFlexId}]:${dragTargetData.draggingIndex} ' + // 'to Group:[$groupId]:$dragTargetIndex'); phantomRecord = PhantomRecord( - toColumnId: columnId, - toColumnIndex: dragTargetIndex, - fromColumnId: dragTargetData.reorderFlexId, - fromColumnIndex: dragTargetData.draggingIndex, + toGroupId: groupId, + toGroupIndex: dragTargetIndex, + fromGroupId: dragTargetData.reorderFlexId, + fromGroupIndex: dragTargetData.draggingIndex, ); Log.debug('[$BoardPhantomController] will move: $phantomRecord'); } @@ -158,10 +158,10 @@ class BoardPhantomController extends OverlapDragTargetDelegate return true; } - final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId; + final isNewDragTarget = phantomRecord!.toGroupId != reorderFlexId; if (isNewDragTarget) { - /// Remove the phantom when the dragTarget is moved from one column to another column. - _removePhantom(phantomRecord!.toColumnId); + /// Remove the phantom when the dragTarget is moved from one group to another group. + _removePhantom(phantomRecord!.toGroupId); _resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex); } @@ -177,9 +177,9 @@ class BoardPhantomController extends OverlapDragTargetDelegate phantomRecord?.updateInsertedIndex(dragTargetIndex); assert(phantomRecord != null); - if (phantomRecord!.toColumnId == reorderFlexId) { + if (phantomRecord!.toGroupId == reorderFlexId) { /// Update the existing phantom index - delegate.updatePhantom(phantomRecord!.toColumnId, dragTargetIndex); + delegate.updatePhantom(phantomRecord!.toGroupId, dragTargetIndex); } } @@ -189,8 +189,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate return; } - /// Remove the phantom when the dragTarge is go back to the original column. - _removePhantom(phantomRecord!.toColumnId); + /// Remove the phantom when the dragTarge is go back to the original group. + _removePhantom(phantomRecord!.toGroupId); phantomRecord = null; } @@ -215,63 +215,63 @@ class BoardPhantomController extends OverlapDragTargetDelegate final controller = delegate.controller(dragTargetId); if (controller != null) { - return controller.columnData.items.length; + return controller.groupData.items.length; } else { return 0; } } } -/// Use [PhantomRecord] to record where to remove the column item and where to -/// insert the column item. +/// Use [PhantomRecord] to record where to remove the group item and where to +/// insert the group item. /// -/// [fromColumnId] the column that phantom comes from -/// [fromColumnIndex] the index of the phantom from the original column -/// [toColumnId] the column that the phantom moves into -/// [toColumnIndex] the index of the phantom moves into the column +/// [fromGroupId] the group that phantom comes from +/// [fromGroupIndex] the index of the phantom from the original group +/// [toGroupId] the group that the phantom moves into +/// [toGroupIndex] the index of the phantom moves into the group /// class PhantomRecord { - final String fromColumnId; - int fromColumnIndex; + final String fromGroupId; + int fromGroupIndex; - final String toColumnId; - int toColumnIndex; + final String toGroupId; + int toGroupIndex; PhantomRecord({ - required this.toColumnId, - required this.toColumnIndex, - required this.fromColumnId, - required this.fromColumnIndex, + required this.toGroupId, + required this.toGroupIndex, + required this.fromGroupId, + required this.fromGroupIndex, }); - void updateFromColumnIndex(int index) { - if (fromColumnIndex == index) { + void updateFromGroupIndex(int index) { + if (fromGroupIndex == index) { return; } - fromColumnIndex = index; + fromGroupIndex = index; } void updateInsertedIndex(int index) { - if (toColumnIndex == index) { + if (toGroupIndex == index) { return; } Log.debug( - '[$PhantomRecord] Column:[$toColumnId] update position $toColumnIndex -> $index'); - toColumnIndex = index; + '[$PhantomRecord] Group:[$toGroupId] update position $toGroupIndex -> $index'); + toGroupIndex = index; } @override String toString() { - return 'Column:[$fromColumnId]:$fromColumnIndex to Column:[$toColumnId]:$toColumnIndex'; + return 'Group:[$fromGroupId]:$fromGroupIndex to Group:[$toGroupId]:$toGroupIndex'; } } -class PhantomColumnItem extends AFColumnItem { +class PhantomGroupItem extends AppFlowyGroupItem { final PassthroughPhantomContext phantomContext; - PhantomColumnItem(PassthroughPhantomContext insertedPhantom) + PhantomGroupItem(PassthroughPhantomContext insertedPhantom) : phantomContext = insertedPhantom; @override @@ -305,7 +305,8 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger Widget? get draggingWidget => dragTargetData.draggingWidget; - AFColumnItem get itemData => dragTargetData.reorderFlexItem as AFColumnItem; + AppFlowyGroupItem get itemData => + dragTargetData.reorderFlexItem as AppFlowyGroupItem; @override void Function(int?)? onInserted; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart index c550ee3bca..0aeeb86a7a 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart @@ -1,48 +1,48 @@ import 'phantom_controller.dart'; import 'package:flutter/material.dart'; -class ColumnPhantomState { - final _states = {}; +class GroupPhantomState { + final _states = {}; - void setColumnIsDragging(String columnId, bool isDragging) { - _stateWithId(columnId).isDragging = isDragging; + void setGroupIsDragging(String groupId, bool isDragging) { + _stateWithId(groupId).isDragging = isDragging; } - bool isDragging(String columnId) { - return _stateWithId(columnId).isDragging; + bool isDragging(String groupId) { + return _stateWithId(groupId).isDragging; } - void addColumnListener(String columnId, PassthroughPhantomListener listener) { - _stateWithId(columnId).notifier.addListener( + void addGroupListener(String groupId, PassthroughPhantomListener listener) { + _stateWithId(groupId).notifier.addListener( onInserted: (index) => listener.onInserted?.call(index), onDeleted: () => listener.onDragEnded?.call(), ); } - void removeColumnListener(String columnId) { - _stateWithId(columnId).notifier.dispose(); - _states.remove(columnId); + void removeGroupListener(String groupId) { + _stateWithId(groupId).notifier.dispose(); + _states.remove(groupId); } - void notifyDidInsertPhantom(String columnId, int index) { - _stateWithId(columnId).notifier.insert(index); + void notifyDidInsertPhantom(String groupId, int index) { + _stateWithId(groupId).notifier.insert(index); } - void notifyDidRemovePhantom(String columnId) { - _stateWithId(columnId).notifier.remove(); + void notifyDidRemovePhantom(String groupId) { + _stateWithId(groupId).notifier.remove(); } - ColumnState _stateWithId(String columnId) { - var state = _states[columnId]; + GroupState _stateWithId(String groupId) { + var state = _states[groupId]; if (state == null) { - state = ColumnState(); - _states[columnId] = state; + state = GroupState(); + _states[groupId] = state; } return state; } } -class ColumnState { +class GroupState { bool isDragging = false; final notifier = PassthroughPhantomNotifier(); } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart index 77cfc1cb13..b3ae96b28e 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; -class AppFlowyColumnItemCard extends StatefulWidget { +class AppFlowyGroupCard extends StatefulWidget { final Widget? child; final EdgeInsets margin; final BoxConstraints boxConstraints; final BoxDecoration decoration; - const AppFlowyColumnItemCard({ + const AppFlowyGroupCard({ this.child, this.margin = const EdgeInsets.all(4), this.decoration = const BoxDecoration( @@ -18,10 +18,10 @@ class AppFlowyColumnItemCard extends StatefulWidget { }) : super(key: key); @override - State createState() => _AppFlowyColumnItemCardState(); + State createState() => _AppFlowyGroupCardState(); } -class _AppFlowyColumnItemCardState extends State { +class _AppFlowyGroupCardState extends State { @override Widget build(BuildContext context) { return Padding( diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart index c877e4fe4d..54ad4e242d 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart @@ -2,14 +2,14 @@ import 'package:flutter/material.dart'; typedef OnFooterAddButtonClick = void Function(); -class AppFlowyColumnFooter extends StatefulWidget { +class AppFlowyGroupFooter extends StatefulWidget { final double height; final Widget? icon; final Widget? title; final EdgeInsets margin; final OnFooterAddButtonClick? onAddButtonClick; - const AppFlowyColumnFooter({ + const AppFlowyGroupFooter({ this.icon, this.title, this.margin = const EdgeInsets.symmetric(horizontal: 12), @@ -19,10 +19,10 @@ class AppFlowyColumnFooter extends StatefulWidget { }) : super(key: key); @override - State createState() => _AppFlowyColumnFooterState(); + State createState() => _AppFlowyGroupFooterState(); } -class _AppFlowyColumnFooterState extends State { +class _AppFlowyGroupFooterState extends State { @override Widget build(BuildContext context) { return GestureDetector( diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart index 88f52c9134..9e17a340ee 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; typedef OnHeaderAddButtonClick = void Function(); typedef OnHeaderMoreButtonClick = void Function(); -class AppFlowyColumnHeader extends StatefulWidget { +class AppFlowyGroupHeader extends StatefulWidget { final double height; final Widget? icon; final Widget? title; @@ -13,7 +13,7 @@ class AppFlowyColumnHeader extends StatefulWidget { final OnHeaderAddButtonClick? onAddButtonClick; final OnHeaderMoreButtonClick? onMoreButtonClick; - const AppFlowyColumnHeader({ + const AppFlowyGroupHeader({ required this.height, this.icon, this.title, @@ -26,10 +26,10 @@ class AppFlowyColumnHeader extends StatefulWidget { }) : super(key: key); @override - State createState() => _AppFlowyColumnHeaderState(); + State createState() => _AppFlowyGroupHeaderState(); } -class _AppFlowyColumnHeaderState extends State { +class _AppFlowyGroupHeaderState extends State { @override Widget build(BuildContext context) { List children = []; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/widgets.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/widgets.dart diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/transitions.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/transitions.dart index 525006af9c..d5cb1de26a 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/transitions.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/transitions.dart @@ -1,5 +1,4 @@ import 'package:flutter/widgets.dart'; -import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; class SizeTransitionWithIntrinsicSize extends SingleChildRenderObjectWidget { diff --git a/frontend/app_flowy/packages/appflowy_board/pubspec.yaml b/frontend/app_flowy/packages/appflowy_board/pubspec.yaml index 14dc501dcc..90907edca5 100644 --- a/frontend/app_flowy/packages/appflowy_board/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_board/pubspec.yaml @@ -1,6 +1,6 @@ name: appflowy_board -description: AppFlowy board implementation. -version: 0.0.6 +description: AppFlowyBoard is a board-style widget that consists of multi-groups. It supports drag and drop between different groups. +version: 0.0.7 homepage: https://github.com/AppFlowy-IO/AppFlowy repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board diff --git a/frontend/app_flowy/pubspec.lock b/frontend/app_flowy/pubspec.lock index 8b8b9cfb05..693c6b41e9 100644 --- a/frontend/app_flowy/pubspec.lock +++ b/frontend/app_flowy/pubspec.lock @@ -28,7 +28,7 @@ packages: path: "packages/appflowy_board" relative: true source: path - version: "0.0.6" + version: "0.0.7" appflowy_editor: dependency: "direct main" description: