chore: add documentation

This commit is contained in:
appflowy 2022-08-05 20:33:00 +08:00
parent 2e0256f305
commit 9e4dbc53f7
8 changed files with 293 additions and 183 deletions

View File

@ -9,7 +9,7 @@ class MultiBoardListExample extends StatefulWidget {
}
class _MultiBoardListExampleState extends State<MultiBoardListExample> {
final BoardDataController boardData = BoardDataController();
final BoardDataController boardDataController = BoardDataController();
@override
void initState() {
@ -34,9 +34,9 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
TextItem("D"),
]);
boardData.setColumnData(column1);
boardData.setColumnData(column2);
boardData.setColumnData(column3);
boardDataController.setColumnData(column1);
boardDataController.setColumnData(column2);
boardDataController.setColumnData(column3);
super.initState();
}
@ -44,7 +44,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
@override
Widget build(BuildContext context) {
return Board(
dataController: boardData,
dataController: boardDataController,
background: Container(color: Colors.red),
footBuilder: (context, columnData) {
return Container(

View File

@ -67,7 +67,7 @@ class Board extends StatelessWidget {
footBuilder: footBuilder,
headerBuilder: headerBuilder,
phantomController: phantomController,
onReorder: dataController.onReorder,
onReorder: dataController.moveColumn,
);
},
),
@ -99,7 +99,7 @@ class BoardContent extends StatefulWidget {
final BoardPhantomController phantomController;
const BoardContent({
BoardContent({
required this.onReorder,
required this.delegate,
required this.dataController,
@ -107,22 +107,23 @@ class BoardContent extends StatefulWidget {
this.onDragEnded,
this.scrollController,
this.background,
this.spacing = 0.0,
this.config = const ReorderFlexConfig(),
this.spacing = 10.0,
required this.columnConstraints,
required this.cardBuilder,
this.footBuilder,
this.headerBuilder,
required this.phantomController,
Key? key,
}) : super(key: key);
}) : config = ReorderFlexConfig(spacing: spacing),
super(key: key);
@override
State<BoardContent> createState() => _BoardContentState();
}
class _BoardContentState extends State<BoardContent> {
final GlobalKey _columnContainerOverlayKey = GlobalKey(debugLabel: '$BoardContent overlay key');
final GlobalKey _columnContainerOverlayKey =
GlobalKey(debugLabel: '$BoardContent overlay key');
late BoardOverlayEntry _overlayEntry;
@override
@ -144,7 +145,6 @@ class _BoardContentState extends State<BoardContent> {
onDragEnded: widget.onDragEnded,
dataSource: widget.dataController,
direction: Axis.horizontal,
spacing: widget.spacing,
interceptor: interceptor,
children: _buildColumns(),
);
@ -171,36 +171,54 @@ class _BoardContentState extends State<BoardContent> {
}
List<Widget> _buildColumns() {
final acceptColumns = widget.dataController.columnIds;
final List<Widget> children = widget.dataController.columnDatas.map(
(columnData) {
final dataSource = _BoardColumnDataSourceImpl(
columnId: columnData.id,
dataController: widget.dataController,
);
final List<Widget> children = widget.dataController.columnDatas.map((columnData) {
final dataController = widget.dataController.columnController(columnData.id);
return ChangeNotifierProvider.value(
key: ValueKey(columnData.id),
value: dataController,
child: Consumer<BoardColumnDataController>(
builder: (context, value, child) {
return ConstrainedBox(
constraints: widget.columnConstraints,
child: BoardColumnWidget(
headerBuilder: widget.headerBuilder,
footBuilder: widget.footBuilder,
cardBuilder: widget.cardBuilder,
acceptedColumns: acceptColumns,
dataController: dataController,
scrollController: ScrollController(),
phantomController: widget.phantomController,
onReorder: (_, int fromIndex, int toIndex) {
dataController.move(fromIndex, toIndex);
},
),
);
},
),
);
}).toList();
return ChangeNotifierProvider.value(
key: ValueKey(columnData.id),
value: widget.dataController.columnController(columnData.id),
child: Consumer<BoardColumnDataController>(
builder: (context, value, child) {
return ConstrainedBox(
constraints: widget.columnConstraints,
child: BoardColumnWidget(
headerBuilder: widget.headerBuilder,
footBuilder: widget.footBuilder,
cardBuilder: widget.cardBuilder,
dataSource: dataSource,
scrollController: ScrollController(),
phantomController: widget.phantomController,
onReorder: widget.dataController.moveColumnItem,
spacing: 10,
),
);
},
),
);
},
).toList();
return children;
}
}
class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource {
String columnId;
final BoardDataController dataController;
_BoardColumnDataSourceImpl({
required this.columnId,
required this.dataController,
});
@override
BoardColumnData get columnData =>
dataController.columnController(columnId).columnData;
@override
List<String> get acceptedColumnIds => dataController.columnIds;
}

View File

@ -1,29 +1,67 @@
import 'package:flutter/material.dart';
import 'dart:collection';
import 'package:flutter/material.dart';
import '../../rendering/board_overlay.dart';
import '../../utils/log.dart';
import '../phantom/phantom_controller.dart';
import '../flex/reorder_flex.dart';
import '../flex/drag_target_inteceptor.dart';
import 'data_controller.dart';
typedef OnColumnDragStarted = void Function(int index);
typedef OnColumnDragEnded = void Function(String listId);
typedef OnColumnReorder = void Function(
String listId, int fromIndex, int toIndex);
String listId,
int fromIndex,
int toIndex,
);
typedef OnColumnDeleted = void Function(String listId, int deletedIndex);
typedef OnColumnInserted = void Function(String listId, int insertedIndex);
typedef BoardColumnCardBuilder = Widget Function(
BuildContext context, ColumnItem item);
BuildContext context,
ColumnItem item,
);
typedef BoardColumnHeaderBuilder = Widget Function(
BuildContext context, BoardColumnData columnData);
BuildContext context,
BoardColumnData columnData,
);
typedef BoardColumnFooterBuilder = Widget Function(
BuildContext context, BoardColumnData columnData);
BuildContext context,
BoardColumnData columnData,
);
abstract class BoardColumnDataDataSource extends ReoderFlextDataSource {
BoardColumnData get columnData;
List<String> get acceptedColumnIds;
@override
String get identifier => columnData.id;
@override
UnmodifiableListView<ColumnItem> get items => columnData.items;
void debugPrint() {
String msg = '[$BoardColumnDataDataSource] $columnData data: ';
for (var element in items) {
msg = '$msg$element,';
}
Log.debug(msg);
}
}
/// [BoardColumnWidget] represents the column of the Board.
///
class BoardColumnWidget extends StatefulWidget {
final BoardColumnDataController dataController;
final BoardColumnDataDataSource dataSource;
final ScrollController? scrollController;
final ReorderFlexConfig config;
@ -33,9 +71,7 @@ class BoardColumnWidget extends StatefulWidget {
final BoardPhantomController phantomController;
String get columnId => dataController.identifier;
final List<String> acceptedColumns;
String get columnId => dataSource.columnData.id;
final BoardColumnCardBuilder cardBuilder;
@ -43,23 +79,20 @@ class BoardColumnWidget extends StatefulWidget {
final BoardColumnFooterBuilder? footBuilder;
final double? spacing;
const BoardColumnWidget({
BoardColumnWidget({
Key? key,
this.headerBuilder,
this.footBuilder,
required this.cardBuilder,
required this.onReorder,
required this.dataController,
required this.dataSource,
required this.phantomController,
required this.acceptedColumns,
this.config = const ReorderFlexConfig(),
this.spacing,
this.onDragStarted,
this.scrollController,
this.onDragEnded,
}) : super(key: key);
double? spacing,
}) : config = ReorderFlexConfig(spacing: spacing),
super(key: key);
@override
State<BoardColumnWidget> createState() => _BoardColumnWidgetState();
@ -75,20 +108,20 @@ class _BoardColumnWidgetState extends State<BoardColumnWidget> {
void initState() {
_overlayEntry = BoardOverlayEntry(
builder: (BuildContext context) {
final children = widget.dataController.items
final children = widget.dataSource.columnData.items
.map((item) => _buildWidget(context, item))
.toList();
final header = widget.headerBuilder
?.call(context, widget.dataController.columnData);
final header =
widget.headerBuilder?.call(context, widget.dataSource.columnData);
final footer =
widget.footBuilder?.call(context, widget.dataController.columnData);
widget.footBuilder?.call(context, widget.dataSource.columnData);
final interceptor = CrossReorderFlexDragTargetInterceptor(
reorderFlexId: widget.columnId,
delegate: widget.phantomController,
acceptedReorderFlexIds: widget.acceptedColumns,
acceptedReorderFlexIds: widget.dataSource.acceptedColumnIds,
draggableTargetBuilder: PhantomDraggableBuilder(),
);
@ -109,11 +142,10 @@ class _BoardColumnWidgetState extends State<BoardColumnWidget> {
onDragEnded: () {
widget.phantomController.columnEndDragging(widget.columnId);
widget.onDragEnded?.call(widget.columnId);
widget.dataController.debugPrintItems();
widget.dataSource.debugPrint();
},
dataSource: widget.dataController,
dataSource: widget.dataSource,
interceptor: interceptor,
spacing: widget.spacing,
children: children,
);

View File

@ -9,36 +9,18 @@ abstract class ColumnItem extends ReoderFlexItem {
bool get isPhantom => false;
@override
String toString() {
if (isPhantom) {
return 'phantom:$id';
} else {
return id;
}
}
String toString() => id;
}
class BoardColumnData extends ReoderFlexItem with EquatableMixin {
@override
final String id;
final List<ColumnItem> _items;
BoardColumnData({
required this.id,
required List<ColumnItem> items,
}) : _items = items;
@override
List<Object?> get props => [id, ..._items];
@override
String toString() {
return 'Column$id';
}
}
class BoardColumnDataController extends ChangeNotifier
with EquatableMixin, ReoderFlextDataSource {
/// [BoardColumnDataController] is used to handle the [BoardColumnData].
/// * 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 BoardColumnDataController extends ChangeNotifier with EquatableMixin {
final BoardColumnData columnData;
BoardColumnDataController({
@ -48,7 +30,18 @@ class BoardColumnDataController extends ChangeNotifier
@override
List<Object?> get props => columnData.props;
/// Returns the readonly List<ColumnItem>
UnmodifiableListView<ColumnItem> get items =>
UnmodifiableListView(columnData.items);
/// 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.
///
ColumnItem removeAt(int index, {bool notify = true}) {
assert(index >= 0);
Log.debug('[$BoardColumnDataController] $columnData remove item at $index');
final item = columnData._items.removeAt(index);
if (notify) {
@ -57,7 +50,16 @@ class BoardColumnDataController extends ChangeNotifier
return item;
}
int removeWhere(bool Function(ColumnItem) condition) {
return items.indexWhere(condition);
}
/// Move the item from [fromIndex] to [toIndex]. It will do nothing if the
/// [fromIndex] equal to the [toIndex].
void move(int fromIndex, int toIndex) {
assert(fromIndex >= 0);
assert(toIndex >= 0);
if (fromIndex == toIndex) {
return;
}
@ -68,7 +70,12 @@ class BoardColumnDataController extends ChangeNotifier
notifyListeners();
}
/// Insert an item to [index] and notify the listen if the value of [notify]
/// is true.
///
/// The default value of [notify] is true.
void insert(int index, ColumnItem item, {bool notify = true}) {
assert(index >= 0);
Log.debug(
'[$BoardColumnDataController] $columnData insert $item at $index');
@ -83,26 +90,35 @@ class BoardColumnDataController extends ChangeNotifier
}
}
void replace(int index, ColumnItem item) {
/// Replace the item at index with the [newItem].
void replace(int index, ColumnItem newItem) {
final removedItem = columnData._items.removeAt(index);
columnData._items.insert(index, item);
columnData._items.insert(index, newItem);
Log.debug(
'[$BoardColumnDataController] $columnData replace $removedItem with $item at $index');
'[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index');
notifyListeners();
}
void debugPrintItems() {
String msg = '[$BoardColumnDataController] $columnData data: ';
for (var element in items) {
msg = '$msg$element,';
}
Log.debug(msg);
}
@override
List<ColumnItem> get items => UnmodifiableListView(columnData._items);
@override
String get identifier => columnData.id;
}
/// [BoardColumnData] represents the data of each Column of the Board.
class BoardColumnData extends ReoderFlexItem with EquatableMixin {
@override
final String id;
final List<ColumnItem> _items;
BoardColumnData({
required this.id,
required List<ColumnItem> items,
}) : _items = items;
/// Returns the readonly List<ColumnItem>
UnmodifiableListView<ColumnItem> get items => UnmodifiableListView(_items);
@override
List<Object?> get props => [id, ..._items];
@override
String toString() {
return 'Column$id';
}
}

View File

@ -3,6 +3,7 @@ import 'dart:collection';
import 'package:equatable/equatable.dart';
import '../../flowy_board.dart';
import '../utils/log.dart';
import 'flex/reorder_flex.dart';
import 'package:flutter/material.dart';
import 'phantom/phantom_controller.dart';
@ -31,12 +32,35 @@ class BoardDataController extends ChangeNotifier
return _columnControllers[columnId]!;
}
void onReorder(int fromIndex, int toIndex) {
void moveColumn(int fromIndex, int toIndex) {
final columnData = _columnDatas.removeAt(fromIndex);
_columnDatas.insert(toIndex, columnData);
notifyListeners();
}
void moveColumnItem(String columnId, int fromIndex, int toIndex) {
final columnController = _columnControllers[columnId];
assert(columnController != null);
if (columnController != null) {
columnController.move(fromIndex, toIndex);
}
}
@override
void swapColumnItem(
String fromColumnId,
int fromColumnIndex,
String toColumnId,
int toColumnIndex,
) {
final item = columnController(fromColumnId).removeAt(fromColumnIndex);
assert(
columnController(toColumnId).items[toColumnIndex] is PhantomColumnItem);
columnController(toColumnId).replace(toColumnIndex, item);
}
@override
List<Object?> get props {
return [_columnDatas];
@ -51,5 +75,42 @@ class BoardDataController extends ChangeNotifier
String get identifier => '$BoardDataController';
@override
List<ReoderFlexItem> get items => _columnDatas;
UnmodifiableListView<ReoderFlexItem> get items =>
UnmodifiableListView(_columnDatas);
@override
bool removePhantom(String columnId) {
final columnController = this.columnController(columnId);
final index = columnController.items.indexWhere((item) => item.isPhantom);
final isExist = index != -1;
if (isExist) {
columnController.removeAt(index);
Log.debug(
'[$BoardPhantomController] Column$columnId remove phantom, current count: ${columnController.items.length}');
}
return isExist;
}
@override
void updatePhantom(String columnId, int newIndex) {
final columnDataController = columnController(columnId);
final index =
columnDataController.items.indexWhere((item) => item.isPhantom);
assert(index != -1);
if (index != -1) {
if (index != newIndex) {
// Log.debug('[$BoardPhantomController] update $toColumnId:$index to $toColumnId:$phantomIndex');
final item = columnDataController.removeAt(index, notify: false);
columnDataController.insert(newIndex, item, notify: false);
}
}
}
@override
void insertPhantom(String columnId, int index, PhantomColumnItem item) {
columnController(columnId).insert(index, item);
}
}

View File

@ -77,7 +77,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
class _ReorderDragTargetState<T extends DragTargetData>
extends State<ReorderDragTarget<T>> {
/// Return the dragTarget's size
/// Returns the dragTarget's size
Size? _draggingFeedbackSize = Size.zero;
@override

View File

@ -1,3 +1,4 @@
import 'dart:collection';
import 'dart:math';
import 'package:flutter/material.dart';
@ -18,10 +19,11 @@ typedef OnReveivePassedInPhantom = void Function(
abstract class ReoderFlextDataSource {
String get identifier;
List<ReoderFlexItem> get items;
UnmodifiableListView<ReoderFlexItem> get items;
}
abstract class ReoderFlexItem {
/// [id] is used to identify the item
String get id;
}
@ -30,7 +32,9 @@ class ReorderFlexConfig {
final double draggingWidgetOpacity = 0.2;
final Duration reorderAnimationDuration = const Duration(milliseconds: 250);
final Duration scrollAnimationDuration = const Duration(milliseconds: 250);
const ReorderFlexConfig();
final double? spacing;
const ReorderFlexConfig({this.spacing});
}
class ReorderFlex extends StatefulWidget {
@ -38,7 +42,6 @@ class ReorderFlex extends StatefulWidget {
final List<Widget> children;
final EdgeInsets? padding;
final double? spacing;
final Axis direction;
final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start;
final ScrollController? scrollController;
@ -62,7 +65,6 @@ class ReorderFlex extends StatefulWidget {
this.onDragEnded,
this.interceptor,
this.padding,
this.spacing,
this.direction = Axis.vertical,
}) : super(key: key);
@ -132,8 +134,8 @@ class ReorderFlexState extends State<ReorderFlex>
for (int i = 0; i < widget.children.length; i += 1) {
Widget child = widget.children[i];
if (widget.spacing != null) {
children.add(SizedBox(width: widget.spacing!));
if (widget.config.spacing != null) {
children.add(SizedBox(width: widget.config.spacing!));
}
final wrapChild = _wrap(child, i);
@ -203,7 +205,7 @@ class ReorderFlexState extends State<ReorderFlex>
dragSpace = SizedBox.fromSize(size: dragState.dropAreaSize);
}
/// Return 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.
///
if (dragState.isNotDragging()) {

View File

@ -8,24 +8,39 @@ import 'phantom_state.dart';
abstract class BoardPhantomControllerDelegate {
BoardColumnDataController? controller(String columnId);
}
mixin ColumnDataPhantomMixim {
BoardColumnDataController? get;
bool removePhantom(String columnId);
/// Insert the phantom into column
///
/// * [toColumnId] id of the column
/// * [phantomIndex] the phantom occupies index
void insertPhantom(
String columnId,
int index,
PhantomColumnItem 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);
void swapColumnItem(
String fromColumnId,
int fromColumnIndex,
String toColumnId,
int toColumnIndex,
);
}
class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate
with CrossReorderFlexDragTargetDelegate {
final BoardPhantomControllerDelegate delegate;
PhantomRecord? phantomRecord;
final BoardPhantomControllerDelegate delegate;
final columnsState = ColumnPhantomStateController();
BoardPhantomController({required this.delegate});
bool get hasPhantom => phantomRecord != null;
bool isFromColumn(String columnId) {
if (phantomRecord != null) {
return phantomRecord!.fromColumnId == columnId;
@ -64,67 +79,25 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate
if (columnsState.isDragging(phantomRecord!.fromColumnId) == false) {
return;
}
final item = delegate
.controller(phantomRecord!.fromColumnId)
?.removeAt(phantomRecord!.fromColumnIndex);
assert(item != null);
assert(delegate
.controller(phantomRecord!.toColumnId)
?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem);
delegate
.controller(phantomRecord!.toColumnId)
?.replace(phantomRecord!.toColumnIndex, item!);
delegate.swapColumnItem(
phantomRecord!.fromColumnId,
phantomRecord!.fromColumnIndex,
phantomRecord!.toColumnId,
phantomRecord!.toColumnIndex,
);
Log.debug("[$BoardPhantomController] did move ${phantomRecord.toString()}");
phantomRecord = null;
}
/// Update the column's phantom index if it exists.
/// [toColumnId] the id of the column
/// [dragTargetIndex] the index of the dragTarget
void _updatePhantom(
String toColumnId,
int dragTargetIndex,
) {
final columnDataController = delegate.controller(toColumnId);
final index =
columnDataController?.items.indexWhere((item) => item.isPhantom);
if (index == null) return;
assert(index != -1);
if (index != -1) {
if (index != dragTargetIndex) {
// Log.debug('[$BoardPhantomController] update $toColumnId:$index to $toColumnId:$phantomIndex');
final item = columnDataController!.removeAt(index, notify: false);
columnDataController.insert(dragTargetIndex, item, notify: false);
}
}
}
/// Remove the phantom in the column if it contains phantom
void _removePhantom(String columnId) {
final index = delegate
.controller(columnId)
?.items
.indexWhere((item) => item.isPhantom);
if (index == null) return;
assert(index != -1);
if (index != -1) {
delegate.controller(columnId)?.removeAt(index);
Log.debug(
'[$BoardPhantomController] Column$columnId remove phantom, current count: ${delegate.controller(columnId)?.items.length}');
if (delegate.removePhantom(columnId)) {
columnsState.notifyDidRemovePhantom(columnId);
columnsState.removeColumnListener(columnId);
}
}
/// Insert the phantom into column
///
/// * [toColumnId] id of the column
/// * [phantomIndex] the phantom occupies index
void _insertPhantom(
String toColumnId,
FlexDragTargetData dragTargetData,
@ -135,9 +108,12 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate
dragTargetData: dragTargetData,
);
columnsState.addColumnListener(toColumnId, phantomContext);
delegate
.controller(toColumnId)
?.insert(phantomIndex, PhantomColumnItem(phantomContext));
delegate.insertPhantom(
toColumnId,
phantomIndex,
PhantomColumnItem(phantomContext),
);
WidgetsBinding.instance.addPostFrameCallback((_) {
Future.delayed(const Duration(milliseconds: 100), () {
@ -204,7 +180,7 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate
assert(phantomRecord != null);
if (phantomRecord!.toColumnId == reorderFlexId) {
/// Update the existing phantom index
_updatePhantom(phantomRecord!.toColumnId, dragTargetIndex);
delegate.updatePhantom(phantomRecord!.toColumnId, dragTargetIndex);
}
}
@ -283,6 +259,11 @@ class PhantomColumnItem extends ColumnItem {
Widget get draggingWidget => phantomContext.draggingWidget == null
? const SizedBox()
: phantomContext.draggingWidget!;
@override
String toString() {
return 'phantom:$id';
}
}
class PassthroughPhantomContext extends FakeDragTargetEventTrigger