Merge pull request #925 from AppFlowy-IO/feat/edit_card_directly

Feat/edit card directly
This commit is contained in:
Nathan.fooo 2022-08-29 19:31:26 +08:00 committed by GitHub
commit 624de03897
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 271 additions and 107 deletions

View File

@ -142,7 +142,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
for (final group in groups) { for (final group in groups) {
final delegate = GroupControllerDelegateImpl( final delegate = GroupControllerDelegateImpl(
controller: boardController, controller: boardController,
didAddColumnItem: (groupId, row) { onNewColumnItem: (groupId, row) {
add(BoardEvent.didCreateRow(groupId, row)); add(BoardEvent.didCreateRow(groupId, row));
}, },
); );
@ -313,11 +313,11 @@ class BoardColumnItem extends AFColumnItem {
class GroupControllerDelegateImpl extends GroupControllerDelegate { class GroupControllerDelegateImpl extends GroupControllerDelegate {
final AFBoardDataController controller; final AFBoardDataController controller;
final void Function(String, RowPB) didAddColumnItem; final void Function(String, RowPB) onNewColumnItem;
GroupControllerDelegateImpl({ GroupControllerDelegateImpl({
required this.controller, required this.controller,
required this.didAddColumnItem, required this.onNewColumnItem,
}); });
@override @override
@ -329,10 +329,8 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
final item = BoardColumnItem( final item = BoardColumnItem(
row: row, row: row,
fieldId: group.fieldId, fieldId: group.fieldId,
requestFocus: true,
); );
controller.addColumnItem(group.groupId, item); controller.addColumnItem(group.groupId, item);
didAddColumnItem(group.groupId, row);
} }
} }
@ -351,6 +349,17 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
), ),
); );
} }
@override
void addNewRow(GroupPB group, RowPB row) {
final item = BoardColumnItem(
row: row,
fieldId: group.fieldId,
requestFocus: true,
);
controller.addColumnItem(group.groupId, item);
onNewColumnItem(group.groupId, row);
}
} }
class BoardEditingRow { class BoardEditingRow {

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async'; import 'dart:async';
@ -20,6 +21,12 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
didReceiveCellUpdate: (content) { didReceiveCellUpdate: (content) {
emit(state.copyWith(content: content)); emit(state.copyWith(content: content));
}, },
updateText: (text) {
if (text != state.content) {
cellController.saveCellData(text);
emit(state.copyWith(content: text));
}
},
); );
}, },
); );
@ -49,6 +56,7 @@ class BoardTextCellBloc extends Bloc<BoardTextCellEvent, BoardTextCellState> {
@freezed @freezed
class BoardTextCellEvent with _$BoardTextCellEvent { class BoardTextCellEvent with _$BoardTextCellEvent {
const factory BoardTextCellEvent.initial() = _InitialCell; const factory BoardTextCellEvent.initial() = _InitialCell;
const factory BoardTextCellEvent.updateText(String text) = _UpdateContent;
const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) = const factory BoardTextCellEvent.didReceiveCellUpdate(String cellContent) =
_DidReceiveCellUpdate; _DidReceiveCellUpdate;
} }

View File

