diff --git a/frontend/appflowy_flutter/integration_test/mobile/document/simple_table_test.dart b/frontend/appflowy_flutter/integration_test/mobile/document/simple_table_test.dart index 92f825f6124ea..fcd5494218fae 100644 --- a/frontend/appflowy_flutter/integration_test/mobile/document/simple_table_test.dart +++ b/frontend/appflowy_flutter/integration_test/mobile/document/simple_table_test.dart @@ -385,5 +385,56 @@ void main() { expect(paragraph.delta!, isEmpty); } }); + + testWidgets(''' +1. insert a simple table via + menu +2. insert a heading block in table cell +''', (tester) async { + await tester.launchInAnonymousMode(); + await tester.createNewDocumentOnMobile('simple table'); + + final editorState = tester.editor.getCurrentEditorState(); + // focus on the editor + unawaited( + editorState.updateSelectionWithReason( + Selection.collapsed(Position(path: [0])), + reason: SelectionUpdateReason.uiEvent, + ), + ); + await tester.pumpAndSettle(); + + final firstParagraphPath = [0, 0, 0, 0]; + + // open the plus menu and select the table block + { + await tester.openPlusMenuAndClickButton( + LocaleKeys.document_slashMenu_name_table.tr(), + ); + + // check the block is inserted + final table = editorState.getNodeAtPath([0])!; + expect(table.type, equals(SimpleTableBlockKeys.type)); + expect(table.rowLength, equals(2)); + expect(table.columnLength, equals(2)); + + // focus on the first cell + + final selection = editorState.selection!; + expect(selection.isCollapsed, isTrue); + expect(selection.start.path, equals(firstParagraphPath)); + } + + // open the plus menu and select the heading block + { + await tester.openPlusMenuAndClickButton( + LocaleKeys.editor_toggleHeading1ShortForm.tr(), + ); + + // check the heading block is inserted + final heading = editorState.getNodeAtPath([0, 0, 0, 0])!; + expect(heading.type, equals(HeadingBlockKeys.type)); + expect(heading.level, equals(1)); + } + }); }); } diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart index a7c6fd10e39aa..5b379967ef773 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_view_page.dart @@ -138,7 +138,6 @@ class MobileViewBottomSheetBody extends StatelessWidget { ), _divider(), ..._buildPublishActions(context), - _divider(), MobileQuickActionButton( text: LocaleKeys.button_delete.tr(), textColor: Theme.of(context).colorScheme.error, @@ -191,6 +190,7 @@ class MobileViewBottomSheetBody extends StatelessWidget { MobileViewBottomSheetBodyAction.unpublish, ), ), + _divider(), ]; } else { return [ @@ -201,6 +201,7 @@ class MobileViewBottomSheetBody extends StatelessWidget { MobileViewBottomSheetBodyAction.publish, ), ), + _divider(), ]; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart index 77a0531180972..c1fa69913cc97 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart @@ -675,7 +675,12 @@ CalloutBlockComponentBuilder _buildCalloutBlockComponentBuilder( final calloutBGColor = AFThemeExtension.of(context).calloutBGColor; return CalloutBlockComponentBuilder( configuration: configuration.copyWith( - padding: (node) => const EdgeInsets.symmetric(vertical: 10), + padding: (node) { + if (UniversalPlatform.isMobile) { + return configuration.padding(node); + } + return const EdgeInsets.symmetric(vertical: 10); + }, textAlign: (node) => _buildTextAlignInTableCell( context, node: node, @@ -725,6 +730,7 @@ CodeBlockComponentBuilder _buildCodeBlockComponentBuilder( ) { return CodeBlockComponentBuilder( styleBuilder: styleCustomizer.codeBlockStyleBuilder, + configuration: configuration, padding: const EdgeInsets.only(left: 20, right: 30, bottom: 34), languagePickerBuilder: codeBlockLanguagePickerBuilder, copyButtonBuilder: codeBlockCopyBuilder, @@ -763,9 +769,10 @@ ToggleListBlockComponentBuilder _buildToggleListBlockComponentBuilder( final factor = pageStyle.fontLayout.factor; final headingPaddings = pageStyle.lineHeightLayout.headingPaddings.map((e) => e * factor); - int level = node.attributes[HeadingBlockKeys.level] ?? 6; - level = level.clamp(1, 6); - return EdgeInsets.only(top: headingPaddings.elementAt(level - 1)); + final level = + (node.attributes[HeadingBlockKeys.level] ?? 6).clamp(1, 6); + final top = headingPaddings.elementAt(level - 1); + return configuration.padding(node).copyWith(top: top); } return const EdgeInsets.only(top: 12.0, bottom: 4.0); @@ -846,7 +853,9 @@ FileBlockComponentBuilder _buildFileBlockComponentBuilder( BuildContext context, BlockComponentConfiguration configuration, ) { - return FileBlockComponentBuilder(configuration: configuration); + return FileBlockComponentBuilder( + configuration: configuration, + ); } SubPageBlockComponentBuilder _buildSubPageBlockComponentBuilder( diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart index 41a1e6624ccc1..52fc2e717fcf5 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/file/file_block_component.dart @@ -281,9 +281,13 @@ class FileBlockComponentState extends State listenable: editorState.selectionNotifier, blockColor: editorState.editorStyle.selectionColor, supportTypes: const [BlockSelectionType.block], - child: Padding(key: fileKey, padding: padding, child: child), + child: Padding( + key: fileKey, + padding: padding, + child: child, + ), ); - } else if (url == null || url.isEmpty) { + } else { return Padding( key: fileKey, padding: padding, @@ -384,6 +388,9 @@ class FileBlockComponentState extends State ), const HSpace(8), ], + if (UniversalPlatform.isMobile) ...[ + const HSpace(36), + ], ]; } else { return [ diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_menu_item_builder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_menu_item_builder.dart new file mode 100644 index 0000000000000..4ae243575ceec --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_menu_item_builder.dart @@ -0,0 +1,480 @@ +import 'dart:async'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy/startup/tasks/app_widget.dart'; +import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class AddBlockMenuItemBuilder { + AddBlockMenuItemBuilder({ + required this.editorState, + required this.selection, + }); + + final EditorState editorState; + final Selection selection; + + List> buildTypeOptionMenuItemValues( + BuildContext context, + ) { + if (selection.isCollapsed) { + final node = editorState.getNodeAtPath(selection.end.path); + if (node?.parentTableCellNode != null) { + return _buildTableTypeOptionMenuItemValues(context); + } + } + return _buildDefaultTypeOptionMenuItemValues(context); + } + + /// Build the default type option menu item values. + + List> _buildDefaultTypeOptionMenuItemValues( + BuildContext context, + ) { + final colorMap = _colorMap(context); + return [ + ..._buildHeadingMenuItems(colorMap), + ..._buildParagraphMenuItems(colorMap), + ..._buildTodoListMenuItems(colorMap), + ..._buildTableMenuItems(colorMap), + ..._buildQuoteMenuItems(colorMap), + ..._buildListMenuItems(colorMap), + ..._buildToggleHeadingMenuItems(colorMap), + ..._buildImageMenuItems(colorMap), + ..._buildPhotoGalleryMenuItems(colorMap), + ..._buildFileMenuItems(colorMap), + ..._buildMentionMenuItems(context, colorMap), + ..._buildDividerMenuItems(colorMap), + ..._buildCalloutMenuItems(colorMap), + ..._buildCodeMenuItems(colorMap), + ..._buildMathEquationMenuItems(colorMap), + ]; + } + + /// Build the table type option menu item values. + List> _buildTableTypeOptionMenuItemValues( + BuildContext context, + ) { + final colorMap = _colorMap(context); + return [ + ..._buildHeadingMenuItems(colorMap), + ..._buildParagraphMenuItems(colorMap), + ..._buildTodoListMenuItems(colorMap), + ..._buildQuoteMenuItems(colorMap), + ..._buildListMenuItems(colorMap), + ..._buildToggleHeadingMenuItems(colorMap), + ..._buildImageMenuItems(colorMap), + ..._buildFileMenuItems(colorMap), + ..._buildMentionMenuItems(context, colorMap), + ..._buildDividerMenuItems(colorMap), + ..._buildCalloutMenuItems(colorMap), + ..._buildCodeMenuItems(colorMap), + ..._buildMathEquationMenuItems(colorMap), + ]; + } + + List> _buildHeadingMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: HeadingBlockKeys.type, + backgroundColor: colorMap[HeadingBlockKeys.type]!, + text: LocaleKeys.editor_heading1.tr(), + icon: FlowySvgs.m_add_block_h1_s, + onTap: (_, __) => _insertBlock(headingNode(level: 1)), + ), + TypeOptionMenuItemValue( + value: HeadingBlockKeys.type, + backgroundColor: colorMap[HeadingBlockKeys.type]!, + text: LocaleKeys.editor_heading2.tr(), + icon: FlowySvgs.m_add_block_h2_s, + onTap: (_, __) => _insertBlock(headingNode(level: 2)), + ), + TypeOptionMenuItemValue( + value: HeadingBlockKeys.type, + backgroundColor: colorMap[HeadingBlockKeys.type]!, + text: LocaleKeys.editor_heading3.tr(), + icon: FlowySvgs.m_add_block_h3_s, + onTap: (_, __) => _insertBlock(headingNode(level: 3)), + ), + ]; + } + + List> _buildParagraphMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: ParagraphBlockKeys.type, + backgroundColor: colorMap[ParagraphBlockKeys.type]!, + text: LocaleKeys.editor_text.tr(), + icon: FlowySvgs.m_add_block_paragraph_s, + onTap: (_, __) => _insertBlock(paragraphNode()), + ), + ]; + } + + List> _buildTodoListMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: TodoListBlockKeys.type, + backgroundColor: colorMap[TodoListBlockKeys.type]!, + text: LocaleKeys.editor_checkbox.tr(), + icon: FlowySvgs.m_add_block_checkbox_s, + onTap: (_, __) => _insertBlock(todoListNode(checked: false)), + ), + ]; + } + + List> _buildTableMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: SimpleTableBlockKeys.type, + backgroundColor: colorMap[SimpleTableBlockKeys.type]!, + text: LocaleKeys.editor_table.tr(), + icon: FlowySvgs.slash_menu_icon_simple_table_s, + onTap: (_, __) => _insertBlock( + createSimpleTableBlockNode(columnCount: 2, rowCount: 2), + ), + ), + ]; + } + + List> _buildQuoteMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: QuoteBlockKeys.type, + backgroundColor: colorMap[QuoteBlockKeys.type]!, + text: LocaleKeys.editor_quote.tr(), + icon: FlowySvgs.m_add_block_quote_s, + onTap: (_, __) => _insertBlock(quoteNode()), + ), + ]; + } + + List> _buildListMenuItems( + Map colorMap, + ) { + return [ + // bulleted list, numbered list, toggle list + TypeOptionMenuItemValue( + value: BulletedListBlockKeys.type, + backgroundColor: colorMap[BulletedListBlockKeys.type]!, + text: LocaleKeys.editor_bulletedListShortForm.tr(), + icon: FlowySvgs.m_add_block_bulleted_list_s, + onTap: (_, __) => _insertBlock(bulletedListNode()), + ), + TypeOptionMenuItemValue( + value: NumberedListBlockKeys.type, + backgroundColor: colorMap[NumberedListBlockKeys.type]!, + text: LocaleKeys.editor_numberedListShortForm.tr(), + icon: FlowySvgs.m_add_block_numbered_list_s, + onTap: (_, __) => _insertBlock(numberedListNode()), + ), + TypeOptionMenuItemValue( + value: ToggleListBlockKeys.type, + backgroundColor: colorMap[ToggleListBlockKeys.type]!, + text: LocaleKeys.editor_toggleListShortForm.tr(), + icon: FlowySvgs.m_add_block_toggle_s, + onTap: (_, __) => _insertBlock(toggleListBlockNode()), + ), + ]; + } + + List> _buildToggleHeadingMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: ToggleListBlockKeys.type, + backgroundColor: colorMap[ToggleListBlockKeys.type]!, + text: LocaleKeys.editor_toggleHeading1ShortForm.tr(), + icon: FlowySvgs.toggle_heading1_s, + iconPadding: const EdgeInsets.all(3), + onTap: (_, __) => _insertBlock(toggleHeadingNode()), + ), + TypeOptionMenuItemValue( + value: ToggleListBlockKeys.type, + backgroundColor: colorMap[ToggleListBlockKeys.type]!, + text: LocaleKeys.editor_toggleHeading2ShortForm.tr(), + icon: FlowySvgs.toggle_heading2_s, + iconPadding: const EdgeInsets.all(3), + onTap: (_, __) => _insertBlock(toggleHeadingNode(level: 2)), + ), + TypeOptionMenuItemValue( + value: ToggleListBlockKeys.type, + backgroundColor: colorMap[ToggleListBlockKeys.type]!, + text: LocaleKeys.editor_toggleHeading3ShortForm.tr(), + icon: FlowySvgs.toggle_heading3_s, + iconPadding: const EdgeInsets.all(3), + onTap: (_, __) => _insertBlock(toggleHeadingNode(level: 3)), + ), + ]; + } + + List> _buildImageMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: ImageBlockKeys.type, + backgroundColor: colorMap[ImageBlockKeys.type]!, + text: LocaleKeys.editor_image.tr(), + icon: FlowySvgs.m_add_block_image_s, + onTap: (_, __) async { + AppGlobals.rootNavKey.currentContext?.pop(true); + Future.delayed(const Duration(milliseconds: 400), () async { + final imagePlaceholderKey = GlobalKey(); + await editorState.insertEmptyImageBlock(imagePlaceholderKey); + }); + }, + ), + ]; + } + + List> _buildPhotoGalleryMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: MultiImageBlockKeys.type, + backgroundColor: colorMap[ImageBlockKeys.type]!, + text: LocaleKeys.document_plugins_photoGallery_name.tr(), + icon: FlowySvgs.m_add_block_photo_gallery_s, + onTap: (_, __) async { + AppGlobals.rootNavKey.currentContext?.pop(true); + Future.delayed(const Duration(milliseconds: 400), () async { + final imagePlaceholderKey = GlobalKey(); + await editorState.insertEmptyMultiImageBlock(imagePlaceholderKey); + }); + }, + ), + ]; + } + + List> _buildFileMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: FileBlockKeys.type, + backgroundColor: colorMap[ImageBlockKeys.type]!, + text: LocaleKeys.document_plugins_file_name.tr(), + icon: FlowySvgs.media_s, + onTap: (_, __) async { + AppGlobals.rootNavKey.currentContext?.pop(true); + Future.delayed(const Duration(milliseconds: 400), () async { + final fileGlobalKey = GlobalKey(); + await editorState.insertEmptyFileBlock(fileGlobalKey); + }); + }, + ), + ]; + } + + List> _buildMentionMenuItems( + BuildContext context, + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: ParagraphBlockKeys.type, + backgroundColor: colorMap[MentionBlockKeys.type]!, + text: LocaleKeys.editor_date.tr(), + icon: FlowySvgs.m_add_block_date_s, + onTap: (_, __) => _insertBlock(dateMentionNode()), + ), + TypeOptionMenuItemValue( + value: ParagraphBlockKeys.type, + backgroundColor: colorMap[MentionBlockKeys.type]!, + text: LocaleKeys.editor_page.tr(), + icon: FlowySvgs.icon_document_s, + onTap: (_, __) async { + AppGlobals.rootNavKey.currentContext?.pop(true); + + final currentViewId = getIt().latestOpenView?.id; + final view = await showPageSelectorSheet( + context, + currentViewId: currentViewId, + ); + + if (view != null) { + Future.delayed(const Duration(milliseconds: 100), () { + editorState.insertBlockAfterCurrentSelection( + selection, + pageMentionNode(view.id), + ); + }); + } + }, + ), + ]; + } + + List> _buildDividerMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: DividerBlockKeys.type, + backgroundColor: colorMap[DividerBlockKeys.type]!, + text: LocaleKeys.editor_divider.tr(), + icon: FlowySvgs.m_add_block_divider_s, + onTap: (_, __) { + AppGlobals.rootNavKey.currentContext?.pop(true); + Future.delayed(const Duration(milliseconds: 100), () { + editorState.insertDivider(selection); + }); + }, + ), + ]; + } + + // callout, code, math equation + List> _buildCalloutMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: CalloutBlockKeys.type, + backgroundColor: colorMap[CalloutBlockKeys.type]!, + text: LocaleKeys.document_plugins_callout.tr(), + icon: FlowySvgs.m_add_block_callout_s, + onTap: (_, __) => _insertBlock(calloutNode()), + ), + ]; + } + + List> _buildCodeMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: CodeBlockKeys.type, + backgroundColor: colorMap[CodeBlockKeys.type]!, + text: LocaleKeys.editor_codeBlockShortForm.tr(), + icon: FlowySvgs.m_add_block_code_s, + onTap: (_, __) => _insertBlock(codeBlockNode()), + ), + ]; + } + + List> _buildMathEquationMenuItems( + Map colorMap, + ) { + return [ + TypeOptionMenuItemValue( + value: MathEquationBlockKeys.type, + backgroundColor: colorMap[MathEquationBlockKeys.type]!, + text: LocaleKeys.editor_mathEquationShortForm.tr(), + icon: FlowySvgs.m_add_block_formula_s, + onTap: (_, __) { + AppGlobals.rootNavKey.currentContext?.pop(true); + Future.delayed(const Duration(milliseconds: 100), () { + editorState.insertMathEquation(selection); + }); + }, + ), + ]; + } + + Map _colorMap(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + if (isDarkMode) { + return { + HeadingBlockKeys.type: const Color(0xFF5465A1), + ParagraphBlockKeys.type: const Color(0xFF5465A1), + TodoListBlockKeys.type: const Color(0xFF4BB299), + SimpleTableBlockKeys.type: const Color(0xFF4BB299), + QuoteBlockKeys.type: const Color(0xFFBAAC74), + BulletedListBlockKeys.type: const Color(0xFFA35F94), + NumberedListBlockKeys.type: const Color(0xFFA35F94), + ToggleListBlockKeys.type: const Color(0xFFA35F94), + ImageBlockKeys.type: const Color(0xFFBAAC74), + MentionBlockKeys.type: const Color(0xFF40AAB8), + DividerBlockKeys.type: const Color(0xFF4BB299), + CalloutBlockKeys.type: const Color(0xFF66599B), + CodeBlockKeys.type: const Color(0xFF66599B), + MathEquationBlockKeys.type: const Color(0xFF66599B), + }; + } + return { + HeadingBlockKeys.type: const Color(0xFFBECCFF), + ParagraphBlockKeys.type: const Color(0xFFBECCFF), + TodoListBlockKeys.type: const Color(0xFF98F4CD), + SimpleTableBlockKeys.type: const Color(0xFF98F4CD), + QuoteBlockKeys.type: const Color(0xFFFDEDA7), + BulletedListBlockKeys.type: const Color(0xFFFFB9EF), + NumberedListBlockKeys.type: const Color(0xFFFFB9EF), + ToggleListBlockKeys.type: const Color(0xFFFFB9EF), + ImageBlockKeys.type: const Color(0xFFFDEDA7), + MentionBlockKeys.type: const Color(0xFF91EAF5), + DividerBlockKeys.type: const Color(0xFF98F4CD), + CalloutBlockKeys.type: const Color(0xFFCABDFF), + CodeBlockKeys.type: const Color(0xFFCABDFF), + MathEquationBlockKeys.type: const Color(0xFFCABDFF), + }; + } + + Future _insertBlock(Node node) async { + AppGlobals.rootNavKey.currentContext?.pop(true); + Future.delayed( + const Duration(milliseconds: 100), + () async { + // if current selected block is a empty paragraph block, replace it with the new block. + if (selection.isCollapsed) { + final currentNode = editorState.getNodeAtPath(selection.end.path); + final text = currentNode?.delta?.toPlainText(); + if (currentNode != null && + currentNode.type == ParagraphBlockKeys.type && + text != null && + text.isEmpty) { + final transaction = editorState.transaction; + transaction.insertNode( + selection.end.path.next, + node, + ); + transaction.deleteNode(currentNode); + if (node.type == SimpleTableBlockKeys.type) { + transaction.afterSelection = Selection.collapsed( + Position( + // table -> row -> cell -> paragraph + path: selection.end.path + [0, 0, 0], + ), + ); + } else { + transaction.afterSelection = Selection.collapsed( + Position(path: selection.end.path), + ); + } + transaction.selectionExtraInfo = {}; + await editorState.apply(transaction); + return; + } + } + + await editorState.insertBlockAfterCurrentSelection(selection, node); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart index 3e5d1b5e328e4..c09368ff954c2 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/add_block_toolbar_item.dart @@ -4,21 +4,14 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/presentation/base/type_option_menu_item.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/image/multi_image_block_component/multi_image_placeholder.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; -import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/tasks/app_widget.dart'; -import 'package:appflowy/workspace/presentation/home/menu/menu_shared_state.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:go_router/go_router.dart'; + +import 'add_block_menu_item_builder.dart'; @visibleForTesting const addBlockToolbarItemKey = ValueKey('add_block_toolbar_item'); @@ -94,323 +87,13 @@ class AddBlockMenu extends StatelessWidget { @override Widget build(BuildContext context) { + final builder = AddBlockMenuItemBuilder( + editorState: editorState, + selection: selection, + ); return TypeOptionMenu( - values: buildTypeOptionMenuItemValues(context), + values: builder.buildTypeOptionMenuItemValues(context), scaleFactor: context.scale, ); } - - Future _insertBlock(Node node) async { - AppGlobals.rootNavKey.currentContext?.pop(true); - Future.delayed( - const Duration(milliseconds: 100), - () async { - // if current selected block is a empty paragraph block, replace it with the new block. - if (selection.isCollapsed) { - final currentNode = editorState.getNodeAtPath(selection.end.path); - final text = currentNode?.delta?.toPlainText(); - if (currentNode != null && - currentNode.type == ParagraphBlockKeys.type && - text != null && - text.isEmpty) { - final transaction = editorState.transaction; - transaction.insertNode( - selection.end.path.next, - node, - ); - transaction.deleteNode(currentNode); - if (node.type == SimpleTableBlockKeys.type) { - transaction.afterSelection = Selection.collapsed( - Position( - // table -> row -> cell -> paragraph - path: selection.end.path + [0, 0, 0], - ), - ); - } else { - transaction.afterSelection = Selection.collapsed( - Position(path: selection.end.path), - ); - } - transaction.selectionExtraInfo = {}; - await editorState.apply(transaction); - return; - } - } - - await editorState.insertBlockAfterCurrentSelection(selection, node); - }, - ); - } - - List> buildTypeOptionMenuItemValues( - BuildContext context, - ) { - final colorMap = _colorMap(context); - return [ - // heading 1 - 3 - TypeOptionMenuItemValue( - value: HeadingBlockKeys.type, - backgroundColor: colorMap[HeadingBlockKeys.type]!, - text: LocaleKeys.editor_heading1.tr(), - icon: FlowySvgs.m_add_block_h1_s, - onTap: (_, __) => _insertBlock(headingNode(level: 1)), - ), - TypeOptionMenuItemValue( - value: HeadingBlockKeys.type, - backgroundColor: colorMap[HeadingBlockKeys.type]!, - text: LocaleKeys.editor_heading2.tr(), - icon: FlowySvgs.m_add_block_h2_s, - onTap: (_, __) => _insertBlock(headingNode(level: 2)), - ), - TypeOptionMenuItemValue( - value: HeadingBlockKeys.type, - backgroundColor: colorMap[HeadingBlockKeys.type]!, - text: LocaleKeys.editor_heading3.tr(), - icon: FlowySvgs.m_add_block_h3_s, - onTap: (_, __) => _insertBlock(headingNode(level: 3)), - ), - - // paragraph - TypeOptionMenuItemValue( - value: ParagraphBlockKeys.type, - backgroundColor: colorMap[ParagraphBlockKeys.type]!, - text: LocaleKeys.editor_text.tr(), - icon: FlowySvgs.m_add_block_paragraph_s, - onTap: (_, __) => _insertBlock(paragraphNode()), - ), - - // checkbox - TypeOptionMenuItemValue( - value: TodoListBlockKeys.type, - backgroundColor: colorMap[TodoListBlockKeys.type]!, - text: LocaleKeys.editor_checkbox.tr(), - icon: FlowySvgs.m_add_block_checkbox_s, - onTap: (_, __) => _insertBlock(todoListNode(checked: false)), - ), - - // table - TypeOptionMenuItemValue( - value: SimpleTableBlockKeys.type, - backgroundColor: colorMap[SimpleTableBlockKeys.type]!, - text: LocaleKeys.editor_table.tr(), - icon: FlowySvgs.slash_menu_icon_simple_table_s, - onTap: (_, __) => _insertBlock( - createSimpleTableBlockNode(columnCount: 2, rowCount: 2), - ), - ), - - // quote - TypeOptionMenuItemValue( - value: QuoteBlockKeys.type, - backgroundColor: colorMap[QuoteBlockKeys.type]!, - text: LocaleKeys.editor_quote.tr(), - icon: FlowySvgs.m_add_block_quote_s, - onTap: (_, __) => _insertBlock(quoteNode()), - ), - - // bulleted list, numbered list, toggle list - TypeOptionMenuItemValue( - value: BulletedListBlockKeys.type, - backgroundColor: colorMap[BulletedListBlockKeys.type]!, - text: LocaleKeys.editor_bulletedListShortForm.tr(), - icon: FlowySvgs.m_add_block_bulleted_list_s, - onTap: (_, __) => _insertBlock(bulletedListNode()), - ), - TypeOptionMenuItemValue( - value: NumberedListBlockKeys.type, - backgroundColor: colorMap[NumberedListBlockKeys.type]!, - text: LocaleKeys.editor_numberedListShortForm.tr(), - icon: FlowySvgs.m_add_block_numbered_list_s, - onTap: (_, __) => _insertBlock(numberedListNode()), - ), - TypeOptionMenuItemValue( - value: ToggleListBlockKeys.type, - backgroundColor: colorMap[ToggleListBlockKeys.type]!, - text: LocaleKeys.editor_toggleListShortForm.tr(), - icon: FlowySvgs.m_add_block_toggle_s, - onTap: (_, __) => _insertBlock(toggleListBlockNode()), - ), - - // toggle headings - TypeOptionMenuItemValue( - value: ToggleListBlockKeys.type, - backgroundColor: colorMap[ToggleListBlockKeys.type]!, - text: LocaleKeys.editor_toggleHeading1ShortForm.tr(), - icon: FlowySvgs.toggle_heading1_s, - iconPadding: const EdgeInsets.all(3), - onTap: (_, __) => _insertBlock(toggleHeadingNode()), - ), - TypeOptionMenuItemValue( - value: ToggleListBlockKeys.type, - backgroundColor: colorMap[ToggleListBlockKeys.type]!, - text: LocaleKeys.editor_toggleHeading2ShortForm.tr(), - icon: FlowySvgs.toggle_heading2_s, - iconPadding: const EdgeInsets.all(3), - onTap: (_, __) => _insertBlock(toggleHeadingNode(level: 2)), - ), - TypeOptionMenuItemValue( - value: ToggleListBlockKeys.type, - backgroundColor: colorMap[ToggleListBlockKeys.type]!, - text: LocaleKeys.editor_toggleHeading3ShortForm.tr(), - icon: FlowySvgs.toggle_heading3_s, - iconPadding: const EdgeInsets.all(3), - onTap: (_, __) => _insertBlock(toggleHeadingNode(level: 3)), - ), - - // image - TypeOptionMenuItemValue( - value: ImageBlockKeys.type, - backgroundColor: colorMap[ImageBlockKeys.type]!, - text: LocaleKeys.editor_image.tr(), - icon: FlowySvgs.m_add_block_image_s, - onTap: (_, __) async { - AppGlobals.rootNavKey.currentContext?.pop(true); - Future.delayed(const Duration(milliseconds: 400), () async { - final imagePlaceholderKey = GlobalKey(); - await editorState.insertEmptyImageBlock(imagePlaceholderKey); - }); - }, - ), - TypeOptionMenuItemValue( - value: MultiImageBlockKeys.type, - backgroundColor: colorMap[ImageBlockKeys.type]!, - text: LocaleKeys.document_plugins_photoGallery_name.tr(), - icon: FlowySvgs.m_add_block_photo_gallery_s, - onTap: (_, __) async { - AppGlobals.rootNavKey.currentContext?.pop(true); - Future.delayed(const Duration(milliseconds: 400), () async { - final imagePlaceholderKey = GlobalKey(); - await editorState.insertEmptyMultiImageBlock(imagePlaceholderKey); - }); - }, - ), - TypeOptionMenuItemValue( - value: FileBlockKeys.type, - backgroundColor: colorMap[ImageBlockKeys.type]!, - text: LocaleKeys.document_plugins_file_name.tr(), - icon: FlowySvgs.media_s, - onTap: (_, __) async { - AppGlobals.rootNavKey.currentContext?.pop(true); - Future.delayed(const Duration(milliseconds: 400), () async { - final fileGlobalKey = GlobalKey(); - await editorState.insertEmptyFileBlock(fileGlobalKey); - }); - }, - ), - - // date - TypeOptionMenuItemValue( - value: ParagraphBlockKeys.type, - backgroundColor: colorMap[MentionBlockKeys.type]!, - text: LocaleKeys.editor_date.tr(), - icon: FlowySvgs.m_add_block_date_s, - onTap: (_, __) => _insertBlock(dateMentionNode()), - ), - // page - TypeOptionMenuItemValue( - value: ParagraphBlockKeys.type, - backgroundColor: colorMap[MentionBlockKeys.type]!, - text: LocaleKeys.editor_page.tr(), - icon: FlowySvgs.icon_document_s, - onTap: (_, __) async { - AppGlobals.rootNavKey.currentContext?.pop(true); - - final currentViewId = getIt().latestOpenView?.id; - final view = await showPageSelectorSheet( - context, - currentViewId: currentViewId, - ); - - if (view != null) { - Future.delayed(const Duration(milliseconds: 100), () { - editorState.insertBlockAfterCurrentSelection( - selection, - pageMentionNode(view.id), - ); - }); - } - }, - ), - - // divider - TypeOptionMenuItemValue( - value: DividerBlockKeys.type, - backgroundColor: colorMap[DividerBlockKeys.type]!, - text: LocaleKeys.editor_divider.tr(), - icon: FlowySvgs.m_add_block_divider_s, - onTap: (_, __) { - AppGlobals.rootNavKey.currentContext?.pop(true); - Future.delayed(const Duration(milliseconds: 100), () { - editorState.insertDivider(selection); - }); - }, - ), - - // callout, code, math equation - TypeOptionMenuItemValue( - value: CalloutBlockKeys.type, - backgroundColor: colorMap[CalloutBlockKeys.type]!, - text: LocaleKeys.document_plugins_callout.tr(), - icon: FlowySvgs.m_add_block_callout_s, - onTap: (_, __) => _insertBlock(calloutNode()), - ), - TypeOptionMenuItemValue( - value: CodeBlockKeys.type, - backgroundColor: colorMap[CodeBlockKeys.type]!, - text: LocaleKeys.editor_codeBlockShortForm.tr(), - icon: FlowySvgs.m_add_block_code_s, - onTap: (_, __) => _insertBlock(codeBlockNode()), - ), - TypeOptionMenuItemValue( - value: MathEquationBlockKeys.type, - backgroundColor: colorMap[MathEquationBlockKeys.type]!, - text: LocaleKeys.editor_mathEquationShortForm.tr(), - icon: FlowySvgs.m_add_block_formula_s, - onTap: (_, __) { - AppGlobals.rootNavKey.currentContext?.pop(true); - Future.delayed(const Duration(milliseconds: 100), () { - editorState.insertMathEquation(selection); - }); - }, - ), - ]; - } - - Map _colorMap(BuildContext context) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - if (isDarkMode) { - return { - HeadingBlockKeys.type: const Color(0xFF5465A1), - ParagraphBlockKeys.type: const Color(0xFF5465A1), - TodoListBlockKeys.type: const Color(0xFF4BB299), - SimpleTableBlockKeys.type: const Color(0xFF4BB299), - QuoteBlockKeys.type: const Color(0xFFBAAC74), - BulletedListBlockKeys.type: const Color(0xFFA35F94), - NumberedListBlockKeys.type: const Color(0xFFA35F94), - ToggleListBlockKeys.type: const Color(0xFFA35F94), - ImageBlockKeys.type: const Color(0xFFBAAC74), - MentionBlockKeys.type: const Color(0xFF40AAB8), - DividerBlockKeys.type: const Color(0xFF4BB299), - CalloutBlockKeys.type: const Color(0xFF66599B), - CodeBlockKeys.type: const Color(0xFF66599B), - MathEquationBlockKeys.type: const Color(0xFF66599B), - }; - } - return { - HeadingBlockKeys.type: const Color(0xFFBECCFF), - ParagraphBlockKeys.type: const Color(0xFFBECCFF), - TodoListBlockKeys.type: const Color(0xFF98F4CD), - SimpleTableBlockKeys.type: const Color(0xFF98F4CD), - QuoteBlockKeys.type: const Color(0xFFFDEDA7), - BulletedListBlockKeys.type: const Color(0xFFFFB9EF), - NumberedListBlockKeys.type: const Color(0xFFFFB9EF), - ToggleListBlockKeys.type: const Color(0xFFFFB9EF), - ImageBlockKeys.type: const Color(0xFFFDEDA7), - MentionBlockKeys.type: const Color(0xFF91EAF5), - DividerBlockKeys.type: const Color(0xFF98F4CD), - CalloutBlockKeys.type: const Color(0xFFCABDFF), - CodeBlockKeys.type: const Color(0xFFCABDFF), - MathEquationBlockKeys.type: const Color(0xFFCABDFF), - }; - } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart index 108579d213263..fa35c6460fd06 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_cell_block_component.dart @@ -2,6 +2,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.da import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; import 'package:universal_platform/universal_platform.dart'; @@ -546,9 +547,17 @@ class SimpleTableCellBlockWidgetState extends State isReorderingHitCellNotifier.value = isHitCurrentCell; if (isHitCurrentCell) { if (isReorderingColumn) { - simpleTableContext.isReorderingHitIndex.value = node.columnIndex; + if (simpleTableContext.isReorderingHitIndex.value != node.columnIndex) { + HapticFeedback.lightImpact(); + + simpleTableContext.isReorderingHitIndex.value = node.columnIndex; + } } else if (isReorderingRow) { - simpleTableContext.isReorderingHitIndex.value = node.rowIndex; + if (simpleTableContext.isReorderingHitIndex.value != node.rowIndex) { + HapticFeedback.lightImpact(); + + simpleTableContext.isReorderingHitIndex.value = node.rowIndex; + } } } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_action_sheet.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_action_sheet.dart index fe19b539771c3..46329e643f7c0 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_action_sheet.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_action_sheet.dart @@ -3,6 +3,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_tab import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_feedback.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:provider/provider.dart'; class SimpleTableMobileDraggableReorderButton extends StatelessWidget { @@ -46,6 +47,8 @@ class SimpleTableMobileDraggableReorderButton extends StatelessWidget { } void _startDragging() { + HapticFeedback.lightImpact(); + isShowingMenu.value = true; editorState.selection = null; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_feedback.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_feedback.dart index 355a35fadc1bf..2467d4539508a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_feedback.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/simple_table/simple_table_widgets/simple_table_feedback.dart @@ -65,6 +65,7 @@ class _SimpleTableFeedbackState extends State { @override Widget build(BuildContext context) { return Material( + color: Colors.transparent, child: Provider.value( value: widget.editorState, child: SimpleTableWidget(