diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart index be877ea1ca..c6d74ad1ee 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -286,12 +286,15 @@ class _AppFlowyEditorPageState extends State { textDecorationMobileToolbarItem, buildTextAndBackgroundColorMobileToolbarItem(), headingMobileToolbarItem, - customListMobileToolbarItem, + mobileBlocksToolbarItem, linkMobileToolbarItem, dividerMobileToolbarItem, imageMobileToolbarItem, mathEquationMobileToolbarItem, codeMobileToolbarItem, + mobileAlignToolbarItem, + mobileIndentToolbarItem, + mobileOutdentToolbarItem, undoMobileToolbarItem, redoMobileToolbarItem, ], diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/mobile_image_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/mobile_image_toolbar_item.dart index 10a756c02f..7d5c95ea51 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/mobile_image_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/mobile_image_toolbar_item.dart @@ -5,7 +5,7 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; final imageMobileToolbarItem = MobileToolbarItem.action( - itemIcon: const FlowySvg(FlowySvgs.m_toolbar_imae_lg), + itemIconBuilder: (_, __) => const FlowySvg(FlowySvgs.m_toolbar_imae_lg), actionHandler: (editorState, selection) async { final imagePlaceholderKey = GlobalKey(); await editorState.insertEmptyImageBlock(imagePlaceholderKey); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/mobile_math_equation_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/mobile_math_equation_toolbar_item.dart index 5e7f7dcc77..0c97ccf0ae 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/mobile_math_equation_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/mobile_math_equation_toolbar_item.dart @@ -4,7 +4,8 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; final mathEquationMobileToolbarItem = MobileToolbarItem.action( - itemIcon: const SizedBox(width: 22, child: FlowySvg(FlowySvgs.math_lg)), + itemIconBuilder: (_, __) => + const SizedBox(width: 22, child: FlowySvg(FlowySvgs.math_lg)), actionHandler: (editorState, selection) async { if (!selection.isCollapsed) { return; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_align_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_align_toolbar_item.dart new file mode 100644 index 0000000000..6171c362e3 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_align_toolbar_item.dart @@ -0,0 +1,103 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +final mobileAlignToolbarItem = MobileToolbarItem.withMenu( + itemIconBuilder: (_, editorState) { + return onlyShowInTextType(editorState) + ? const FlowySvg( + FlowySvgs.toolbar_align_center_s, + size: Size.square(32), + ) + : null; + }, + itemMenuBuilder: (editorState, selection, _) { + return _MobileAlignMenu( + editorState: editorState, + selection: selection, + ); + }, +); + +class _MobileAlignMenu extends StatelessWidget { + const _MobileAlignMenu({ + required this.editorState, + required this.selection, + }); + + final Selection selection; + final EditorState editorState; + + @override + Widget build(BuildContext context) { + return GridView.count( + crossAxisCount: 3, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + childAspectRatio: 3, + shrinkWrap: true, + children: [ + _buildAlignmentButton( + context, + 'left', + LocaleKeys.document_plugins_optionAction_left.tr(), + ), + _buildAlignmentButton( + context, + 'center', + LocaleKeys.document_plugins_optionAction_center.tr(), + ), + _buildAlignmentButton( + context, + 'right', + LocaleKeys.document_plugins_optionAction_right.tr(), + ), + ], + ); + } + + Widget _buildAlignmentButton( + BuildContext context, + String alignment, + String label, + ) { + final nodes = editorState.getNodesInSelection(selection); + if (nodes.isEmpty) { + const SizedBox.shrink(); + } + + bool isSatisfyCondition(bool Function(Object? value) test) { + return nodes.every( + (n) => test(n.attributes[blockComponentAlign]), + ); + } + + final data = switch (alignment) { + 'left' => FlowySvgs.toolbar_align_left_s, + 'center' => FlowySvgs.toolbar_align_center_s, + 'right' => FlowySvgs.toolbar_align_right_s, + _ => throw UnimplementedError(), + }; + final isSelected = isSatisfyCondition((value) => value == alignment); + + return MobileToolbarItemMenuBtn( + icon: FlowySvg(data, size: const Size.square(28)), + label: FlowyText(label), + isSelected: isSelected, + onPressed: () async { + await editorState.updateNode( + selection, + (node) => node.copyWith( + attributes: { + ...node.attributes, + blockComponentAlign: alignment, + }, + ), + ); + }, + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/list_mobile_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_toolbar_item.dart similarity index 95% rename from frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/list_mobile_toolbar_item.dart rename to frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_toolbar_item.dart index d1f633eb60..8cece8ec97 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/list_mobile_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_blocks_toolbar_item.dart @@ -6,8 +6,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; -final customListMobileToolbarItem = MobileToolbarItem.withMenu( - itemIcon: const AFMobileIcon(afMobileIcons: AFMobileIcons.list), +final mobileBlocksToolbarItem = MobileToolbarItem.withMenu( + itemIconBuilder: (_, __) => + const AFMobileIcon(afMobileIcons: AFMobileIcons.list), itemMenuBuilder: (editorState, selection, _) { return _MobileListMenu( editorState: editorState, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_indent_toolbar_items.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_indent_toolbar_items.dart new file mode 100644 index 0000000000..fb07a38a25 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_indent_toolbar_items.dart @@ -0,0 +1,24 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +final mobileIndentToolbarItem = MobileToolbarItem.action( + itemIconBuilder: (_, editorState) { + return onlyShowInTextType(editorState) + ? const Icon(Icons.format_indent_increase_rounded) + : null; + }, + actionHandler: (editorState, selection) { + indentCommand.execute(editorState); + }, +); + +final mobileOutdentToolbarItem = MobileToolbarItem.action( + itemIconBuilder: (_, editorState) { + return onlyShowInTextType(editorState) + ? const Icon(Icons.format_indent_decrease_rounded) + : null; + }, + actionHandler: (editorState, selection) { + outdentCommand.execute(editorState); + }, +); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart index f1f816f8a0..c019d6a5f9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/plugins.dart @@ -26,7 +26,9 @@ export 'inline_math_equation/inline_math_equation.dart'; export 'inline_math_equation/inline_math_equation_toolbar_item.dart'; export 'math_equation/math_equation_block_component.dart'; export 'math_equation/mobile_math_equation_toolbar_item.dart'; -export 'mobile_toolbar_item/list_mobile_toolbar_item.dart'; +export 'mobile_toolbar_item/mobile_align_toolbar_item.dart'; +export 'mobile_toolbar_item/mobile_blocks_toolbar_item.dart'; +export 'mobile_toolbar_item/mobile_indent_toolbar_items.dart'; export 'openai/widgets/auto_completion_node_widget.dart'; export 'openai/widgets/smart_edit_node_widget.dart'; export 'openai/widgets/smart_edit_toolbar_item.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/redo_mobile_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/redo_mobile_toolbar_item.dart index 99b29f9b42..abd445dbc8 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/redo_mobile_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/redo_mobile_toolbar_item.dart @@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; final redoMobileToolbarItem = MobileToolbarItem.action( - itemIcon: const FlowySvg(FlowySvgs.m_redo_m), + itemIconBuilder: (_, __) => const FlowySvg(FlowySvgs.m_redo_m), actionHandler: (editorState, selection) async { editorState.undoManager.redo(); }, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/undo_mobile_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/undo_mobile_toolbar_item.dart index cf132c1486..bc9d27aeb7 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/undo_mobile_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/undo_mobile_toolbar_item.dart @@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; final undoMobileToolbarItem = MobileToolbarItem.action( - itemIcon: const FlowySvg(FlowySvgs.m_undo_m), + itemIconBuilder: (_, __) => const FlowySvg(FlowySvgs.m_undo_m), actionHandler: (editorState, selection) async { editorState.undoManager.undo(); }, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart index 402e4a5c9c..eab6c10fe3 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart @@ -7,6 +7,7 @@ import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart'; import 'package:appflowy/util/google_font_family_extension.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:collection/collection.dart'; +import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; @@ -264,6 +265,19 @@ class EditorStyleCustomizer { ); } + // customize the link on mobile + final href = attributes[AppFlowyRichTextKeys.href] as String?; + if (PlatformExtension.isMobile && href != null) { + return TextSpan( + style: textSpan.style, + text: text.text, + recognizer: TapGestureRecognizer() + ..onTap = () { + safeLaunchUrl(href); + }, + ); + } + return defaultTextSpanDecoratorForAttribute( context, node, diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 26506fe39d..3ae7a7b318 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -54,8 +54,8 @@ packages: dependency: "direct main" description: path: "." - ref: "50117b6" - resolved-ref: "50117b6900e4b239603ee48f6f3e7b7bc603c865" + ref: "009115d" + resolved-ref: "009115da836616e9fb2d0abd327753809a78b983" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "1.5.2" diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 0692d31898..3c86e546f3 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -47,7 +47,7 @@ dependencies: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: 50117b6 + ref: 009115d appflowy_popover: path: packages/appflowy_popover