@ -9,6 +9,7 @@ abstract class GroupControllerDelegate {
void removeRow(GroupPB group, String rowId); void removeRow(GroupPB group, String rowId);
void insertRow(GroupPB group, RowPB row, int? index); void insertRow(GroupPB group, RowPB row, int? index);
void updateRow(GroupPB group, RowPB row); void updateRow(GroupPB group, RowPB row);
void addNewRow(GroupPB group, RowPB row);
} }
class GroupController { class GroupController {
@ -48,8 +49,12 @@ class GroupController {
group.rows.add(insertedRow.row); group.rows.add(insertedRow.row);
} }
if (insertedRow.isNew) {
delegate.addNewRow(group, insertedRow.row);
} else {
delegate.insertRow(group, insertedRow.row, index); delegate.insertRow(group, insertedRow.row, index);
} }
}
for (final updatedRow in changeset.updatedRows) { for (final updatedRow in changeset.updatedRows) {
final index = group.rows.indexWhere( final index = group.rows.indexWhere(

View File

@ -196,10 +196,8 @@ class _BoardContentState extends State<BoardContent> {
); );
final cellBuilder = BoardCellBuilder(cardController); final cellBuilder = BoardCellBuilder(cardController);
final isEditing = context.read<BoardBloc>().state.editingRow.fold(
() => false, final isEditing = context.read<BoardBloc>().state.editingRow.isSome();
(editingRow) => editingRow.row.id == rowPB.id,
);
return AppFlowyColumnItemCard( return AppFlowyColumnItemCard(
key: ValueKey(columnItem.id), key: ValueKey(columnItem.id),
@ -212,9 +210,6 @@ class _BoardContentState extends State<BoardContent> {
isEditing: isEditing, isEditing: isEditing,
cellBuilder: cellBuilder, cellBuilder: cellBuilder,
dataController: cardController, dataController: cardController,
onEditEditing: (rowId) {
context.read<BoardBloc>().add(BoardEvent.endEditRow(rowId));
},
openCard: (context) => _openCard( openCard: (context) => _openCard(
gridId, gridId,
fieldCache, fieldCache,

View File

@ -0,0 +1,3 @@
abstract class FocusableBoardCell {
set becomeFocus(bool isFocus);
}

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart'; import 'package:app_flowy/plugins/board/application/card/board_select_option_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -41,7 +42,8 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
builder: (context, state) { builder: (context, state) {
if (state.selectedOptions if (state.selectedOptions
.where((element) => element.id == widget.groupId) .where((element) => element.id == widget.groupId)
.isNotEmpty) { .isNotEmpty ||
state.selectedOptions.isEmpty) {
return const SizedBox(); return const SizedBox();
} else { } else {
final children = state.selectedOptions final children = state.selectedOptions
@ -52,10 +54,17 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
), ),
) )
.toList(); .toList();
return Align(
alignment: Alignment.centerLeft, return IntrinsicHeight(
child: AbsorbPointer( child: Stack(
child: Wrap(children: children, spacing: 4, runSpacing: 2), alignment: AlignmentDirectional.center,
fit: StackFit.expand,
children: [
Wrap(children: children, spacing: 4, runSpacing: 2),
_SelectOptionDialog(
controller: widget.cellControllerBuilder.build(),
),
],
), ),
); );
} }
@ -70,3 +79,23 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
super.dispose(); super.dispose();
} }
} }
class _SelectOptionDialog extends StatelessWidget {
final GridSelectOptionCellController _controller;
const _SelectOptionDialog({
Key? key,
required IGridCellController controller,
}) : _controller = controller as GridSelectOptionCellController,
super(key: key);
@override
Widget build(BuildContext context) {
return InkWell(onTap: () {
SelectOptionCellEditor.show(
context,
_controller,
() {},
);
});
}
}

View File

@ -1,15 +1,19 @@
import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart'; import 'package:app_flowy/plugins/board/application/card/board_text_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart'; import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
class BoardTextCell extends StatefulWidget { class BoardTextCell extends StatefulWidget {
final String groupId; final String groupId;
final bool isFocus;
final GridCellControllerBuilder cellControllerBuilder; final GridCellControllerBuilder cellControllerBuilder;
const BoardTextCell({ const BoardTextCell({
required this.groupId, required this.groupId,
required this.cellControllerBuilder, required this.cellControllerBuilder,
this.isFocus = false,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -19,14 +23,20 @@ class BoardTextCell extends StatefulWidget {
class _BoardTextCellState extends State<BoardTextCell> { class _BoardTextCellState extends State<BoardTextCell> {
late BoardTextCellBloc _cellBloc; late BoardTextCellBloc _cellBloc;
late TextEditingController _controller;
SingleListenerFocusNode focusNode = SingleListenerFocusNode();
@override @override
void initState() { void initState() {
final cellController = final cellController =
widget.cellControllerBuilder.build() as GridCellController; widget.cellControllerBuilder.build() as GridCellController;
_cellBloc = BoardTextCellBloc(cellController: cellController) _cellBloc = BoardTextCellBloc(cellController: cellController)
..add(const BoardTextCellEvent.initial()); ..add(const BoardTextCellEvent.initial());
_controller = TextEditingController(text: _cellBloc.state.content);
if (widget.isFocus) {
focusNode.requestFocus();
}
super.initState(); super.initState();
} }
@ -34,28 +44,38 @@ class _BoardTextCellState extends State<BoardTextCell> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider.value( return BlocProvider.value(
value: _cellBloc, value: _cellBloc,
child: BlocBuilder<BoardTextCellBloc, BoardTextCellState>( child: BlocListener<BoardTextCellBloc, BoardTextCellState>(
buildWhen: (previous, current) => previous.content != current.content, listener: (context, state) {
builder: (context, state) { if (_controller.text != state.content) {
if (state.content.isEmpty) { _controller.text = state.content;
return const SizedBox(); }
} else { },
return Align( child: TextField(
alignment: Alignment.centerLeft, controller: _controller,
child: ConstrainedBox( focusNode: focusNode,
constraints: const BoxConstraints(maxHeight: 120), onChanged: (value) => focusChanged(),
child: FlowyText.medium(state.content, fontSize: 14), onEditingComplete: () => focusNode.unfocus(),
maxLines: 1,
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
decoration: const InputDecoration(
contentPadding: EdgeInsets.symmetric(vertical: 6),
border: InputBorder.none,
isDense: true,
),
),
), ),
); );
} }
},
), Future<void> focusChanged() async {
); _cellBloc.add(BoardTextCellEvent.updateText(_controller.text));
} }
@override @override
Future<void> dispose() async { Future<void> dispose() async {
_cellBloc.close(); _cellBloc.close();
_controller.dispose();
focusNode.dispose();
super.dispose(); super.dispose();
} }
} }

View File

@ -10,8 +10,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'card_cell_builder.dart'; import 'card_cell_builder.dart';
import 'card_container.dart'; import 'card_container.dart';
typedef OnEndEditing = void Function(String rowId);
class BoardCard extends StatefulWidget { class BoardCard extends StatefulWidget {
final String gridId; final String gridId;
final String groupId; final String groupId;
@ -19,7 +17,6 @@ class BoardCard extends StatefulWidget {
final bool isEditing; final bool isEditing;
final CardDataController dataController; final CardDataController dataController;
final BoardCellBuilder cellBuilder; final BoardCellBuilder cellBuilder;
final OnEndEditing onEditEditing;
final void Function(BuildContext) openCard; final void Function(BuildContext) openCard;
const BoardCard({ const BoardCard({
@ -29,7 +26,6 @@ class BoardCard extends StatefulWidget {
required this.isEditing, required this.isEditing,
required this.dataController, required this.dataController,
required this.cellBuilder, required this.cellBuilder,
required this.onEditEditing,
required this.openCard, required this.openCard,
Key? key, Key? key,
}) : super(key: key); }) : super(key: key);
@ -68,6 +64,7 @@ class _BoardCardState extends State<BoardCard> {
widget.openCard(context); widget.openCard(context);
}, },
child: Column( child: Column(
mainAxisSize: MainAxisSize.min,
children: _makeCells( children: _makeCells(
context, context,
state.cells.map((cell) => cell.identifier).toList(), state.cells.map((cell) => cell.identifier).toList(),
@ -83,15 +80,33 @@ class _BoardCardState extends State<BoardCard> {
BuildContext context, BuildContext context,
List<GridCellIdentifier> cells, List<GridCellIdentifier> cells,
) { ) {
return cells.map( final List<Widget> children = [];
(GridCellIdentifier cellId) { cells.asMap().forEach(
final child = widget.cellBuilder.buildCell(widget.groupId, cellId); (int index, GridCellIdentifier cellId) {
return Padding( Widget child = widget.cellBuilder.buildCell(
padding: const EdgeInsets.only(left: 4, right: 4, top: 6), widget.groupId,
cellId,
widget.isEditing,
);
if (index != 0) {
child = Padding(
key: cellId.key(),
padding: const EdgeInsets.only(left: 4, right: 4, top: 8),
child: child, child: child,
); );
} else {
child = Padding(
key: UniqueKey(),
padding: const EdgeInsets.only(left: 4, right: 4),
child: child,
);
}
children.add(child);
}, },
).toList(); );
return children;
} }
@override @override

View File

@ -19,7 +19,11 @@ class BoardCellBuilder {
BoardCellBuilder(this.delegate); BoardCellBuilder(this.delegate);
Widget buildCell(String groupId, GridCellIdentifier cellId) { Widget buildCell(
String groupId,
GridCellIdentifier cellId,
bool isEditing,
) {
final cellControllerBuilder = GridCellControllerBuilder( final cellControllerBuilder = GridCellControllerBuilder(
delegate: delegate, delegate: delegate,
cellId: cellId, cellId: cellId,
@ -62,6 +66,7 @@ class BoardCellBuilder {
return BoardTextCell( return BoardTextCell(
groupId: groupId, groupId: groupId,
cellControllerBuilder: cellControllerBuilder, cellControllerBuilder: cellControllerBuilder,
isFocus: isEditing,
key: key, key: key,
); );
case FieldType.URL: case FieldType.URL:

View File

@ -11,13 +11,15 @@ typedef UpdateFieldNotifiedValue = Either<Unit, FlowyError>;
class CellListener { class CellListener {
final String rowId; final String rowId;
final String fieldId; final String fieldId;
PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier = PublishNotifier(); PublishNotifier<UpdateFieldNotifiedValue>? _updateCellNotifier =
PublishNotifier();
GridNotificationListener? _listener; GridNotificationListener? _listener;
CellListener({required this.rowId, required this.fieldId}); CellListener({required this.rowId, required this.fieldId});
void start({required void Function(UpdateFieldNotifiedValue) onCellChanged}) { void start({required void Function(UpdateFieldNotifiedValue) onCellChanged}) {
_updateCellNotifier?.addPublishListener(onCellChanged); _updateCellNotifier?.addPublishListener(onCellChanged);
_listener = GridNotificationListener(objectId: "$rowId:$fieldId", handler: _handler); _listener = GridNotificationListener(
objectId: "$rowId:$fieldId", handler: _handler);
} }
void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) { void _handler(GridNotification ty, Either<Uint8List, FlowyError> result) {

View File

@ -33,10 +33,17 @@ class GridCellCache {
required this.gridId, required this.gridId,
}); });
void remove(String fieldId) { void removeCellWithFieldId(String fieldId) {
_cellDataByFieldId.remove(fieldId); _cellDataByFieldId.remove(fieldId);
} }
void remove(GridCellCacheKey key) {
var map = _cellDataByFieldId[key.fieldId];
if (map != null) {
map.remove(key.rowId);
}
}
void insert<T extends GridCell>(GridCellCacheKey key, T value) { void insert<T extends GridCell>(GridCellCacheKey key, T value) {
var map = _cellDataByFieldId[key.fieldId]; var map = _cellDataByFieldId[key.fieldId];
if (map == null) { if (map == null) {

View File

@ -191,7 +191,7 @@ class IGridCellController<T, D> extends Equatable {
_cellListener?.start(onCellChanged: (result) { _cellListener?.start(onCellChanged: (result) {
result.fold( result.fold(
(_) { (_) {
_cellsCache.remove(fieldId); _cellsCache.remove(_cacheKey);
_loadData(); _loadData();
}, },
(err) => Log.error(err), (err) => Log.error(err),

View File

@ -52,7 +52,8 @@ class GridRowCache {
// //
notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier notifier.onRowFieldsChanged(() => _rowChangeReasonNotifier
.receive(const RowsChangedReason.fieldDidChange())); .receive(const RowsChangedReason.fieldDidChange()));
notifier.onRowFieldChanged((field) => _cellCache.remove(field.id)); notifier.onRowFieldChanged(
(field) => _cellCache.removeCellWithFieldId(field.id));
_rowInfos = block.rows.map((rowPB) => buildGridRow(rowPB)).toList(); _rowInfos = block.rows.map((rowPB) => buildGridRow(rowPB)).toList();
} }

View File

@ -7,6 +7,7 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <flowy_infra_ui/flowy_infra_u_i_plugin.h> #include <flowy_infra_ui/flowy_infra_u_i_plugin.h>
#include <hotkey_manager/hotkey_manager_plugin.h>
#include <rich_clipboard_linux/rich_clipboard_plugin.h> #include <rich_clipboard_linux/rich_clipboard_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
#include <window_size/window_size_plugin.h> #include <window_size/window_size_plugin.h>
@ -15,6 +16,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) flowy_infra_ui_registrar = g_autoptr(FlPluginRegistrar) flowy_infra_ui_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlowyInfraUIPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlowyInfraUIPlugin");
flowy_infra_u_i_plugin_register_with_registrar(flowy_infra_ui_registrar); flowy_infra_u_i_plugin_register_with_registrar(flowy_infra_ui_registrar);
g_autoptr(FlPluginRegistrar) hotkey_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "HotkeyManagerPlugin");
hotkey_manager_plugin_register_with_registrar(hotkey_manager_registrar);
g_autoptr(FlPluginRegistrar) rich_clipboard_linux_registrar = g_autoptr(FlPluginRegistrar) rich_clipboard_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "RichClipboardPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "RichClipboardPlugin");
rich_clipboard_plugin_register_with_registrar(rich_clipboard_linux_registrar); rich_clipboard_plugin_register_with_registrar(rich_clipboard_linux_registrar);

View File

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
flowy_infra_ui flowy_infra_ui
hotkey_manager
rich_clipboard_linux rich_clipboard_linux
url_launcher_linux url_launcher_linux
window_size window_size

View File

@ -9,6 +9,7 @@ import connectivity_plus_macos
import device_info_plus_macos import device_info_plus_macos
import flowy_infra_ui import flowy_infra_ui
import flowy_sdk import flowy_sdk
import hotkey_manager
import package_info_plus_macos import package_info_plus_macos
import path_provider_macos import path_provider_macos
import rich_clipboard_macos import rich_clipboard_macos
@ -21,6 +22,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FlowyInfraUIPlugin.register(with: registry.registrar(forPlugin: "FlowyInfraUIPlugin")) FlowyInfraUIPlugin.register(with: registry.registrar(forPlugin: "FlowyInfraUIPlugin"))
FlowySdkPlugin.register(with: registry.registrar(forPlugin: "FlowySdkPlugin")) FlowySdkPlugin.register(with: registry.registrar(forPlugin: "FlowySdkPlugin"))
HotkeyManagerPlugin.register(with: registry.registrar(forPlugin: "HotkeyManagerPlugin"))
FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin")) FLTPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FLTPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
RichClipboardPlugin.register(with: registry.registrar(forPlugin: "RichClipboardPlugin")) RichClipboardPlugin.register(with: registry.registrar(forPlugin: "RichClipboardPlugin"))

View File

@ -1,3 +1,7 @@
# 0.0.6
* Support scroll to bottom
* Fix some bugs
# 0.0.5 # 0.0.5
* Optimize insert card animation * Optimize insert card animation
* Enable insert card at the end of the column * Enable insert card at the end of the column

View File

@ -11,13 +11,13 @@ class MultiBoardListExample extends StatefulWidget {
class _MultiBoardListExampleState extends State<MultiBoardListExample> { class _MultiBoardListExampleState extends State<MultiBoardListExample> {
final AFBoardDataController boardDataController = AFBoardDataController( final AFBoardDataController boardDataController = AFBoardDataController(
onMoveColumn: (fromColumnId, fromIndex, toColumnId, toIndex) { onMoveColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
debugPrint('Move column from $fromIndex to $toIndex'); // debugPrint('Move column from $fromIndex to $toIndex');
}, },
onMoveColumnItem: (columnId, fromIndex, toIndex) { onMoveColumnItem: (columnId, fromIndex, toIndex) {
debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex'); // debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex');
}, },
onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) { onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) {
debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex'); // debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex');
}, },
); );
@ -96,7 +96,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
}, },
cardBuilder: (context, column, columnItem) { cardBuilder: (context, column, columnItem) {
return AppFlowyColumnItemCard( return AppFlowyColumnItemCard(
key: ObjectKey(columnItem), key: ValueKey(columnItem.id),
child: _buildCard(columnItem), child: _buildCard(columnItem),
); );
}, },
@ -121,6 +121,32 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
} }
if (item is RichTextItem) { if (item is RichTextItem) {
return RichTextCard(item: item);
}
throw UnimplementedError();
}
}
class RichTextCard extends StatefulWidget {
final RichTextItem item;
const RichTextCard({
required this.item,
Key? key,
}) : super(key: key);
@override
State<RichTextCard> createState() => _RichTextCardState();
}
class _RichTextCardState extends State<RichTextCard> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Align( return Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerLeft,
child: Padding( child: Padding(
@ -129,13 +155,13 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
item.title, widget.item.title,
style: const TextStyle(fontSize: 14), style: const TextStyle(fontSize: 14),
textAlign: TextAlign.left, textAlign: TextAlign.left,
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
item.subtitle, widget.item.subtitle,
style: const TextStyle(fontSize: 12, color: Colors.grey), style: const TextStyle(fontSize: 12, color: Colors.grey),
) )
], ],
@ -143,9 +169,6 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
), ),
); );
} }
throw UnimplementedError();
}
} }
class TextItem extends AFColumnItem { class TextItem extends AFColumnItem {

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
const DART_LOG = "Dart_LOG"; const DART_LOG = "Dart_LOG";
class Log { class Log {
static const enableLog = false; static const enableLog = true;
static void info(String? message) { static void info(String? message) {
if (enableLog) { if (enableLog) {
@ -26,7 +26,7 @@ class Log {
static void trace(String? message) { static void trace(String? message) {
if (enableLog) { if (enableLog) {
debugPrint('❗️[Trace] - ${DateTime.now().second}=> $message'); // debugPrint('❗️[Trace] - ${DateTime.now().second}=> $message');
} }
} }
} }

View File

@ -64,7 +64,7 @@ class AFBoard extends StatelessWidget {
final BoxConstraints columnConstraints; final BoxConstraints columnConstraints;
/// ///
final BoardPhantomController phantomController; late final BoardPhantomController phantomController;
final ScrollController? scrollController; final ScrollController? scrollController;
@ -85,8 +85,12 @@ class AFBoard extends StatelessWidget {
this.columnConstraints = const BoxConstraints(maxWidth: 200), this.columnConstraints = const BoxConstraints(maxWidth: 200),
this.config = const AFBoardConfig(), this.config = const AFBoardConfig(),
Key? key, Key? key,
}) : phantomController = BoardPhantomController(delegate: dataController), }) : super(key: key) {
super(key: key); phantomController = BoardPhantomController(
delegate: dataController,
columnsState: _columnState,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -194,6 +198,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
dataSource: widget.dataController, dataSource: widget.dataController,
direction: Axis.horizontal, direction: Axis.horizontal,
interceptor: interceptor, interceptor: interceptor,
reorderable: false,
children: _buildColumns(), children: _buildColumns(),
); );
@ -244,7 +249,7 @@ class _AFBoardContentState extends State<AFBoardContent> {
child: Consumer<AFBoardColumnDataController>( child: Consumer<AFBoardColumnDataController>(
builder: (context, value, child) { builder: (context, value, child) {
final boardColumn = AFBoardColumnWidget( final boardColumn = AFBoardColumnWidget(
key: PageStorageKey<String>(columnData.id), // key: PageStorageKey<String>(columnData.id),
// key: GlobalObjectKey(columnData.id), // key: GlobalObjectKey(columnData.id),
margin: _marginFromIndex(columnIndex), margin: _marginFromIndex(columnIndex),
itemMargin: widget.config.columnItemPadding, itemMargin: widget.config.columnItemPadding,

View File

@ -92,7 +92,7 @@ class AFBoardColumnWidget extends StatefulWidget {
final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage; final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
final GlobalKey globalKey; final GlobalObjectKey globalKey;
AFBoardColumnWidget({ AFBoardColumnWidget({
Key? key, Key? key,
@ -111,7 +111,7 @@ class AFBoardColumnWidget extends StatefulWidget {
this.itemMargin = EdgeInsets.zero, this.itemMargin = EdgeInsets.zero,
this.cornerRadius = 0.0, this.cornerRadius = 0.0,
this.backgroundColor = Colors.transparent, this.backgroundColor = Colors.transparent,
}) : globalKey = GlobalKey(), }) : globalKey = GlobalObjectKey(dataSource.columnData.id),
config = const ReorderFlexConfig(), config = const ReorderFlexConfig(),
super(key: key); super(key: key);

View File

@ -75,6 +75,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
final AnimationController deleteAnimationController; final AnimationController deleteAnimationController;
final bool useMoveAnimation; final bool useMoveAnimation;
final bool draggable;
const ReorderDragTarget({ const ReorderDragTarget({
Key? key, Key? key,
@ -88,6 +89,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
required this.insertAnimationController, required this.insertAnimationController,
required this.deleteAnimationController, required this.deleteAnimationController,
required this.useMoveAnimation, required this.useMoveAnimation,
required this.draggable,
this.onAccept, this.onAccept,
this.onLeave, this.onLeave,
this.draggableTargetBuilder, this.draggableTargetBuilder,
@ -132,6 +134,9 @@ class _ReorderDragTargetState<T extends DragTargetData>
List<T?> acceptedCandidates, List<T?> acceptedCandidates,
List<dynamic> rejectedCandidates, List<dynamic> rejectedCandidates,
) { ) {
if (!widget.draggable) {
return widget.child;
}
Widget feedbackBuilder = Builder(builder: (BuildContext context) { Widget feedbackBuilder = Builder(builder: (BuildContext context) {
BoxConstraints contentSizeConstraints = BoxConstraints contentSizeConstraints =
BoxConstraints.loose(_draggingFeedbackSize!); BoxConstraints.loose(_draggingFeedbackSize!);

View File

@ -131,6 +131,7 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor {
final String reorderFlexId; final String reorderFlexId;
final List<String> acceptedReorderFlexIds; final List<String> acceptedReorderFlexIds;
final CrossReorderFlexDragTargetDelegate delegate; final CrossReorderFlexDragTargetDelegate delegate;
@override @override
final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder; final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder;
@ -188,7 +189,7 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor {
); );
Log.debug( Log.debug(
'[$CrossReorderFlexDragTargetInterceptor] dargTargetIndex: $dragTargetIndex, reorderFlexId: $reorderFlexId'); '[$CrossReorderFlexDragTargetInterceptor] isNewDragTarget: $isNewDragTarget, dargTargetIndex: $dragTargetIndex, reorderFlexId: $reorderFlexId');
if (isNewDragTarget == false) { if (isNewDragTarget == false) {
delegate.updateDragTargetData(reorderFlexId, dragTargetIndex); delegate.updateDragTargetData(reorderFlexId, dragTargetIndex);

View File

@ -82,6 +82,8 @@ class ReorderFlex extends StatefulWidget {
final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage; final ReorderDragTargetIndexKeyStorage? dragTargetIndexKeyStorage;
final bool reorderable;
ReorderFlex({ ReorderFlex({
Key? key, Key? key,
this.scrollController, this.scrollController,
@ -89,6 +91,7 @@ class ReorderFlex extends StatefulWidget {
required this.children, required this.children,
required this.config, required this.config,
required this.onReorder, required this.onReorder,
this.reorderable = true,
this.dragStateStorage, this.dragStateStorage,
this.dragTargetIndexKeyStorage, this.dragTargetIndexKeyStorage,
this.onDragStarted, this.onDragStarted,
@ -378,14 +381,13 @@ class ReorderFlexState extends State<ReorderFlex>
dragState.currentIndex, dragState.currentIndex,
); );
} }
dragState.endDragging(); dragState.endDragging();
widget.onDragEnded?.call(); widget.onDragEnded?.call();
}); });
}, },
onWillAccept: (FlexDragTargetData dragTargetData) { onWillAccept: (FlexDragTargetData dragTargetData) {
// Do not receive any events if the Insert item is animating. // Do not receive any events if the Insert item is animating.
if (_animation.deleteController.isAnimating) { if (_animation.insertController.isAnimating) {
return false; return false;
} }
@ -421,6 +423,7 @@ class ReorderFlexState extends State<ReorderFlex>
deleteAnimationController: _animation.deleteController, deleteAnimationController: _animation.deleteController,
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder, draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
useMoveAnimation: widget.config.useMoveAnimation, useMoveAnimation: widget.config.useMoveAnimation,
draggable: widget.reorderable,
child: child, child: child,
); );
} }

View File

@ -1,7 +1,7 @@
import 'package:appflowy_board/appflowy_board.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import '../../utils/log.dart'; import '../../utils/log.dart';
import '../board_column/board_column_data.dart';
import '../reorder_flex/drag_state.dart'; import '../reorder_flex/drag_state.dart';
import '../reorder_flex/drag_target.dart'; import '../reorder_flex/drag_target.dart';
import '../reorder_flex/drag_target_interceptor.dart'; import '../reorder_flex/drag_target_interceptor.dart';
@ -39,8 +39,12 @@ class BoardPhantomController extends OverlapDragTargetDelegate
with CrossReorderFlexDragTargetDelegate { with CrossReorderFlexDragTargetDelegate {
PhantomRecord? phantomRecord; PhantomRecord? phantomRecord;
final BoardPhantomControllerDelegate delegate; final BoardPhantomControllerDelegate delegate;
final columnsState = ColumnPhantomStateController(); final BoardColumnsState columnsState;
BoardPhantomController({required this.delegate}); final phantomState = ColumnPhantomState();
BoardPhantomController({
required this.delegate,
required this.columnsState,
});
bool isFromColumn(String columnId) { bool isFromColumn(String columnId) {
if (phantomRecord != null) { if (phantomRecord != null) {
@ -59,19 +63,19 @@ class BoardPhantomController extends OverlapDragTargetDelegate
} }
void columnStartDragging(String columnId) { void columnStartDragging(String columnId) {
columnsState.setColumnIsDragging(columnId, true); phantomState.setColumnIsDragging(columnId, true);
} }
/// Remove the phantom in the column when the column is end dragging. /// Remove the phantom in the column when the column is end dragging.
void columnEndDragging(String columnId) { void columnEndDragging(String columnId) {
columnsState.setColumnIsDragging(columnId, false); phantomState.setColumnIsDragging(columnId, false);
if (phantomRecord == null) return; if (phantomRecord == null) return;
final fromColumnId = phantomRecord!.fromColumnId; final fromColumnId = phantomRecord!.fromColumnId;
final toColumnId = phantomRecord!.toColumnId; final toColumnId = phantomRecord!.toColumnId;
if (fromColumnId == columnId) { if (fromColumnId == columnId) {
columnsState.notifyDidRemovePhantom(toColumnId); phantomState.notifyDidRemovePhantom(toColumnId);
} }
if (phantomRecord!.toColumnId == columnId) { if (phantomRecord!.toColumnId == columnId) {
@ -82,8 +86,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
phantomRecord!.toColumnIndex, phantomRecord!.toColumnIndex,
); );
Log.debug( // Log.debug(
"[$BoardPhantomController] did move ${phantomRecord.toString()}"); // "[$BoardPhantomController] did move ${phantomRecord.toString()}");
phantomRecord = null; phantomRecord = null;
} }
} }
@ -91,8 +95,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
/// Remove the phantom in the column if it contains phantom /// Remove the phantom in the column if it contains phantom
void _removePhantom(String columnId) { void _removePhantom(String columnId) {
if (delegate.removePhantom(columnId)) { if (delegate.removePhantom(columnId)) {
columnsState.notifyDidRemovePhantom(columnId); phantomState.notifyDidRemovePhantom(columnId);
columnsState.removeColumnListener(columnId); phantomState.removeColumnListener(columnId);
} }
} }
@ -105,7 +109,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
index: phantomIndex, index: phantomIndex,
dragTargetData: dragTargetData, dragTargetData: dragTargetData,
); );
columnsState.addColumnListener(toColumnId, phantomContext); phantomState.addColumnListener(toColumnId, phantomContext);
delegate.insertPhantom( delegate.insertPhantom(
toColumnId, toColumnId,
@ -113,7 +117,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
PhantomColumnItem(phantomContext), PhantomColumnItem(phantomContext),
); );
columnsState.notifyDidInsertPhantom(toColumnId, phantomIndex); phantomState.notifyDidInsertPhantom(toColumnId, phantomIndex);
} }
/// Reset or initial the [PhantomRecord] /// Reset or initial the [PhantomRecord]
@ -150,7 +154,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate
if (phantomRecord == null) { if (phantomRecord == null) {
_resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); _resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex);
_insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex); _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex);
return false;
return true;
} }
final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId; final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId;
@ -204,7 +209,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate
@override @override
int getInsertedIndex(String dragTargetId) { int getInsertedIndex(String dragTargetId) {
if (columnsState.isDragging(dragTargetId)) { if (phantomState.isDragging(dragTargetId)) {
return -1; return -1;
} }
@ -243,8 +248,7 @@ class PhantomRecord {
if (fromColumnIndex == index) { if (fromColumnIndex == index) {
return; return;
} }
Log.debug(
'[$PhantomRecord] Update Column:[$fromColumnId] remove position to $index');
fromColumnIndex = index; fromColumnIndex = index;
} }

View File

@ -1,7 +1,7 @@
import 'phantom_controller.dart'; import 'phantom_controller.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class ColumnPhantomStateController { class ColumnPhantomState {
final _states = <String, ColumnState>{}; final _states = <String, ColumnState>{};
void setColumnIsDragging(String columnId, bool isDragging) { void setColumnIsDragging(String columnId, bool isDragging) {

View File

@ -1,6 +1,6 @@
name: appflowy_board name: appflowy_board
description: AppFlowy board implementation. description: AppFlowy board implementation.
version: 0.0.5 version: 0.0.6
homepage: https://github.com/AppFlowy-IO/AppFlowy homepage: https://github.com/AppFlowy-IO/AppFlowy
repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board

View File

@ -120,17 +120,28 @@ pub struct InsertedRowPB {
#[pb(index = 2, one_of)] #[pb(index = 2, one_of)]
pub index: Option<i32>, pub index: Option<i32>,
#[pb(index = 3)]
pub is_new: bool,
} }
impl InsertedRowPB { impl InsertedRowPB {
pub fn new(row: RowPB) -> Self { pub fn new(row: RowPB) -> Self {
Self { row, index: None } Self {
row,
index: None,
is_new: false,
}
} }
} }
impl std::convert::From<RowPB> for InsertedRowPB { impl std::convert::From<RowPB> for InsertedRowPB {
fn from(row: RowPB) -> Self { fn from(row: RowPB) -> Self {
Self { row, index: None } Self {
row,
index: None,
is_new: false,
}
} }
} }

View File

@ -164,6 +164,7 @@ impl GridBlockManager {
let insert_row = InsertedRowPB { let insert_row = InsertedRowPB {
index: Some(to as i32), index: Some(to as i32),
row: make_row_from_row_rev(row_rev), row: make_row_from_row_rev(row_rev),
is_new: false,
}; };
let notified_changeset = GridBlockChangesetPB { let notified_changeset = GridBlockChangesetPB {

View File

@ -98,6 +98,7 @@ impl GridViewRevisionEditor {
let inserted_row = InsertedRowPB { let inserted_row = InsertedRowPB {
row: row_pb.clone(), row: row_pb.clone(),
index: None, index: None,
is_new: true,
}; };
let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]); let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]);
self.notify_did_update_group(changeset).await; self.notify_did_update_group(changeset).await;