chore: scroll to bottom after post frame

This commit is contained in:
appflowy 2022-08-27 09:52:08 +08:00
parent d6162159aa
commit 82c0006868
5 changed files with 164 additions and 77 deletions

View File

@ -72,16 +72,19 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
createRow: (groupId) async {
final result = await _gridDataController.createBoardCard(groupId);
result.fold(
(rowPB) {
emit(state.copyWith(editingRow: some(rowPB)));
},
(_) {},
(err) => Log.error(err),
);
},
didCreateRow: (String groupId, RowPB row) {
emit(state.copyWith(
editingRow: Some(BoardEditingRow(columnId: groupId, row: row)),
));
},
endEditRow: (rowId) {
assert(state.editingRow.isSome());
state.editingRow.fold(() => null, (row) {
assert(row.id == rowId);
state.editingRow.fold(() => null, (editingRow) {
assert(editingRow.row.id == rowId);
emit(state.copyWith(editingRow: none()));
});
},
@ -137,7 +140,12 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
void initializeGroups(List<GroupPB> groups) {
for (final group in groups) {
final delegate = GroupControllerDelegateImpl(boardController);
final delegate = GroupControllerDelegateImpl(
controller: boardController,
didAddColumnItem: (groupId, row) {
add(BoardEvent.didCreateRow(groupId, row));
},
);
final controller = GroupController(
gridId: state.gridId,
group: group,
@ -222,8 +230,10 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
@freezed
class BoardEvent with _$BoardEvent {
const factory BoardEvent.initial() = InitialGrid;
const factory BoardEvent.initial() = InitialBrid;
const factory BoardEvent.createRow(String groupId) = _CreateRow;
const factory BoardEvent.didCreateRow(String groupId, RowPB row) =
_DidCreateRow;
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
const factory BoardEvent.didReceiveGridUpdate(
@ -239,7 +249,7 @@ class BoardState with _$BoardState {
required String gridId,
required Option<GridPB> grid,
required List<String> groupIds,
required Option<RowPB> editingRow,
required Option<BoardEditingRow> editingRow,
required GridLoadingState loadingState,
required Option<FlowyError> noneOrError,
}) = _BoardState;
@ -303,16 +313,17 @@ class BoardColumnItem extends AFColumnItem {
class GroupControllerDelegateImpl extends GroupControllerDelegate {
final AFBoardDataController controller;
final void Function(String, RowPB) didAddColumnItem;
GroupControllerDelegateImpl(this.controller);
GroupControllerDelegateImpl({
required this.controller,
required this.didAddColumnItem,
});
@override
void insertRow(GroupPB group, RowPB row, int? index) {
if (index != null) {
final item = BoardColumnItem(
row: row,
fieldId: group.fieldId,
);
final item = BoardColumnItem(row: row, fieldId: group.fieldId);
controller.insertColumnItem(group.groupId, index, item);
} else {
final item = BoardColumnItem(
@ -321,6 +332,7 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
requestFocus: true,
);
controller.addColumnItem(group.groupId, item);
didAddColumnItem(group.groupId, row);
}
}
@ -332,10 +344,21 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
@override
void updateRow(GroupPB group, RowPB row) {
controller.updateColumnItem(
group.groupId,
BoardColumnItem(
row: row,
fieldId: group.fieldId,
));
group.groupId,
BoardColumnItem(
row: row,
fieldId: group.fieldId,
),
);
}
}
class BoardEditingRow {
String columnId;
RowPB row;
BoardEditingRow({
required this.columnId,
required this.row,
});
}

View File

@ -60,6 +60,8 @@ class BoardContent extends StatefulWidget {
class _BoardContentState extends State<BoardContent> {
late ScrollController scrollController;
late AFBoardScrollManager scrollManager;
final config = AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
);
@ -67,37 +69,55 @@ class _BoardContentState extends State<BoardContent> {
@override
void initState() {
scrollController = ScrollController();
scrollManager = AFBoardScrollManager();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<BoardBloc, BoardState>(
buildWhen: (previous, current) =>
previous.groupIds.length != current.groupIds.length,
builder: (context, state) {
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: AFBoard(
scrollController: scrollController,
dataController: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader,
footBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context,
column,
columnItem,
),
columnConstraints: const BoxConstraints.tightFor(width: 240),
config: AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
),
),
),
return BlocListener<BoardBloc, BoardState>(
listener: (context, state) {
state.editingRow.fold(
() => null,
(editingRow) {
WidgetsBinding.instance.addPostFrameCallback((_) {
scrollManager.scrollToBottom(editingRow.columnId, () {
context
.read<BoardBloc>()
.add(BoardEvent.endEditRow(editingRow.row.id));
});
});
},
);
},
child: BlocBuilder<BoardBloc, BoardState>(
buildWhen: (previous, current) =>
previous.groupIds.length != current.groupIds.length,
builder: (context, state) {
return Container(
color: Colors.white,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 20),
child: AFBoard(
scrollManager: scrollManager,
scrollController: scrollController,
dataController: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader,
footBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context,
column,
columnItem,
),
columnConstraints: const BoxConstraints.tightFor(width: 240),
config: AFBoardConfig(
columnBackgroundColor: HexColor.fromHex('#F7F8FC'),
),
),
),
);
},
),
);
}
@ -178,7 +198,7 @@ class _BoardContentState extends State<BoardContent> {
final cellBuilder = BoardCellBuilder(cardController);
final isEditing = context.read<BoardBloc>().state.editingRow.fold(
() => false,
(editingRow) => editingRow.id == rowPB.id,
(editingRow) => editingRow.row.id == rowPB.id,
);
return AppFlowyColumnItemCard(

View File

@ -10,6 +10,16 @@ import 'reorder_flex/reorder_flex.dart';
import 'reorder_phantom/phantom_controller.dart';
import '../rendering/board_overlay.dart';
class AFBoardScrollManager {
BoardColumnState? _columnState;
// AFBoardScrollManager();
void scrollToBottom(String columnId, VoidCallback? completed) {
_columnState?.reorderFlexStateAtColumn(columnId)?.scrollToBottom(completed);
}
}
class AFBoardConfig {
final double cornerRadius;
final EdgeInsets columnPadding;
@ -58,6 +68,10 @@ class AFBoard extends StatelessWidget {
final AFBoardConfig config;
final AFBoardScrollManager? scrollManager;
final BoardColumnState _columnState = BoardColumnState();
AFBoard({
required this.dataController,
required this.cardBuilder,
@ -65,6 +79,7 @@ class AFBoard extends StatelessWidget {
this.footBuilder,
this.headerBuilder,
this.scrollController,
this.scrollManager,
this.columnConstraints = const BoxConstraints(maxWidth: 200),
this.config = const AFBoardConfig(),
Key? key,
@ -77,10 +92,16 @@ class AFBoard extends StatelessWidget {
value: dataController,
child: Consumer<AFBoardDataController>(
builder: (context, notifier, child) {
if (scrollManager != null) {
scrollManager!._columnState = _columnState;
}
return AFBoardContent(
config: config,
dataController: dataController,
scrollController: scrollController,
scrollManager: scrollManager,
columnState: _columnState,
background: background,
delegate: phantomController,
columnConstraints: columnConstraints,
@ -106,6 +127,8 @@ class AFBoardContent extends StatefulWidget {
final AFBoardConfig config;
final ReorderFlexConfig reorderFlexConfig;
final BoxConstraints columnConstraints;
final AFBoardScrollManager? scrollManager;
final BoardColumnState columnState;
///
final AFBoardColumnCardBuilder cardBuilder;
@ -125,6 +148,8 @@ class AFBoardContent extends StatefulWidget {
required this.onReorder,
required this.delegate,
required this.dataController,
required this.scrollManager,
required this.columnState,
this.onDragStarted,
this.onDragEnded,
this.scrollController,
@ -143,22 +168,19 @@ class AFBoardContent extends StatefulWidget {
}
class _AFBoardContentState extends State<AFBoardContent> {
late _BoardColumnState columnState;
final GlobalKey _columnContainerOverlayKey =
final GlobalKey _boardContentKey =
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),
columnKeys: UnmodifiableMapView(widget.columnState.columnKeys),
);
final reorderFlex = ReorderFlex(
@ -198,7 +220,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
@override
Widget build(BuildContext context) {
return BoardOverlay(
key: _columnContainerOverlayKey,
key: _boardContentKey,
initialEntries: [_overlayEntry],
);
}
@ -220,6 +242,8 @@ class _AFBoardContentState extends State<AFBoardContent> {
value: widget.dataController.getColumnController(columnData.id),
child: Consumer<AFBoardColumnDataController>(
builder: (context, value, child) {
final scrollController =
widget.columnState.scrollController(columnData.id);
final boardColumn = AFBoardColumnWidget(
key: ValueKey(columnData.id),
margin: _marginFromIndex(columnIndex),
@ -228,14 +252,17 @@ class _AFBoardContentState extends State<AFBoardContent> {
footBuilder: widget.footBuilder,
cardBuilder: widget.cardBuilder,
dataSource: dataSource,
scrollController: columnState.scrollController(columnData.id),
scrollController: scrollController,
phantomController: widget.phantomController,
onReorder: widget.dataController.moveColumnItem,
cornerRadius: widget.config.cornerRadius,
backgroundColor: widget.config.columnBackgroundColor,
);
columnState.cacheColumn(columnData.id, boardColumn.globalKey);
widget.columnState.addColumn(
columnData.id,
boardColumn.globalKey,
);
return ConstrainedBox(
constraints: widget.columnConstraints,
@ -297,25 +324,36 @@ class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
List<String> get acceptedColumnIds => dataController.columnIds;
}
class _BoardColumnState {
class BoardColumnState {
/// Quick access to the [AFBoardColumnWidget]
final Map<String, GlobalKey> columnKeys = {};
void cacheColumn(String columnId, GlobalKey key) {
/// Records the position of the [AFBoardColumnWidget]
final Map<String, ScrollPosition> columnScrollPositions = {};
void addColumn(String columnId, GlobalKey key) {
columnKeys[columnId] = key;
}
ScrollController scrollController(String columnId) {
ReorderFlexState? reorderFlexStateAtColumn(String columnId) {
final flexGlobalKey = columnKeys[columnId];
var scrollController = ScrollController();
if (flexGlobalKey != null) {
// assert(flexGlobalKey.currentWidget is ReorderFlex);
if (flexGlobalKey == null) return null;
if (flexGlobalKey.currentState is! ReorderFlexState) return null;
final state = flexGlobalKey.currentState as ReorderFlexState;
return state;
}
ReorderFlex? reorderFlexAtColumn(String columnId) {
final flexGlobalKey = columnKeys[columnId];
if (flexGlobalKey == null) return null;
if (flexGlobalKey.currentWidget is! ReorderFlex) return null;
final widget = flexGlobalKey.currentWidget as ReorderFlex;
return widget;
}
ScrollController scrollController(String columnId) {
ScrollController scrollController = ScrollController();
// if (flexGlobalKey.currentWidget is ReorderFlex) {
// final reorderFlex = flexGlobalKey.currentWidget as ReorderFlex;
// final offset = reorderFlex.scrollController!.offset;
// scrollController = ScrollController(initialScrollOffset: offset);
// }
}
return scrollController;
}
}

View File

@ -116,13 +116,10 @@ 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
@ -143,6 +140,7 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
);
Widget reorderFlex = ReorderFlex(
key: widget.globalKey,
scrollController: widget.scrollController,
config: widget.config,
onDragStarted: (index) {
@ -165,8 +163,6 @@ class _AFBoardColumnWidgetState extends State<AFBoardColumnWidget> {
children: children,
);
reorderFlex = KeyedSubtree(key: _indexGlobalKey, child: reorderFlex);
return Container(
margin: widget.margin,
clipBehavior: Clip.hardEdge,

View File

@ -177,9 +177,6 @@ class ReorderFlexState extends State<ReorderFlex>
// ));
// }
}
Future.delayed(Duration(seconds: 3), () {
scrollToBottom();
});
final child = _wrapContainer(children);
return _wrapScrollView(child: child);
@ -532,17 +529,25 @@ class ReorderFlexState extends State<ReorderFlex>
}
}
void scrollToBottom() {
if (_scrolling) return;
void scrollToBottom(VoidCallback? completed) {
if (_scrolling) {
completed?.call();
return;
}
if (widget.dataSource.items.isNotEmpty) {
final item = widget.dataSource.items.last;
final indexKey = _childKeys[item.id];
if (indexKey == null) return;
if (indexKey == null) {
completed?.call();
return;
}
final indexContext = indexKey.currentContext;
if (indexContext == null) return;
if (_scrollController.hasClients == false) return;
if (indexContext == null || _scrollController.hasClients == false) {
completed?.call();
return;
}
final renderObject = indexContext.findRenderObject();
if (renderObject != null) {
@ -554,8 +559,13 @@ class ReorderFlexState extends State<ReorderFlex>
duration: const Duration(milliseconds: 120),
)
.then((value) {
setState(() => _scrolling = false);
setState(() {
_scrolling = false;
completed?.call();
});
});
} else {
completed?.call();
}
}
}