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

View File

@ -1,3 +1,5 @@
import 'dart:collection';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'board_column/board_column.dart'; import 'board_column/board_column.dart';
@ -141,18 +143,22 @@ class AFBoardContent extends StatefulWidget {
} }
class _AFBoardContentState extends State<AFBoardContent> { class _AFBoardContentState extends State<AFBoardContent> {
late _BoardColumnState columnState;
final GlobalKey _columnContainerOverlayKey = final GlobalKey _columnContainerOverlayKey =
GlobalKey(debugLabel: '$AFBoardContent overlay key'); GlobalKey(debugLabel: '$AFBoardContent overlay key');
late BoardOverlayEntry _overlayEntry; late BoardOverlayEntry _overlayEntry;
@override @override
void initState() { void initState() {
columnState = _BoardColumnState();
_overlayEntry = BoardOverlayEntry( _overlayEntry = BoardOverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
final interceptor = OverlappingDragTargetInterceptor( final interceptor = OverlappingDragTargetInterceptor(
reorderFlexId: widget.dataController.identifier, reorderFlexId: widget.dataController.identifier,
acceptedReorderFlexId: widget.dataController.columnIds, acceptedReorderFlexId: widget.dataController.columnIds,
delegate: widget.delegate, delegate: widget.delegate,
columnKeys: UnmodifiableMapView(columnState.columnKeys),
); );
final reorderFlex = ReorderFlex( final reorderFlex = ReorderFlex(
@ -165,7 +171,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
dataSource: widget.dataController, dataSource: widget.dataController,
direction: Axis.horizontal, direction: Axis.horizontal,
interceptor: interceptor, interceptor: interceptor,
children: _buildColumns(interceptor.columnKeys), children: _buildColumns(),
); );
return Stack( return Stack(
@ -197,7 +203,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
); );
} }
List<Widget> _buildColumns(List<ColumnKey> columnKeys) { List<Widget> _buildColumns() {
final List<Widget> children = final List<Widget> children =
widget.dataController.columnDatas.asMap().entries.map( widget.dataController.columnDatas.asMap().entries.map(
(item) { (item) {
@ -222,21 +228,14 @@ class _AFBoardContentState extends State<AFBoardContent> {
footBuilder: widget.footBuilder, footBuilder: widget.footBuilder,
cardBuilder: widget.cardBuilder, cardBuilder: widget.cardBuilder,
dataSource: dataSource, dataSource: dataSource,
scrollController: ScrollController(), scrollController: columnState.scrollController(columnData.id),
phantomController: widget.phantomController, phantomController: widget.phantomController,
onReorder: widget.dataController.moveColumnItem, onReorder: widget.dataController.moveColumnItem,
cornerRadius: widget.config.cornerRadius, cornerRadius: widget.config.cornerRadius,
backgroundColor: widget.config.columnBackgroundColor, backgroundColor: widget.config.columnBackgroundColor,
); );
// columnKeys columnState.cacheColumn(columnData.id, boardColumn.globalKey);
// .removeWhere((element) => element.columnId == columnData.id);
// columnKeys.add(
// ColumnKey(
// columnId: columnData.id,
// key: boardColumn.columnGlobalKey,
// ),
// );
return ConstrainedBox( return ConstrainedBox(
constraints: widget.columnConstraints, constraints: widget.columnConstraints,
@ -297,3 +296,26 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
@override @override
List<String> get acceptedColumnIds => dataController.columnIds; 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; final Color backgroundColor;
const AFBoardColumnWidget({ final GlobalKey globalKey = GlobalKey();
AFBoardColumnWidget({
Key? key, Key? key,
this.headerBuilder, this.headerBuilder,
this.footBuilder, this.footBuilder,
@ -114,10 +116,13 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
final GlobalKey _columnOverlayKey = final GlobalKey _columnOverlayKey =
GlobalKey(debugLabel: '$AFBoardColumnWidget overlay key'); GlobalKey(debugLabel: '$AFBoardColumnWidget overlay key');
late GlobalObjectKey _indexGlobalKey;
late BoardOverlayEntry _overlayEntry; late BoardOverlayEntry _overlayEntry;
@override @override
void initState() { void initState() {
_indexGlobalKey = GlobalObjectKey(widget.key!);
_overlayEntry = BoardOverlayEntry( _overlayEntry = BoardOverlayEntry(
builder: (BuildContext context) { builder: (BuildContext context) {
final children = widget.dataSource.columnData.items final children = widget.dataSource.columnData.items
@ -138,7 +143,6 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
); );
Widget reorderFlex = ReorderFlex( Widget reorderFlex = ReorderFlex(
key: widget.key,
scrollController: widget.scrollController, scrollController: widget.scrollController,
config: widget.config, config: widget.config,
onDragStarted: (index) { onDragStarted: (index) {
@ -161,6 +165,8 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
children: children, children: children,
); );
reorderFlex = KeyedSubtree(key: _indexGlobalKey, child: reorderFlex);
return Container( return Container(
margin: widget.margin, margin: widget.margin,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
@ -172,10 +178,7 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
children: [ children: [
if (header != null) header, if (header != null) header,
Expanded( Expanded(
child: Padding( child: Padding(padding: widget.itemMargin, child: reorderFlex),
padding: widget.itemMargin,
child: reorderFlex,
),
), ),
if (footer != null) footer, if (footer != null) footer,
], ],

View File

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

View File

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

View File

@ -74,7 +74,7 @@ class ReorderFlex extends StatefulWidget {
final DragTargetInterceptor? interceptor; final DragTargetInterceptor? interceptor;
const ReorderFlex({ ReorderFlex({
Key? key, Key? key,
this.scrollController, this.scrollController,
required this.dataSource, required this.dataSource,
@ -85,7 +85,9 @@ class ReorderFlex extends StatefulWidget {
this.onDragEnded, this.onDragEnded,
this.interceptor, this.interceptor,
this.direction = Axis.vertical, 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 @override
State<ReorderFlex> createState() => ReorderFlexState(); State<ReorderFlex> createState() => ReorderFlexState();
@ -112,10 +114,13 @@ class ReorderFlexState extends State<ReorderFlex>
late ReorderFlexNotifier _notifier; late ReorderFlexNotifier _notifier;
late Map<String, GlobalObjectKey> _childKeys;
@override @override
void initState() { void initState() {
_notifier = ReorderFlexNotifier(); _notifier = ReorderFlexNotifier();
dragState = DraggingState(widget.reorderFlexId); dragState = DraggingState(widget.reorderFlexId);
_childKeys = {};
_animation = DragTargetAnimation( _animation = DragTargetAnimation(
reorderAnimationDuration: widget.config.reorderAnimationDuration, reorderAnimationDuration: widget.config.reorderAnimationDuration,
@ -159,7 +164,11 @@ class ReorderFlexState extends State<ReorderFlex>
for (int i = 0; i < widget.children.length; i += 1) { for (int i = 0; i < widget.children.length; i += 1) {
Widget child = widget.children[i]; 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) { // if (widget.config.useMovePlaceholder) {
// children.add(DragTargeMovePlaceholder( // children.add(DragTargeMovePlaceholder(
@ -168,6 +177,9 @@ class ReorderFlexState extends State<ReorderFlex>
// )); // ));
// } // }
} }
Future.delayed(Duration(seconds: 3), () {
scrollToBottom();
});
final child = _wrapContainer(children); final child = _wrapContainer(children);
return _wrapScrollView(child: child); return _wrapScrollView(child: child);
@ -203,10 +215,10 @@ class ReorderFlexState extends State<ReorderFlex>
/// [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) { Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexGlobalKey) {
return Builder(builder: (context) { return Builder(builder: (context) {
final ReorderDragTarget dragTarget = final ReorderDragTarget dragTarget =
_buildDragTarget(context, child, childIndex); _buildDragTarget(context, child, childIndex, indexGlobalKey);
int shiftedIndex = childIndex; int shiftedIndex = childIndex;
if (dragState.isOverlapWithPhantom()) { if (dragState.isOverlapWithPhantom()) {
@ -312,10 +324,15 @@ class ReorderFlexState extends State<ReorderFlex>
} }
ReorderDragTarget _buildDragTarget( ReorderDragTarget _buildDragTarget(
BuildContext builderContext, Widget child, int dragTargetIndex) { BuildContext builderContext,
Widget child,
int dragTargetIndex,
GlobalObjectKey indexGlobalKey,
) {
final ReoderFlexItem reorderFlexItem = final ReoderFlexItem reorderFlexItem =
widget.dataSource.items[dragTargetIndex]; widget.dataSource.items[dragTargetIndex];
return ReorderDragTarget<FlexDragTargetData>( return ReorderDragTarget<FlexDragTargetData>(
indexGlobalKey: indexGlobalKey,
dragTargetData: FlexDragTargetData( dragTargetData: FlexDragTargetData(
draggingIndex: dragTargetIndex, draggingIndex: dragTargetIndex,
reorderFlexId: widget.reorderFlexId, 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. // Scrolls to a target context if that context is not on the screen.
void _scrollTo(BuildContext context) { void _scrollTo(BuildContext context) {
if (_scrolling) return; if (_scrolling) return;