123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- import 'package:appflowy_board/src/utils/log.dart';
- import 'package:flutter/material.dart';
- import 'package:provider/provider.dart';
- import 'board_column/board_column.dart';
- import 'board_column/board_column_data.dart';
- import 'board_data.dart';
- import 'reorder_flex/drag_state.dart';
- import 'reorder_flex/drag_target_interceptor.dart';
- import 'reorder_flex/reorder_flex.dart';
- import 'reorder_phantom/phantom_controller.dart';
- import '../rendering/board_overlay.dart';
- class AFBoardScrollManager {
- BoardColumnsState? _columnState;
- // AFBoardScrollManager();
- void scrollToBottom(String columnId, VoidCallback? completed) {
- _columnState
- ?.getReorderFlexState(columnId: columnId)
- ?.scrollToBottom(completed);
- }
- }
- class AFBoardConfig {
- final double cornerRadius;
- final EdgeInsets columnPadding;
- final EdgeInsets columnItemPadding;
- final EdgeInsets footerPadding;
- final EdgeInsets headerPadding;
- final EdgeInsets cardPadding;
- final Color columnBackgroundColor;
- const AFBoardConfig({
- this.cornerRadius = 6.0,
- this.columnPadding = const EdgeInsets.symmetric(horizontal: 8),
- this.columnItemPadding = const EdgeInsets.symmetric(horizontal: 12),
- this.footerPadding = const EdgeInsets.symmetric(horizontal: 12),
- this.headerPadding = const EdgeInsets.symmetric(horizontal: 16),
- this.cardPadding = const EdgeInsets.symmetric(horizontal: 3, vertical: 4),
- this.columnBackgroundColor = Colors.transparent,
- });
- }
- class AFBoard extends StatelessWidget {
- /// The direction to use as the main axis.
- final Axis direction = Axis.vertical;
- ///
- final Widget? background;
- ///
- final AFBoardColumnCardBuilder cardBuilder;
- ///
- final AFBoardColumnHeaderBuilder? headerBuilder;
- ///
- final AFBoardColumnFooterBuilder? footBuilder;
- ///
- final AFBoardDataController dataController;
- final BoxConstraints columnConstraints;
- ///
- late final BoardPhantomController phantomController;
- final ScrollController? scrollController;
- final AFBoardConfig config;
- final AFBoardScrollManager? scrollManager;
- final BoardColumnsState _columnState = BoardColumnsState();
- AFBoard({
- required this.dataController,
- required this.cardBuilder,
- this.background,
- this.footBuilder,
- this.headerBuilder,
- this.scrollController,
- this.scrollManager,
- this.columnConstraints = const BoxConstraints(maxWidth: 200),
- this.config = const AFBoardConfig(),
- Key? key,
- }) : super(key: key) {
- phantomController = BoardPhantomController(
- delegate: dataController,
- columnsState: _columnState,
- );
- }
- @override
- Widget build(BuildContext context) {
- return ChangeNotifierProvider.value(
- 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,
- columnsState: _columnState,
- background: background,
- delegate: phantomController,
- columnConstraints: columnConstraints,
- cardBuilder: cardBuilder,
- footBuilder: footBuilder,
- headerBuilder: headerBuilder,
- phantomController: phantomController,
- onReorder: dataController.moveColumn,
- );
- },
- ),
- );
- }
- }
- class AFBoardContent extends StatefulWidget {
- final ScrollController? scrollController;
- final OnDragStarted? onDragStarted;
- final OnReorder onReorder;
- final OnDragEnded? onDragEnded;
- final AFBoardDataController dataController;
- final Widget? background;
- final AFBoardConfig config;
- final ReorderFlexConfig reorderFlexConfig;
- final BoxConstraints columnConstraints;
- final AFBoardScrollManager? scrollManager;
- final BoardColumnsState columnsState;
- ///
- final AFBoardColumnCardBuilder cardBuilder;
- ///
- final AFBoardColumnHeaderBuilder? headerBuilder;
- ///
- final AFBoardColumnFooterBuilder? footBuilder;
- final OverlapDragTargetDelegate delegate;
- final BoardPhantomController phantomController;
- const AFBoardContent({
- required this.config,
- required this.onReorder,
- required this.delegate,
- required this.dataController,
- required this.scrollManager,
- required this.columnsState,
- this.onDragStarted,
- this.onDragEnded,
- this.scrollController,
- this.background,
- required this.columnConstraints,
- required this.cardBuilder,
- this.footBuilder,
- this.headerBuilder,
- required this.phantomController,
- Key? key,
- }) : reorderFlexConfig = const ReorderFlexConfig(),
- super(key: key);
- @override
- State<AFBoardContent> createState() => _AFBoardContentState();
- }
- class _AFBoardContentState extends State<AFBoardContent> {
- final GlobalKey _boardContentKey =
- GlobalKey(debugLabel: '$AFBoardContent overlay key');
- late BoardOverlayEntry _overlayEntry;
- @override
- void initState() {
- _overlayEntry = BoardOverlayEntry(
- builder: (BuildContext context) {
- final interceptor = OverlappingDragTargetInterceptor(
- reorderFlexId: widget.dataController.identifier,
- acceptedReorderFlexId: widget.dataController.columnIds,
- delegate: widget.delegate,
- columnsState: widget.columnsState,
- );
- final reorderFlex = ReorderFlex(
- config: widget.reorderFlexConfig,
- scrollController: widget.scrollController,
- onDragStarted: widget.onDragStarted,
- onReorder: widget.onReorder,
- onDragEnded: widget.onDragEnded,
- dataSource: widget.dataController,
- direction: Axis.horizontal,
- interceptor: interceptor,
- reorderable: false,
- children: _buildColumns(),
- );
- return Stack(
- alignment: AlignmentDirectional.topStart,
- children: [
- if (widget.background != null)
- Container(
- clipBehavior: Clip.hardEdge,
- decoration: BoxDecoration(
- borderRadius:
- BorderRadius.circular(widget.config.cornerRadius),
- ),
- child: widget.background,
- ),
- reorderFlex,
- ],
- );
- },
- opaque: false,
- );
- super.initState();
- }
- @override
- Widget build(BuildContext context) {
- return BoardOverlay(
- key: _boardContentKey,
- initialEntries: [_overlayEntry],
- );
- }
- List<Widget> _buildColumns() {
- final List<Widget> children =
- widget.dataController.columnDatas.asMap().entries.map(
- (item) {
- final columnData = item.value;
- final columnIndex = item.key;
- final dataSource = _BoardColumnDataSourceImpl(
- columnId: columnData.id,
- dataController: widget.dataController,
- );
- return ChangeNotifierProvider.value(
- key: ValueKey(columnData.id),
- value: widget.dataController.getColumnController(columnData.id),
- child: Consumer<AFBoardColumnDataController>(
- builder: (context, value, child) {
- final boardColumn = AFBoardColumnWidget(
- // key: PageStorageKey<String>(columnData.id),
- // key: GlobalObjectKey(columnData.id),
- margin: _marginFromIndex(columnIndex),
- itemMargin: widget.config.columnItemPadding,
- headerBuilder: _buildHeader,
- footBuilder: widget.footBuilder,
- cardBuilder: widget.cardBuilder,
- dataSource: dataSource,
- scrollController: ScrollController(),
- phantomController: widget.phantomController,
- onReorder: widget.dataController.moveColumnItem,
- cornerRadius: widget.config.cornerRadius,
- backgroundColor: widget.config.columnBackgroundColor,
- dragStateStorage: widget.columnsState,
- dragTargetIndexKeyStorage: widget.columnsState,
- );
- widget.columnsState.addColumn(columnData.id, boardColumn);
- return ConstrainedBox(
- constraints: widget.columnConstraints,
- child: boardColumn,
- );
- },
- ),
- );
- },
- ).toList();
- return children;
- }
- Widget? _buildHeader(
- BuildContext context, AFBoardColumnHeaderData headerData) {
- if (widget.headerBuilder == null) {
- return null;
- }
- return Selector<AFBoardColumnDataController, AFBoardColumnHeaderData>(
- selector: (context, controller) => controller.columnData.headerData,
- builder: (context, headerData, _) {
- return widget.headerBuilder!(context, headerData)!;
- },
- );
- }
- EdgeInsets _marginFromIndex(int index) {
- if (widget.dataController.columnDatas.isEmpty) {
- return widget.config.columnPadding;
- }
- if (index == 0) {
- return EdgeInsets.only(right: widget.config.columnPadding.right);
- }
- if (index == widget.dataController.columnDatas.length - 1) {
- return EdgeInsets.only(left: widget.config.columnPadding.left);
- }
- return widget.config.columnPadding;
- }
- }
- class _BoardColumnDataSourceImpl extends AFBoardColumnDataDataSource {
- String columnId;
- final AFBoardDataController dataController;
- _BoardColumnDataSourceImpl({
- required this.columnId,
- required this.dataController,
- });
- @override
- AFBoardColumnData get columnData =>
- dataController.getColumnController(columnId)!.columnData;
- @override
- List<String> get acceptedColumnIds => dataController.columnIds;
- }
- class BoardColumnContext {
- GlobalKey? columnKey;
- DraggingState? draggingState;
- }
- class BoardColumnsState extends DraggingStateStorage
- with ReorderDragTargetIndexKeyStorage {
- /// Quick access to the [AFBoardColumnWidget]
- final Map<String, GlobalKey> columnKeys = {};
- final Map<String, DraggingState> columnDragStates = {};
- final Map<String, Map<String, GlobalObjectKey>> columnDragDragTargets = {};
- void addColumn(String columnId, AFBoardColumnWidget columnWidget) {
- columnKeys[columnId] = columnWidget.globalKey;
- }
- ReorderFlexState? getReorderFlexState({required String columnId}) {
- final flexGlobalKey = columnKeys[columnId];
- if (flexGlobalKey == null) return null;
- if (flexGlobalKey.currentState is! ReorderFlexState) return null;
- final state = flexGlobalKey.currentState as ReorderFlexState;
- return state;
- }
- ReorderFlex? getReorderFlex({required 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;
- }
- @override
- DraggingState? read(String reorderFlexId) {
- return columnDragStates[reorderFlexId];
- }
- @override
- void write(String reorderFlexId, DraggingState state) {
- Log.trace('$reorderFlexId Write dragging state: $state');
- columnDragStates[reorderFlexId] = state;
- }
- @override
- void remove(String reorderFlexId) {
- columnDragStates.remove(reorderFlexId);
- }
- @override
- void addKey(
- String reorderFlexId,
- String key,
- GlobalObjectKey<State<StatefulWidget>> value,
- ) {
- Map<String, GlobalObjectKey>? column = columnDragDragTargets[reorderFlexId];
- if (column == null) {
- column = {};
- columnDragDragTargets[reorderFlexId] = column;
- }
- column[key] = value;
- }
- @override
- GlobalObjectKey<State<StatefulWidget>>? readKey(
- String reorderFlexId, String key) {
- Map<String, GlobalObjectKey>? column = columnDragDragTargets[reorderFlexId];
- if (column != null) {
- return column[key];
- } else {
- return null;
- }
- }
- }
|