fix: select card text

This commit is contained in:
appflowy 2022-10-06 22:26:18 +08:00
parent 2331b328d8
commit 73e81da356
14 changed files with 245 additions and 127 deletions

View File

@ -45,5 +45,7 @@
<array> <array>
<string>en</string> <string>en</string>
</array> </array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict> </dict>
</plist> </plist>

View File

@ -89,18 +89,30 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
(err) => Log.error(err), (err) => Log.error(err),
); );
}, },
didCreateRow: (String groupId, RowPB row, int? index) { didCreateRow: (group, row, int? index) {
emit(state.copyWith( emit(state.copyWith(
editingRow: Some(BoardEditingRow( editingRow: Some(BoardEditingRow(
columnId: groupId, group: group,
row: row, row: row,
index: index, index: index,
)), )),
)); ));
_groupItemStartEditing(group, row, true);
}, },
endEditRow: (rowId) { startEditingRow: (group, row) {
emit(state.copyWith(
editingRow: Some(BoardEditingRow(
group: group,
row: row,
index: null,
)),
));
_groupItemStartEditing(group, row, true);
},
endEditingRow: (rowId) {
state.editingRow.fold(() => null, (editingRow) { state.editingRow.fold(() => null, (editingRow) {
assert(editingRow.row.id == rowId); assert(editingRow.row.id == rowId);
_groupItemStartEditing(editingRow.group, editingRow.row, false);
emit(state.copyWith(editingRow: none())); emit(state.copyWith(editingRow: none()));
}); });
}, },
@ -122,6 +134,24 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
); );
} }
void _groupItemStartEditing(GroupPB group, RowPB row, bool isEdit) {
final fieldContext = fieldController.getField(group.fieldId);
if (fieldContext == null) {
Log.warn("FieldContext should not be null");
return;
}
boardController.enableGroupDragging(!isEdit);
// boardController.updateGroupItem(
// group.groupId,
// GroupItem(
// row: row,
// fieldContext: fieldContext,
// isDraggable: !isEdit,
// ),
// );
}
void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) { void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) {
if (fromRow != null) { if (fromRow != null) {
_rowService _rowService
@ -156,7 +186,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
return super.close(); return super.close();
} }
void initializeGroups(List<GroupPB> groups) { void initializeGroups(List<GroupPB> groupsData) {
for (var controller in groupControllers.values) { for (var controller in groupControllers.values) {
controller.dispose(); controller.dispose();
} }
@ -164,27 +194,27 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
boardController.clear(); boardController.clear();
// //
List<AppFlowyGroupData> columns = groups List<AppFlowyGroupData> groups = groupsData
.where((group) => fieldController.getField(group.fieldId) != null) .where((group) => fieldController.getField(group.fieldId) != null)
.map((group) { .map((group) {
return AppFlowyGroupData( return AppFlowyGroupData(
id: group.groupId, id: group.groupId,
name: group.desc, name: group.desc,
items: _buildRows(group), items: _buildGroupItems(group),
customData: BoardCustomData( customData: GroupData(
group: group, group: group,
fieldContext: fieldController.getField(group.fieldId)!, fieldContext: fieldController.getField(group.fieldId)!,
), ),
); );
}).toList(); }).toList();
boardController.addGroups(columns); boardController.addGroups(groups);
for (final group in groups) { for (final group in groupsData) {
final delegate = GroupControllerDelegateImpl( final delegate = GroupControllerDelegateImpl(
controller: boardController, controller: boardController,
fieldController: fieldController, fieldController: fieldController,
onNewColumnItem: (groupId, row, index) { onNewColumnItem: (groupId, row, index) {
add(BoardEvent.didCreateRow(groupId, row, index)); add(BoardEvent.didCreateRow(group, row, index));
}, },
); );
final controller = GroupController( final controller = GroupController(
@ -242,10 +272,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
); );
} }
List<AppFlowyGroupItem> _buildRows(GroupPB group) { List<AppFlowyGroupItem> _buildGroupItems(GroupPB group) {
final items = group.rows.map((row) { final items = group.rows.map((row) {
final fieldContext = fieldController.getField(group.fieldId); final fieldContext = fieldController.getField(group.fieldId);
return BoardColumnItem(row: row, fieldContext: fieldContext!); return GroupItem(
row: row,
fieldContext: fieldContext!,
);
}).toList(); }).toList();
return <AppFlowyGroupItem>[...items]; return <AppFlowyGroupItem>[...items];
@ -270,11 +303,15 @@ class BoardEvent with _$BoardEvent {
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow; const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow; const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
const factory BoardEvent.didCreateRow( const factory BoardEvent.didCreateRow(
String groupId, GroupPB group,
RowPB row, RowPB row,
int? index, int? index,
) = _DidCreateRow; ) = _DidCreateRow;
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow; const factory BoardEvent.startEditingRow(
GroupPB group,
RowPB row,
) = _StartEditRow;
const factory BoardEvent.endEditingRow(String rowId) = _EndEditRow;
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError; const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
const factory BoardEvent.didReceiveGridUpdate( const factory BoardEvent.didReceiveGridUpdate(
GridPB grid, GridPB grid,
@ -334,14 +371,17 @@ class GridFieldEquatable extends Equatable {
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields); UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
} }
class BoardColumnItem extends AppFlowyGroupItem { class GroupItem extends AppFlowyGroupItem {
final RowPB row; final RowPB row;
final GridFieldContext fieldContext; final GridFieldContext fieldContext;
BoardColumnItem({ GroupItem({
required this.row, required this.row,
required this.fieldContext, required this.fieldContext,
}); bool draggable = true,
}) {
super.draggable = draggable;
}
@override @override
String get id => row.id; String get id => row.id;
@ -367,10 +407,16 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
} }
if (index != null) { if (index != null) {
final item = BoardColumnItem(row: row, fieldContext: fieldContext); final item = GroupItem(
row: row,
fieldContext: fieldContext,
);
controller.insertGroupItem(group.groupId, index, item); controller.insertGroupItem(group.groupId, index, item);
} else { } else {
final item = BoardColumnItem(row: row, fieldContext: fieldContext); final item = GroupItem(
row: row,
fieldContext: fieldContext,
);
controller.addGroupItem(group.groupId, item); controller.addGroupItem(group.groupId, item);
} }
} }
@ -389,7 +435,10 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
} }
controller.updateGroupItem( controller.updateGroupItem(
group.groupId, group.groupId,
BoardColumnItem(row: row, fieldContext: fieldContext), GroupItem(
row: row,
fieldContext: fieldContext,
),
); );
} }
@ -400,7 +449,11 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
Log.warn("FieldContext should not be null"); Log.warn("FieldContext should not be null");
return; return;
} }
final item = BoardColumnItem(row: row, fieldContext: fieldContext); final item = GroupItem(
row: row,
fieldContext: fieldContext,
draggable: false,
);
if (index != null) { if (index != null) {
controller.insertGroupItem(group.groupId, index, item); controller.insertGroupItem(group.groupId, index, item);
@ -412,21 +465,21 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
} }
class BoardEditingRow { class BoardEditingRow {
String columnId; GroupPB group;
RowPB row; RowPB row;
int? index; int? index;
BoardEditingRow({ BoardEditingRow({
required this.columnId, required this.group,
required this.row, required this.row,
required this.index, required this.index,
}); });
} }
class BoardCustomData { class GroupData {
final GroupPB group; final GroupPB group;
final GridFieldContext fieldContext; final GridFieldContext fieldContext;
BoardCustomData({ GroupData({
required this.group, required this.group,
required this.fieldContext, required this.fieldContext,
}); });

View File

@ -83,7 +83,7 @@ class _BoardContentState extends State<BoardContent> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocListener<BoardBloc, BoardState>( return BlocListener<BoardBloc, BoardState>(
listener: (context, state) => _handleEditState(state, context), listener: (context, state) => _handleEditStateChanged(state, context),
child: BlocBuilder<BoardBloc, BoardState>( child: BlocBuilder<BoardBloc, BoardState>(
buildWhen: (previous, current) => previous.groupIds != current.groupIds, buildWhen: (previous, current) => previous.groupIds != current.groupIds,
builder: (context, state) { builder: (context, state) {
@ -128,21 +128,14 @@ class _BoardContentState extends State<BoardContent> {
); );
} }
void _handleEditState(BoardState state, BuildContext context) { void _handleEditStateChanged(BoardState state, BuildContext context) {
state.editingRow.fold( state.editingRow.fold(
() => null, () => null,
(editingRow) { (editingRow) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (editingRow.index != null) { if (editingRow.index != null) {
context
.read<BoardBloc>()
.add(BoardEvent.endEditRow(editingRow.row.id));
} else { } else {
scrollManager.scrollToBottom(editingRow.columnId, (boardContext) { scrollManager.scrollToBottom(editingRow.group.groupId);
context
.read<BoardBloc>()
.add(BoardEvent.endEditRow(editingRow.row.id));
});
} }
}); });
}, },
@ -156,14 +149,14 @@ class _BoardContentState extends State<BoardContent> {
Widget _buildHeader( Widget _buildHeader(
BuildContext context, BuildContext context,
AppFlowyGroupData columnData, AppFlowyGroupData groupData,
) { ) {
final boardCustomData = columnData.customData as BoardCustomData; final boardCustomData = groupData.customData as GroupData;
return AppFlowyGroupHeader( return AppFlowyGroupHeader(
title: Flexible( title: Flexible(
fit: FlexFit.tight, fit: FlexFit.tight,
child: FlowyText.medium( child: FlowyText.medium(
columnData.headerData.groupName, groupData.headerData.groupName,
fontSize: 14, fontSize: 14,
overflow: TextOverflow.clip, overflow: TextOverflow.clip,
color: context.read<AppTheme>().textColor, color: context.read<AppTheme>().textColor,
@ -180,7 +173,7 @@ class _BoardContentState extends State<BoardContent> {
), ),
onAddButtonClick: () { onAddButtonClick: () {
context.read<BoardBloc>().add( context.read<BoardBloc>().add(
BoardEvent.createHeaderRow(columnData.id), BoardEvent.createHeaderRow(groupData.id),
); );
}, },
height: 50, height: 50,
@ -218,15 +211,16 @@ class _BoardContentState extends State<BoardContent> {
Widget _buildCard( Widget _buildCard(
BuildContext context, BuildContext context,
AppFlowyGroupData group, AppFlowyGroupData afGroupData,
AppFlowyGroupItem columnItem, AppFlowyGroupItem afGroupItem,
) { ) {
final boardColumnItem = columnItem as BoardColumnItem; final groupItem = afGroupItem as GroupItem;
final rowPB = boardColumnItem.row; final groupData = afGroupData.customData as GroupData;
final rowPB = groupItem.row;
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId); final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
/// Return placeholder widget if the rowCache is null. /// Return placeholder widget if the rowCache is null.
if (rowCache == null) return SizedBox(key: ObjectKey(columnItem)); if (rowCache == null) return SizedBox(key: ObjectKey(groupItem));
final fieldController = context.read<BoardBloc>().fieldController; final fieldController = context.read<BoardBloc>().fieldController;
final gridId = context.read<BoardBloc>().gridId; final gridId = context.read<BoardBloc>().gridId;
@ -241,19 +235,19 @@ class _BoardContentState extends State<BoardContent> {
context.read<BoardBloc>().state.editingRow.fold( context.read<BoardBloc>().state.editingRow.fold(
() => null, () => null,
(editingRow) { (editingRow) {
isEditing = editingRow.row.id == columnItem.row.id; isEditing = editingRow.row.id == groupItem.row.id;
}, },
); );
final groupItemId = columnItem.id + group.id; final groupItemId = groupItem.row.id + groupData.group.groupId;
return AppFlowyGroupCard( return AppFlowyGroupCard(
key: ValueKey(groupItemId), key: ValueKey(groupItemId),
margin: config.cardPadding, margin: config.cardPadding,
decoration: _makeBoxDecoration(context), decoration: _makeBoxDecoration(context),
child: BoardCard( child: BoardCard(
gridId: gridId, gridId: gridId,
groupId: group.id, groupId: groupData.group.groupId,
fieldId: boardColumnItem.fieldContext.id, fieldId: groupItem.fieldContext.id,
isEditing: isEditing, isEditing: isEditing,
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
dataController: cardController, dataController: cardController,
@ -264,6 +258,19 @@ class _BoardContentState extends State<BoardContent> {
rowCache, rowCache,
context, context,
), ),
onStartEditing: () {
context.read<BoardBloc>().add(
BoardEvent.startEditingRow(
groupData.group,
groupItem.row,
),
);
},
onEndEditing: () {
context
.read<BoardBloc>()
.add(BoardEvent.endEditingRow(groupItem.row.id));
},
), ),
); );
} }
@ -345,7 +352,7 @@ extension HexColor on Color {
} }
} }
Widget? _buildHeaderIcon(BoardCustomData customData) { Widget? _buildHeaderIcon(GroupData customData) {
Widget? widget; Widget? widget;
switch (customData.fieldType) { switch (customData.fieldType) {
case FieldType.Checkbox: case FieldType.Checkbox:

View File

@ -76,6 +76,10 @@ class EditableRowNotifier {
} }
abstract class EditableCell { abstract class EditableCell {
// Each cell notifier will be bind to the [EditableRowNotifier], which enable
// the row notifier receive its cells event. For example: begin editing the
// cell or end editing the cell.
//
EditableCellNotifier? get editableNotifier; EditableCellNotifier? get editableNotifier;
} }

View File

@ -42,6 +42,9 @@ class _BoardTextCellState extends State<BoardTextCell> {
focusNode.requestFocus(); focusNode.requestFocus();
} }
// If the focusNode lost its focus, the widget's editableNotifier will
// set to false, which will cause the [EditableRowNotifier] to receive
// end edit event.
focusNode.addListener(() { focusNode.addListener(() {
if (!focusNode.hasFocus) { if (!focusNode.hasFocus) {
focusWhenInit = false; focusWhenInit = false;
@ -131,7 +134,11 @@ class _BoardTextCellState extends State<BoardTextCell> {
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
vertical: BoardSizes.cardCellVPadding, vertical: BoardSizes.cardCellVPadding,
), ),
child: FlowyText.medium(state.content, fontSize: 14), child: FlowyText.medium(
state.content,
fontSize: 14,
maxLines: null, // Enable multiple lines
),
); );
} }

View File

@ -21,6 +21,8 @@ class BoardCard extends StatefulWidget {
final CardDataController dataController; final CardDataController dataController;
final BoardCellBuilder cellBuilder; final BoardCellBuilder cellBuilder;
final void Function(BuildContext) openCard; final void Function(BuildContext) openCard;
final VoidCallback onStartEditing;
final VoidCallback onEndEditing;
const BoardCard({ const BoardCard({
required this.gridId, required this.gridId,
@ -30,6 +32,8 @@ class BoardCard extends StatefulWidget {
required this.dataController, required this.dataController,
required this.cellBuilder, required this.cellBuilder,
required this.openCard, required this.openCard,
required this.onStartEditing,
required this.onEndEditing,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -56,6 +60,12 @@ class _BoardCardState extends State<BoardCard> {
rowNotifier.isEditing.addListener(() { rowNotifier.isEditing.addListener(() {
if (!mounted) return; if (!mounted) return;
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value)); _cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
if (rowNotifier.isEditing.value) {
widget.onStartEditing();
} else {
widget.onEndEditing();
}
}); });
popoverController = PopoverController(); popoverController = PopoverController();

View File

@ -78,7 +78,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
height: 50, height: 50,
margin: config.groupItemPadding, margin: config.groupItemPadding,
onAddButtonClick: () { onAddButtonClick: () {
boardController.scrollToBottom(columnData.id, (p0) {}); boardController.scrollToBottom(columnData.id);
}, },
); );
}, },

View File

@ -13,7 +13,8 @@ import '../rendering/board_overlay.dart';
class AppFlowyBoardScrollController { class AppFlowyBoardScrollController {
AppFlowyBoardState? _groupState; AppFlowyBoardState? _groupState;
void scrollToBottom(String groupId, void Function(BuildContext)? completed) { void scrollToBottom(String groupId,
{void Function(BuildContext)? completed}) {
_groupState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed); _groupState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
} }
} }
@ -39,9 +40,6 @@ class AppFlowyBoardConfig {
} }
class AppFlowyBoard 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. /// The widget that will be rendered as the background of the board.
final Widget? background; final Widget? background;
@ -178,7 +176,10 @@ class _AppFlowyBoardContent extends StatefulWidget {
this.headerBuilder, this.headerBuilder,
required this.phantomController, required this.phantomController,
Key? key, Key? key,
}) : reorderFlexConfig = const ReorderFlexConfig(), }) : reorderFlexConfig = const ReorderFlexConfig(
direction: Axis.horizontal,
dragDirection: Axis.horizontal,
),
super(key: key); super(key: key);
@override @override
@ -206,9 +207,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
scrollController: widget.scrollController, scrollController: widget.scrollController,
onReorder: widget.onReorder, onReorder: widget.onReorder,
dataSource: widget.dataController, dataSource: widget.dataController,
direction: Axis.horizontal,
interceptor: interceptor, interceptor: interceptor,
reorderable: true,
children: _buildColumns(), children: _buildColumns(),
); );

View File

@ -202,6 +202,14 @@ class AppFlowyBoardController extends ChangeNotifier
getGroupController(groupId)?.replaceOrInsertItem(item); getGroupController(groupId)?.replaceOrInsertItem(item);
} }
void enableGroupDragging(bool isEnable) {
for (var groupController in _groupControllers.values) {
groupController.enableDragging(isEnable);
}
notifyListeners();
}
/// Moves the item at [fromGroupIndex] in group with id [fromGroupId] to /// Moves the item at [fromGroupIndex] in group with id [fromGroupId] to
/// group with id [toGroupId] at [toGroupIndex] /// group with id [toGroupId] at [toGroupIndex]
@override @override

View File

@ -5,6 +5,8 @@ import 'package:appflowy_board/src/widgets/reorder_flex/reorder_flex.dart';
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
typedef IsDraggable = bool;
/// A item represents the generic data model of each group card. /// A item represents the generic data model of each group card.
/// ///
/// Each item displayed in the group required to implement this class. /// Each item displayed in the group required to implement this class.
@ -155,6 +157,15 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
-1; -1;
} }
void enableDragging(bool isEnable) {
groupData.draggable = isEnable;
for (var item in groupData._items) {
item.draggable = isEnable;
}
_notify();
}
void _notify() { void _notify() {
notifyListeners(); notifyListeners();
} }

View File

@ -16,13 +16,13 @@ class FlexDragTargetData extends DragTargetData {
@override @override
final int draggingIndex; final int draggingIndex;
final DraggingState _state; final DraggingState _draggingState;
Widget? get draggingWidget => _state.draggingWidget; Widget? get draggingWidget => _draggingState.draggingWidget;
Size? get feedbackSize => _state.feedbackSize; Size? get feedbackSize => _draggingState.feedbackSize;
bool get isDragging => _state.isDragging(); bool get isDragging => _draggingState.isDragging();
final String dragTargetId; final String dragTargetId;
@ -40,8 +40,8 @@ class FlexDragTargetData extends DragTargetData {
required this.reorderFlexId, required this.reorderFlexId,
required this.reorderFlexItem, required this.reorderFlexItem,
required this.dragTargetIndexKey, required this.dragTargetIndexKey,
required DraggingState state, required DraggingState draggingState,
}) : _state = state; }) : _draggingState = draggingState;
@override @override
String toString() { String toString() {

View File

@ -1,3 +1,4 @@
import 'package:appflowy_board/appflowy_board.dart';
import 'package:appflowy_board/src/utils/log.dart'; import 'package:appflowy_board/src/utils/log.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
@ -78,10 +79,12 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
final bool useMoveAnimation; final bool useMoveAnimation;
final bool draggable; final IsDraggable draggable;
final double draggingOpacity; final double draggingOpacity;
final Axis? dragDirection;
const ReorderDragTarget({ const ReorderDragTarget({
Key? key, Key? key,
required this.child, required this.child,
@ -99,6 +102,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
this.onLeave, this.onLeave,
this.draggableTargetBuilder, this.draggableTargetBuilder,
this.draggingOpacity = 0.3, this.draggingOpacity = 0.3,
this.dragDirection,
}) : super(key: key); }) : super(key: key);
@override @override
@ -115,8 +119,10 @@ class _ReorderDragTargetState<T extends DragTargetData>
Widget dragTarget = DragTarget<T>( Widget dragTarget = DragTarget<T>(
builder: _buildDraggableWidget, builder: _buildDraggableWidget,
onWillAccept: (dragTargetData) { onWillAccept: (dragTargetData) {
assert(dragTargetData != null); if (dragTargetData == null) {
if (dragTargetData == null) return false; return false;
}
return widget.onWillAccept(dragTargetData); return widget.onWillAccept(dragTargetData);
}, },
onAccept: widget.onAccept, onAccept: widget.onAccept,
@ -140,9 +146,6 @@ class _ReorderDragTargetState<T extends DragTargetData>
List<T?> acceptedCandidates, List<T?> acceptedCandidates,
List<dynamic> rejectedCandidates, List<dynamic> rejectedCandidates,
) { ) {
if (!widget.draggable) {
return widget.child;
}
Widget feedbackBuilder = Builder(builder: (BuildContext context) { Widget feedbackBuilder = Builder(builder: (BuildContext context) {
BoxConstraints contentSizeConstraints = BoxConstraints contentSizeConstraints =
BoxConstraints.loose(_draggingFeedbackSize!); BoxConstraints.loose(_draggingFeedbackSize!);
@ -163,7 +166,8 @@ class _ReorderDragTargetState<T extends DragTargetData>
widget.deleteAnimationController, widget.deleteAnimationController,
) ?? ) ??
Draggable<DragTargetData>( Draggable<DragTargetData>(
maxSimultaneousDrags: 1, axis: widget.dragDirection,
maxSimultaneousDrags: widget.draggable ? 1 : 0,
data: widget.dragTargetData, data: widget.dragTargetData,
ignoringFeedbackSemantics: false, ignoringFeedbackSemantics: false,
feedback: feedbackBuilder, feedback: feedbackBuilder,

View File

@ -1,6 +1,7 @@
import 'dart:collection'; import 'dart:collection';
import 'dart:math'; import 'dart:math';
import 'package:appflowy_board/appflowy_board.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import '../../utils/log.dart'; import '../../utils/log.dart';
@ -29,6 +30,8 @@ abstract class ReoderFlexDataSource {
abstract class ReoderFlexItem { abstract class ReoderFlexItem {
/// [id] is used to identify the item. It must be unique. /// [id] is used to identify the item. It must be unique.
String get id; String get id;
IsDraggable draggable = true;
} }
/// Cache each dragTarget's key. /// Cache each dragTarget's key.
@ -73,8 +76,15 @@ class ReorderFlexConfig {
final bool useMovePlaceholder; final bool useMovePlaceholder;
/// [direction] How to place the children, default is Axis.vertical
final Axis direction;
final Axis? dragDirection;
const ReorderFlexConfig({ const ReorderFlexConfig({
this.useMoveAnimation = true, this.useMoveAnimation = true,
this.direction = Axis.vertical,
this.dragDirection,
}) : useMovePlaceholder = !useMoveAnimation; }) : useMovePlaceholder = !useMoveAnimation;
} }
@ -82,8 +92,6 @@ class ReorderFlex extends StatefulWidget {
final ReorderFlexConfig config; final ReorderFlexConfig config;
final List<Widget> children; final List<Widget> children;
/// [direction] How to place the children, default is Axis.vertical
final Axis direction;
final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start; final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start;
final ScrollController? scrollController; final ScrollController? scrollController;
@ -108,8 +116,6 @@ class ReorderFlex extends StatefulWidget {
final ReorderFlexAction? reorderFlexAction; final ReorderFlexAction? reorderFlexAction;
final bool reorderable;
ReorderFlex({ ReorderFlex({
Key? key, Key? key,
this.scrollController, this.scrollController,
@ -117,14 +123,12 @@ class ReorderFlex extends StatefulWidget {
required this.children, required this.children,
required this.config, required this.config,
required this.onReorder, required this.onReorder,
this.reorderable = true,
this.dragStateStorage, this.dragStateStorage,
this.dragTargetKeys, this.dragTargetKeys,
this.onDragStarted, this.onDragStarted,
this.onDragEnded, this.onDragEnded,
this.interceptor, this.interceptor,
this.reorderFlexAction, this.reorderFlexAction,
this.direction = Axis.vertical,
}) : assert(children.every((Widget w) => w.key != null), }) : assert(children.every((Widget w) => w.key != null),
'All child must have a key.'), 'All child must have a key.'),
super(key: key); super(key: key);
@ -146,8 +150,8 @@ class ReorderFlexState extends State<ReorderFlex>
/// Whether or not we are currently scrolling this view to show a widget. /// Whether or not we are currently scrolling this view to show a widget.
bool _scrolling = false; bool _scrolling = false;
/// [dragState] records the dragging state including dragStartIndex, and phantomIndex, etc. /// [draggingState] records the dragging state including dragStartIndex, and phantomIndex, etc.
late DraggingState dragState; late DraggingState draggingState;
/// [_animation] controls the dragging animations /// [_animation] controls the dragging animations
late DragTargetAnimation _animation; late DragTargetAnimation _animation;
@ -158,9 +162,9 @@ class ReorderFlexState extends State<ReorderFlex>
void initState() { void initState() {
_notifier = ReorderFlexNotifier(); _notifier = ReorderFlexNotifier();
final flexId = widget.reorderFlexId; final flexId = widget.reorderFlexId;
dragState = widget.dragStateStorage?.readState(flexId) ?? draggingState = widget.dragStateStorage?.readState(flexId) ??
DraggingState(widget.reorderFlexId); DraggingState(widget.reorderFlexId);
Log.trace('[DragTarget] init dragState: $dragState'); Log.trace('[DragTarget] init dragState: $draggingState');
widget.dragStateStorage?.removeState(flexId); widget.dragStateStorage?.removeState(flexId);
@ -168,7 +172,7 @@ class ReorderFlexState extends State<ReorderFlex>
reorderAnimationDuration: widget.config.reorderAnimationDuration, reorderAnimationDuration: widget.config.reorderAnimationDuration,
entranceAnimateStatusChanged: (status) { entranceAnimateStatusChanged: (status) {
if (status == AnimationStatus.completed) { if (status == AnimationStatus.completed) {
if (dragState.nextIndex == -1) return; if (draggingState.nextIndex == -1) return;
setState(() => _requestAnimationToNextIndex()); setState(() => _requestAnimationToNextIndex());
} }
}, },
@ -225,7 +229,7 @@ class ReorderFlexState extends State<ReorderFlex>
indexKey, indexKey,
); );
children.add(_wrap(child, i, indexKey)); children.add(_wrap(child, i, indexKey, item.draggable));
// if (widget.config.useMovePlaceholder) { // if (widget.config.useMovePlaceholder) {
// children.add(DragTargeMovePlaceholder( // children.add(DragTargeMovePlaceholder(
@ -256,64 +260,70 @@ class ReorderFlexState extends State<ReorderFlex>
/// when the animation finish. /// when the animation finish.
if (_animation.entranceController.isCompleted) { if (_animation.entranceController.isCompleted) {
dragState.removePhantom(); draggingState.removePhantom();
if (!isAcceptingNewTarget && dragState.didDragTargetMoveToNext()) { if (!isAcceptingNewTarget && draggingState.didDragTargetMoveToNext()) {
return; return;
} }
dragState.moveDragTargetToNext(); draggingState.moveDragTargetToNext();
_animation.animateToNext(); _animation.animateToNext();
} }
} }
/// [child]: the child will be wrapped with dartTarget /// [child]: the child will be wrapped with dartTarget
/// [childIndex]: the index of the child in a list /// [childIndex]: the index of the child in a list
Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) { Widget _wrap(
Widget child,
int childIndex,
GlobalObjectKey indexKey,
IsDraggable draggable,
) {
return Builder(builder: (context) { return Builder(builder: (context) {
final ReorderDragTarget dragTarget = _buildDragTarget( final ReorderDragTarget dragTarget = _buildDragTarget(
context, context,
child, child,
childIndex, childIndex,
indexKey, indexKey,
draggable,
); );
int shiftedIndex = childIndex; int shiftedIndex = childIndex;
if (dragState.isOverlapWithPhantom()) { if (draggingState.isOverlapWithPhantom()) {
shiftedIndex = dragState.calculateShiftedIndex(childIndex); shiftedIndex = draggingState.calculateShiftedIndex(childIndex);
} }
Log.trace( Log.trace(
'Rebuild: Group:[${dragState.reorderFlexId}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex'); 'Rebuild: Group:[${draggingState.reorderFlexId}] ${draggingState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
final currentIndex = dragState.currentIndex; final currentIndex = draggingState.currentIndex;
final dragPhantomIndex = dragState.phantomIndex; final dragPhantomIndex = draggingState.phantomIndex;
if (shiftedIndex == currentIndex || childIndex == dragPhantomIndex) { if (shiftedIndex == currentIndex || childIndex == dragPhantomIndex) {
Widget dragSpace; Widget dragSpace;
if (dragState.draggingWidget != null) { if (draggingState.draggingWidget != null) {
if (dragState.draggingWidget is PhantomWidget) { if (draggingState.draggingWidget is PhantomWidget) {
dragSpace = dragState.draggingWidget!; dragSpace = draggingState.draggingWidget!;
} else { } else {
dragSpace = PhantomWidget( dragSpace = PhantomWidget(
opacity: widget.config.draggingWidgetOpacity, opacity: widget.config.draggingWidgetOpacity,
child: dragState.draggingWidget, child: draggingState.draggingWidget,
); );
} }
} else { } else {
dragSpace = SizedBox.fromSize(size: dragState.dropAreaSize); dragSpace = SizedBox.fromSize(size: draggingState.dropAreaSize);
} }
/// Returns the dragTarget it is not start dragging. The size of the /// Returns the dragTarget it is not start dragging. The size of the
/// dragTarget is the same as the the passed in child. /// dragTarget is the same as the the passed in child.
/// ///
if (dragState.isNotDragging()) { if (draggingState.isNotDragging()) {
return _buildDraggingContainer(children: [dragTarget]); return _buildDraggingContainer(children: [dragTarget]);
} }
/// Determine the size of the drop area to show under the dragging widget. /// Determine the size of the drop area to show under the dragging widget.
Size? feedbackSize = Size.zero; Size? feedbackSize = Size.zero;
if (widget.config.useMoveAnimation) { if (widget.config.useMoveAnimation) {
feedbackSize = dragState.feedbackSize; feedbackSize = draggingState.feedbackSize;
} }
Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize); Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize);
@ -321,7 +331,7 @@ class ReorderFlexState extends State<ReorderFlex>
/// When start dragging, the dragTarget, [ReorderDragTarget], will /// When start dragging, the dragTarget, [ReorderDragTarget], will
/// return a [IgnorePointerWidget] which size is zero. /// return a [IgnorePointerWidget] which size is zero.
if (dragState.isPhantomAboveDragTarget()) { if (draggingState.isPhantomAboveDragTarget()) {
_notifier.updateDragTargetIndex(currentIndex); _notifier.updateDragTargetIndex(currentIndex);
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) { if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
return _buildDraggingContainer(children: [ return _buildDraggingContainer(children: [
@ -343,7 +353,7 @@ class ReorderFlexState extends State<ReorderFlex>
} }
/// ///
if (dragState.isPhantomBelowDragTarget()) { if (draggingState.isPhantomBelowDragTarget()) {
_notifier.updateDragTargetIndex(currentIndex); _notifier.updateDragTargetIndex(currentIndex);
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) { if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
return _buildDraggingContainer(children: [ return _buildDraggingContainer(children: [
@ -364,10 +374,10 @@ class ReorderFlexState extends State<ReorderFlex>
} }
} }
assert(!dragState.isOverlapWithPhantom()); assert(!draggingState.isOverlapWithPhantom());
List<Widget> children = []; List<Widget> children = [];
if (dragState.isDragTargetMovingDown()) { if (draggingState.isDragTargetMovingDown()) {
children.addAll([dragTarget, appearSpace]); children.addAll([dragTarget, appearSpace]);
} else { } else {
children.addAll([appearSpace, dragTarget]); children.addAll([appearSpace, dragTarget]);
@ -395,15 +405,17 @@ class ReorderFlexState extends State<ReorderFlex>
Widget child, Widget child,
int dragTargetIndex, int dragTargetIndex,
GlobalObjectKey indexKey, GlobalObjectKey indexKey,
IsDraggable draggable,
) { ) {
final reorderFlexItem = widget.dataSource.items[dragTargetIndex]; final reorderFlexItem = widget.dataSource.items[dragTargetIndex];
return ReorderDragTarget<FlexDragTargetData>( return ReorderDragTarget<FlexDragTargetData>(
indexGlobalKey: indexKey, indexGlobalKey: indexKey,
draggable: draggable,
dragTargetData: FlexDragTargetData( dragTargetData: FlexDragTargetData(
draggingIndex: dragTargetIndex, draggingIndex: dragTargetIndex,
reorderFlexId: widget.reorderFlexId, reorderFlexId: widget.reorderFlexId,
reorderFlexItem: reorderFlexItem, reorderFlexItem: reorderFlexItem,
state: dragState, draggingState: draggingState,
dragTargetId: reorderFlexItem.id, dragTargetId: reorderFlexItem.id,
dragTargetIndexKey: indexKey, dragTargetIndexKey: indexKey,
), ),
@ -432,11 +444,11 @@ class ReorderFlexState extends State<ReorderFlex>
setState(() { setState(() {
if (dragTargetData.reorderFlexId == widget.reorderFlexId) { if (dragTargetData.reorderFlexId == widget.reorderFlexId) {
_onReordered( _onReordered(
dragState.dragStartIndex, draggingState.dragStartIndex,
dragState.currentIndex, draggingState.currentIndex,
); );
} }
dragState.endDragging(); draggingState.endDragging();
widget.onDragEnded?.call(); widget.onDragEnded?.call();
}); });
}, },
@ -482,8 +494,8 @@ class ReorderFlexState extends State<ReorderFlex>
deleteAnimationController: _animation.deleteController, deleteAnimationController: _animation.deleteController,
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder, draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
useMoveAnimation: widget.config.useMoveAnimation, useMoveAnimation: widget.config.useMoveAnimation,
draggable: widget.reorderable,
draggingOpacity: widget.config.draggingWidgetOpacity, draggingOpacity: widget.config.draggingWidgetOpacity,
dragDirection: widget.config.dragDirection,
child: child, child: child,
); );
} }
@ -506,7 +518,7 @@ class ReorderFlexState extends State<ReorderFlex>
child, child,
_animation.entranceController, _animation.entranceController,
feedbackSize, feedbackSize,
widget.direction, widget.config.direction,
); );
} }
@ -515,7 +527,7 @@ class ReorderFlexState extends State<ReorderFlex>
child, child,
_animation.phantomController, _animation.phantomController,
feedbackSize, feedbackSize,
widget.direction, widget.config.direction,
); );
} }
@ -525,7 +537,7 @@ class ReorderFlexState extends State<ReorderFlex>
Size? feedbackSize, Size? feedbackSize,
) { ) {
setState(() { setState(() {
dragState.startDragging(draggingWidget, dragIndex, feedbackSize); draggingState.startDragging(draggingWidget, dragIndex, feedbackSize);
_animation.startDragging(); _animation.startDragging();
}); });
} }
@ -535,34 +547,34 @@ class ReorderFlexState extends State<ReorderFlex>
return; return;
} }
dragState.setStartDraggingIndex(dragTargetIndex); draggingState.setStartDraggingIndex(dragTargetIndex);
widget.dragStateStorage?.insertState( widget.dragStateStorage?.insertState(
widget.reorderFlexId, widget.reorderFlexId,
dragState, draggingState,
); );
} }
bool handleOnWillAccept(BuildContext context, int dragTargetIndex) { bool handleOnWillAccept(BuildContext context, int dragTargetIndex) {
final dragIndex = dragState.dragStartIndex; final dragIndex = draggingState.dragStartIndex;
/// The [willAccept] will be true if the dargTarget is the widget that gets /// The [willAccept] will be true if the dargTarget is the widget that gets
/// dragged and it is dragged on top of the other dragTargets. /// dragged and it is dragged on top of the other dragTargets.
/// ///
bool willAccept = bool willAccept = draggingState.dragStartIndex == dragIndex &&
dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; dragIndex != dragTargetIndex;
setState(() { setState(() {
if (willAccept) { if (willAccept) {
int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex); int shiftedIndex = draggingState.calculateShiftedIndex(dragTargetIndex);
dragState.updateNextIndex(shiftedIndex); draggingState.updateNextIndex(shiftedIndex);
} else { } else {
dragState.updateNextIndex(dragTargetIndex); draggingState.updateNextIndex(dragTargetIndex);
} }
_requestAnimationToNextIndex(isAcceptingNewTarget: true); _requestAnimationToNextIndex(isAcceptingNewTarget: true);
}); });
Log.trace( Log.trace(
'[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $dragState}'); '[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $draggingState}');
_scrollTo(context); _scrollTo(context);
@ -587,7 +599,7 @@ class ReorderFlexState extends State<ReorderFlex>
return child; return child;
} else { } else {
return SingleChildScrollView( return SingleChildScrollView(
scrollDirection: widget.direction, scrollDirection: widget.config.direction,
controller: _scrollController, controller: _scrollController,
child: child, child: child,
); );
@ -595,7 +607,7 @@ class ReorderFlexState extends State<ReorderFlex>
} }
Widget _wrapContainer(List<Widget> children) { Widget _wrapContainer(List<Widget> children) {
switch (widget.direction) { switch (widget.config.direction) {
case Axis.horizontal: case Axis.horizontal:
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -613,7 +625,7 @@ class ReorderFlexState extends State<ReorderFlex>
} }
Widget _buildDraggingContainer({required List<Widget> children}) { Widget _buildDraggingContainer({required List<Widget> children}) {
switch (widget.direction) { switch (widget.config.direction) {
case Axis.horizontal: case Axis.horizontal:
return Row( return Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@ -660,6 +672,7 @@ class ReorderFlexState extends State<ReorderFlex>
.ensureVisible( .ensureVisible(
dragTargetRenderObject, dragTargetRenderObject,
alignment: 0.5, alignment: 0.5,
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
duration: const Duration(milliseconds: 120), duration: const Duration(milliseconds: 120),
) )
.then((value) { .then((value) {
@ -683,9 +696,9 @@ class ReorderFlexState extends State<ReorderFlex>
// If and only if the current scroll offset falls in-between the offsets // If and only if the current scroll offset falls in-between the offsets
// necessary to reveal the selected context at the top or bottom of the // necessary to reveal the selected context at the top or bottom of the
// screen, then it is already on-screen. // screen, then it is already on-screen.
final double margin = widget.direction == Axis.horizontal final double margin = widget.config.direction == Axis.horizontal
? dragState.dropAreaSize.width ? draggingState.dropAreaSize.width
: dragState.dropAreaSize.height / 2.0; : draggingState.dropAreaSize.height / 2.0;
if (_scrollController.hasClients) { if (_scrollController.hasClients) {
final double scrollOffset = _scrollController.offset; final double scrollOffset = _scrollController.offset;
final double topOffset = max( final double topOffset = max(

View File

@ -28,7 +28,7 @@ packages:
path: "packages/appflowy_board" path: "packages/appflowy_board"
relative: true relative: true
source: path source: path
version: "0.0.7" version: "0.0.8"
appflowy_editor: appflowy_editor:
dependency: "direct main" dependency: "direct main"
description: description: