chore: scroll to bottom when create new card

This commit is contained in:
appflowy 2022-08-26 21:42:59 +08:00
parent aba0f946dd
commit d6162159aa
6 changed files with 122 additions and 44 deletions

View File

@ -198,7 +198,10 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
List<AFColumnItem> _buildRows(GroupPB group) {
final items = group.rows.map((row) {
return BoardColumnItem(row: row, fieldId: group.fieldId);
return BoardColumnItem(
row: row,
fieldId: group.fieldId,
);
}).toList();
return <AFColumnItem>[...items];
@ -286,17 +289,18 @@ class BoardColumnItem extends AFColumnItem {
final String fieldId;
BoardColumnItem({required this.row, required this.fieldId});
final bool requestFocus;
BoardColumnItem({
required this.row,
required this.fieldId,
this.requestFocus = false,
});
@override
String get id => row.id;
}
class CreateCardItem extends AFColumnItem {
@override
String get id => '$CreateCardItem';
}
class GroupControllerDelegateImpl extends GroupControllerDelegate {
final AFBoardDataController controller;
@ -304,10 +308,18 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
@override
void insertRow(GroupPB group, RowPB row, int? index) {
final item = BoardColumnItem(row: row, fieldId: group.fieldId);
if (index != null) {
final item = BoardColumnItem(
row: row,
fieldId: group.fieldId,
);
controller.insertColumnItem(group.groupId, index, item);
} else {
final item = BoardColumnItem(
row: row,
fieldId: group.fieldId,
requestFocus: true,
);
controller.addColumnItem(group.groupId, item);
}
}

View File

@ -1,3 +1,5 @@
import 'dart:collection';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'board_column/board_column.dart';
@ -141,18 +143,22 @@ class AFBoardContent extends StatefulWidget {
}
class _AFBoardContentState extends State<AFBoardContent> {
late _BoardColumnState columnState;
final GlobalKey _columnContainerOverlayKey =
GlobalKey(debugLabel: '$AFBoardContent overlay key');
late BoardOverlayEntry _overlayEntry;
@override
void initState() {
columnState = _BoardColumnState();
_overlayEntry = BoardOverlayEntry(
builder: (BuildContext context) {
final interceptor = OverlappingDragTargetInterceptor(
reorderFlexId: widget.dataController.identifier,
acceptedReorderFlexId: widget.dataController.columnIds,
delegate: widget.delegate,
columnKeys: UnmodifiableMapView(columnState.columnKeys),
);
final reorderFlex = ReorderFlex(
@ -165,7 +171,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
dataSource: widget.dataController,
direction: Axis.horizontal,
interceptor: interceptor,
children: _buildColumns(interceptor.columnKeys),
children: _buildColumns(),
);
return Stack(
@ -197,7 +203,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
);
}
List<Widget> _buildColumns(List<ColumnKey> columnKeys) {
List<Widget> _buildColumns() {
final List<Widget> children =
widget.dataController.columnDatas.asMap().entries.map(
(item) {
@ -222,21 +228,14 @@ class _AFBoardContentState extends State<AFBoardContent> {
footBuilder: widget.footBuilder,
cardBuilder: widget.cardBuilder,
dataSource: dataSource,
scrollController: ScrollController(),
scrollController: columnState.scrollController(columnData.id),
phantomController: widget.phantomController,
onReorder: widget.dataController.moveColumnItem,
cornerRadius: widget.config.cornerRadius,
backgroundColor: widget.config.columnBackgroundColor,
);
// columnKeys
// .removeWhere((element) => element.columnId == columnData.id);
// columnKeys.add(
// ColumnKey(
// columnId: columnData.id,
// key: boardColumn.columnGlobalKey,
// ),
// );
columnState.cacheColumn(columnData.id, boardColumn.globalKey);
return ConstrainedBox(
constraints: widget.columnConstraints,
@ -297,3 +296,26 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
@override
List<String> get acceptedColumnIds => dataController.columnIds;
}
class _BoardColumnState {
final Map<String, GlobalKey> columnKeys = {};
void cacheColumn(String columnId, GlobalKey key) {
columnKeys[columnId] = key;
}
ScrollController scrollController(String columnId) {
final flexGlobalKey = columnKeys[columnId];
var scrollController = ScrollController();
if (flexGlobalKey != null) {
// assert(flexGlobalKey.currentWidget is ReorderFlex);
// if (flexGlobalKey.currentWidget is ReorderFlex) {
// final reorderFlex = flexGlobalKey.currentWidget as ReorderFlex;
// final offset = reorderFlex.scrollController!.offset;
// scrollController = ScrollController(initialScrollOffset: offset);
// }
}
return scrollController;
}
}

View File

@ -88,7 +88,9 @@ class AFBoardColumnWidget extends StatefulWidget {
final Color backgroundColor;
const AFBoardColumnWidget({
final GlobalKey globalKey = GlobalKey();
AFBoardColumnWidget({
Key? key,
this.headerBuilder,
this.footBuilder,
@ -114,10 +116,13 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
final GlobalKey _columnOverlayKey =
GlobalKey(debugLabel: '$AFBoardColumnWidget overlay key');
late GlobalObjectKey _indexGlobalKey;
late BoardOverlayEntry _overlayEntry;
@override
void initState() {
_indexGlobalKey = GlobalObjectKey(widget.key!);
_overlayEntry = BoardOverlayEntry(
builder: (BuildContext context) {
final children = widget.dataSource.columnData.items
@ -138,7 +143,6 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
);
Widget reorderFlex = ReorderFlex(
key: widget.key,
scrollController: widget.scrollController,
config: widget.config,
onDragStarted: (index) {
@ -161,6 +165,8 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
children: children,
);
reorderFlex = KeyedSubtree(key: _indexGlobalKey, child: reorderFlex);
return Container(
margin: widget.margin,
clipBehavior: Clip.hardEdge,
@ -172,10 +178,7 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
children: [
if (header != null) header,
Expanded(
child: Padding(
padding: widget.itemMargin,
child: reorderFlex,
),
child: Padding(padding: widget.itemMargin, child: reorderFlex),
),
if (footer != null) footer,
],

View File

@ -39,7 +39,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
final Widget child;
final T dragTargetData;
final GlobalObjectKey _indexGlobalKey;
final GlobalObjectKey indexGlobalKey;
/// Called when dragTarget is being dragging.
final DragTargetOnStarted onDragStarted;
@ -69,9 +69,10 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
final bool useMoveAnimation;
ReorderDragTarget({
const ReorderDragTarget({
Key? key,
required this.child,
required this.indexGlobalKey,
required this.dragTargetData,
required this.onDragStarted,
required this.onDragEnded,
@ -82,8 +83,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
this.onAccept,
this.onLeave,
this.draggableTargetBuilder,
}) : _indexGlobalKey = GlobalObjectKey(child.key!),
super(key: key);
}) : super(key: key);
@override
State<ReorderDragTarget<T>> createState() => _ReorderDragTargetState<T>();
@ -112,7 +112,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
},
);
dragTarget = KeyedSubtree(key: widget._indexGlobalKey, child: dragTarget);
dragTarget = KeyedSubtree(key: widget.indexGlobalKey, child: dragTarget);
return dragTarget;
}
@ -150,7 +150,7 @@ class _ReorderDragTargetState<T extends DragTargetData>
child: widget.child,
),
onDragStarted: () {
_draggingFeedbackSize = widget._indexGlobalKey.currentContext?.size;
_draggingFeedbackSize = widget.indexGlobalKey.currentContext?.size;
widget.onDragStarted(
widget.child,
widget.dragTargetData.draggingIndex,

View File

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:collection';
import 'package:flutter/material.dart';
@ -55,13 +56,14 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
final String reorderFlexId;
final List<String> acceptedReorderFlexId;
final OverlapDragTargetDelegate delegate;
final List<ColumnKey> columnKeys = [];
final UnmodifiableMapView<String, GlobalKey> columnKeys;
Timer? _delayOperation;
OverlappingDragTargetInterceptor({
required this.delegate,
required this.reorderFlexId,
required this.acceptedReorderFlexId,
required this.columnKeys,
});
@override
@ -105,12 +107,6 @@ class OverlappingDragTargetInterceptor extends DragTargetInterceptor {
}
}
class ColumnKey {
String columnId;
GlobalKey key;
ColumnKey({required this.columnId, required this.key});
}
abstract class CrossReorderFlexDragTargetDelegate {
/// * [reorderFlexId] is the id that the [ReorderFlex] passed in.
bool acceptNewDragTargetData(

View File

@ -74,7 +74,7 @@ class ReorderFlex extends StatefulWidget {
final DragTargetInterceptor? interceptor;
const ReorderFlex({
ReorderFlex({
Key? key,
this.scrollController,
required this.dataSource,
@ -85,7 +85,9 @@ class ReorderFlex extends StatefulWidget {
this.onDragEnded,
this.interceptor,
this.direction = Axis.vertical,
}) : super(key: key);
}) : assert(children.every((Widget w) => w.key != null),
'All child must have a key.'),
super(key: key);
@override
State<ReorderFlex> createState() => ReorderFlexState();
@ -112,10 +114,13 @@ class ReorderFlexState extends State<ReorderFlex>
late ReorderFlexNotifier _notifier;
late Map<String, GlobalObjectKey> _childKeys;
@override
void initState() {
_notifier = ReorderFlexNotifier();
dragState = DraggingState(widget.reorderFlexId);
_childKeys = {};
_animation = DragTargetAnimation(
reorderAnimationDuration: widget.config.reorderAnimationDuration,
@ -159,7 +164,11 @@ class ReorderFlexState extends State<ReorderFlex>
for (int i = 0; i < widget.children.length; i += 1) {
Widget child = widget.children[i];
children.add(_wrap(child, i));
final item = widget.dataSource.items[i];
final indexGlobalKey = GlobalObjectKey(child.key!);
_childKeys[item.id] = indexGlobalKey;
children.add(_wrap(child, i, indexGlobalKey));
// if (widget.config.useMovePlaceholder) {
// children.add(DragTargeMovePlaceholder(
@ -168,6 +177,9 @@ class ReorderFlexState extends State<ReorderFlex>
// ));
// }
}
Future.delayed(Duration(seconds: 3), () {
scrollToBottom();
});
final child = _wrapContainer(children);
return _wrapScrollView(child: child);
@ -203,10 +215,10 @@ class ReorderFlexState extends State<ReorderFlex>
/// [child]: the child will be wrapped with dartTarget
/// [childIndex]: the index of the child in a list
Widget _wrap(Widget child, int childIndex) {
Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexGlobalKey) {
return Builder(builder: (context) {
final ReorderDragTarget dragTarget =
_buildDragTarget(context, child, childIndex);
_buildDragTarget(context, child, childIndex, indexGlobalKey);
int shiftedIndex = childIndex;
if (dragState.isOverlapWithPhantom()) {
@ -312,10 +324,15 @@ class ReorderFlexState extends State<ReorderFlex>
}
ReorderDragTarget _buildDragTarget(
BuildContext builderContext, Widget child, int dragTargetIndex) {
BuildContext builderContext,
Widget child,
int dragTargetIndex,
GlobalObjectKey indexGlobalKey,
) {
final ReoderFlexItem reorderFlexItem =
widget.dataSource.items[dragTargetIndex];
return ReorderDragTarget<FlexDragTargetData>(
indexGlobalKey: indexGlobalKey,
dragTargetData: FlexDragTargetData(
draggingIndex: dragTargetIndex,
reorderFlexId: widget.reorderFlexId,
@ -515,6 +532,34 @@ class ReorderFlexState extends State<ReorderFlex>
}
}
void scrollToBottom() {
if (_scrolling) return;
if (widget.dataSource.items.isNotEmpty) {
final item = widget.dataSource.items.last;
final indexKey = _childKeys[item.id];
if (indexKey == null) return;
final indexContext = indexKey.currentContext;
if (indexContext == null) return;
if (_scrollController.hasClients == false) return;
final renderObject = indexContext.findRenderObject();
if (renderObject != null) {
_scrolling = true;
_scrollController.position
.ensureVisible(
renderObject,
alignment: 0.5,
duration: const Duration(milliseconds: 120),
)
.then((value) {
setState(() => _scrolling = false);
});
}
}
}
// Scrolls to a target context if that context is not on the screen.
void _scrollTo(BuildContext context) {
if (_scrolling) return;