diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar_actions.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar_actions.dart index 2a35ab24cc..2341513c25 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar_actions.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/app_bar_actions.dart @@ -77,7 +77,7 @@ class AppBarDoneButton extends StatelessWidget { Widget build(BuildContext context) { return AppBarButton( onTap: onTap, - padding: const EdgeInsets.fromLTRB(12, 12, 8, 12), + padding: const EdgeInsets.all(12), child: FlowyText( LocaleKeys.button_done.tr(), color: Theme.of(context).colorScheme.primary, @@ -93,7 +93,7 @@ class AppBarSaveButton extends StatelessWidget { super.key, required this.onTap, this.enable = true, - this.padding = const EdgeInsets.fromLTRB(12, 12, 8, 12), + this.padding = const EdgeInsets.all(12), }); final VoidCallback onTap; @@ -165,7 +165,7 @@ class AppBarMoreButton extends StatelessWidget { @override Widget build(BuildContext context) { return AppBarButton( - padding: const EdgeInsets.fromLTRB(12, 12, 8, 12), + padding: const EdgeInsets.all(12), onTap: () => onTap(context), child: const FlowySvg(FlowySvgs.three_dots_s), ); diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart index 5ed50a2f1a..c7c9758ed9 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/base/mobile_view_page.dart @@ -4,7 +4,7 @@ import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_state_container.dart'; import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; -import 'package:appflowy/plugins/document/document_page.dart'; +import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/user/application/reminder/reminder_bloc.dart'; import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart'; @@ -144,6 +144,8 @@ class _MobileViewPageState extends State { Widget _buildAppBarMoreButton(ViewPB view) { return AppBarMoreButton( onTap: (context) { + EditorNotification.exitEditing().post(); + showMobileBottomSheet( context, showDragHandle: true, @@ -183,14 +185,12 @@ class _MobileViewPageState extends State { context.read().add(FavoriteEvent.toggle(view)); break; case MobileViewBottomSheetBodyAction.undo: - context.dispatchNotification( - const EditorNotification(type: EditorNotificationType.redo), - ); + EditorNotification.undo().post(); context.pop(); break; case MobileViewBottomSheetBodyAction.redo: + EditorNotification.redo().post(); context.pop(); - context.dispatchNotification(EditorNotification.redo()); break; case MobileViewBottomSheetBodyAction.helpCenter: // unimplemented diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart new file mode 100644 index 0000000000..c9d8a48e6b --- /dev/null +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart @@ -0,0 +1,82 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class BottomSheetCloseButton extends StatelessWidget { + const BottomSheetCloseButton({ + super.key, + this.onTap, + }); + + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap ?? () => Navigator.pop(context), + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + width: 18, + height: 18, + child: FlowySvg( + FlowySvgs.m_bottom_sheet_close_m, + ), + ), + ), + ); + } +} + +class BottomSheetDoneButton extends StatelessWidget { + const BottomSheetDoneButton({ + super.key, + this.onDone, + }); + + final VoidCallback? onDone; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onDone ?? () => Navigator.pop(context), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12.0), + child: FlowyText( + LocaleKeys.button_done.tr(), + color: Theme.of(context).colorScheme.primary, + fontWeight: FontWeight.w500, + textAlign: TextAlign.right, + ), + ), + ); + } +} + +class BottomSheetBackButton extends StatelessWidget { + const BottomSheetBackButton({ + super.key, + this.onTap, + }); + + final VoidCallback? onTap; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap ?? () => Navigator.pop(context), + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), + child: SizedBox( + width: 18, + height: 18, + child: FlowySvg( + FlowySvgs.m_app_bar_back_s, + ), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_header.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_header.dart index e09b13268c..e1bc32a6f0 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_header.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_header.dart @@ -1,6 +1,4 @@ -import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; -import 'package:easy_localization/easy_localization.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; @@ -26,7 +24,7 @@ class BottomSheetHeader extends StatelessWidget { left: 0, child: Align( alignment: Alignment.centerLeft, - child: AppBarCloseButton( + child: BottomSheetCloseButton( onTap: onClose, ), ), @@ -41,19 +39,8 @@ class BottomSheetHeader extends StatelessWidget { if (onDone != null) Align( alignment: Alignment.centerRight, - child: FlowyButton( - useIntrinsicWidth: true, - margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 5), - decoration: const BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(10)), - color: Color(0xFF00BCF0), - ), - text: FlowyText.medium( - LocaleKeys.button_done.tr(), - color: Colors.white, - fontSize: 16.0, - ), - onTap: onDone, + child: BottomSheetDoneButton( + onDone: onDone, ), ), ], diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart index a240f32178..8a51a08176 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart @@ -1,4 +1,4 @@ -import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart'; import 'package:appflowy/plugins/base/drag_handler.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart' hide WidgetBuilder; import 'package:flutter/material.dart'; @@ -195,12 +195,12 @@ class BottomSheetHeader extends StatelessWidget { if (showBackButton) const Align( alignment: Alignment.centerLeft, - child: AppBarBackButton(), + child: BottomSheetBackButton(), ), if (showCloseButton) const Align( alignment: Alignment.centerLeft, - child: AppBarCloseButton(), + child: BottomSheetCloseButton(), ), Align( child: FlowyText( @@ -212,8 +212,8 @@ class BottomSheetHeader extends StatelessWidget { if (showDoneButton) Align( alignment: Alignment.centerRight, - child: AppBarDoneButton( - onTap: () => Navigator.pop(context), + child: BottomSheetDoneButton( + onDone: () => Navigator.pop(context), ), ), ], diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart index cdb54ec122..6dc45c1c40 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/setting/widgets/mobile_setting_item_widget.dart @@ -43,6 +43,7 @@ class MobileSettingItem extends StatelessWidget { trailing: trailing, onTap: onTap, visualDensity: VisualDensity.compact, + contentPadding: const EdgeInsets.only(left: 8.0), ), ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart index 8cc60eaf7c..4c9b3267dc 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart @@ -1,8 +1,7 @@ -import 'package:flutter/material.dart'; - import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/doc_bloc.dart'; import 'package:appflowy/plugins/document/presentation/banner.dart'; +import 'package:appflowy/plugins/document/presentation/editor_notification.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; @@ -15,24 +14,9 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart' hide Log; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/widget/error_page.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -enum EditorNotificationType { - undo, - redo, -} - -class EditorNotification extends Notification { - const EditorNotification({ - required this.type, - }); - - EditorNotification.undo() : type = EditorNotificationType.undo; - EditorNotification.redo() : type = EditorNotificationType.redo; - - final EditorNotificationType type; -} - class DocumentPage extends StatefulWidget { const DocumentPage({ super.key, @@ -50,12 +34,23 @@ class DocumentPage extends StatefulWidget { } class _DocumentPageState extends State { + EditorState? editorState; + @override void initState() { super.initState(); // The appflowy editor use Intl as localization, set the default language as fallback. Intl.defaultLocale = 'en_US'; + + EditorNotification.addListener(_onEditorNotification); + } + + @override + void dispose() { + EditorNotification.removeListener(_onEditorNotification); + + super.dispose(); } @override @@ -75,6 +70,7 @@ class _DocumentPageState extends State { } final editorState = state.editorState; + this.editorState = editorState; final error = state.error; if (error != null || editorState == null) { Log.error(error); @@ -149,20 +145,19 @@ class _DocumentPageState extends State { ); } - // Future _exportPage(DocumentDataPB data) async { - // final picker = getIt(); - // final dir = await picker.getDirectoryPath(); - // if (dir == null) { - // return; - // } - // final path = p.join(dir, '${documentBloc.view.name}.json'); - // const encoder = JsonEncoder.withIndent(' '); - // final json = encoder.convert(data.toProto3Json()); - // await File(path).writeAsString(json.base64.base64); - // if (mounted) { - // showSnackBarMessage(context, 'Export success to $path'); - // } - // } + void _onEditorNotification(EditorNotificationType type) { + final editorState = this.editorState; + if (editorState == null) { + return; + } + if (type == EditorNotificationType.undo) { + undoCommand.execute(editorState); + } else if (type == EditorNotificationType.redo) { + redoCommand.execute(editorState); + } else if (type == EditorNotificationType.exitEditing) { + editorState.selection = null; + } + } void _onNotificationAction( BuildContext context, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_notification.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_notification.dart new file mode 100644 index 0000000000..9ec6090b5b --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_notification.dart @@ -0,0 +1,46 @@ +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; + +enum EditorNotificationType { + none, + undo, + redo, + exitEditing, +} + +class EditorNotification { + const EditorNotification({ + required this.type, + }); + + EditorNotification.undo() : type = EditorNotificationType.undo; + EditorNotification.redo() : type = EditorNotificationType.redo; + EditorNotification.exitEditing() : type = EditorNotificationType.exitEditing; + + static final PropertyValueNotifier _notifier = + PropertyValueNotifier( + EditorNotificationType.none, + ); + + final EditorNotificationType type; + + void post() { + _notifier.value = type; + } + + static void addListener(ValueChanged listener) { + _notifier.addListener(() { + listener(_notifier.value); + }); + } + + static void removeListener(ValueChanged listener) { + _notifier.removeListener(() { + listener(_notifier.value); + }); + } + + static void dispose() { + _notifier.dispose(); + } +} 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 02fb2065d0..0b3e881904 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -197,7 +197,7 @@ class _AppFlowyEditorPageState extends State { _initEditorL10n(); _initializeShortcuts(); - appFlowyEditorAutoScrollEdgeOffset = 250; + appFlowyEditorAutoScrollEdgeOffset = 220; indentableBlockTypes.add(ToggleListBlockKeys.type); convertibleBlockTypes.add(ToggleListBlockKeys.type); slashMenuItems = _customSlashMenuItems(); @@ -317,19 +317,12 @@ class _AppFlowyEditorPageState extends State { editorState: editorState, editorScrollController: editorScrollController, toolbarBuilder: (context, anchor, closeToolbar) { - return AdaptiveTextSelectionToolbar.editable( - clipboardStatus: ClipboardStatus.pasteable, - onCopy: () { - customCopyCommand.execute(editorState); - closeToolbar(); - }, - onCut: () => customCutCommand.execute(editorState), - onPaste: () => customPasteCommand.execute(editorState), - onSelectAll: () => selectAllCommand.execute(editorState), - onLiveTextInput: null, - onLookUp: null, - onSearchWeb: null, - onShare: null, + return AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: buildMobileFloatingToolbarItems( + editorState, + anchor, + closeToolbar, + ), anchors: TextSelectionToolbarAnchors( primaryAnchor: anchor, ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_floating_toolbar/custom_mobile_floating_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_floating_toolbar/custom_mobile_floating_toolbar.dart new file mode 100644 index 0000000000..5a8b99af5d --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_floating_toolbar/custom_mobile_floating_toolbar.dart @@ -0,0 +1,84 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +List buildMobileFloatingToolbarItems( + EditorState editorState, + Offset offset, + Function closeToolbar, +) { + // copy, paste, select, select all, cut + final selection = editorState.selection; + if (selection == null) { + return []; + } + final toolbarItems = []; + + if (!selection.isCollapsed) { + toolbarItems.add( + ContextMenuButtonItem( + label: LocaleKeys.editor_copy.tr(), + onPressed: () { + copyCommand.execute(editorState); + closeToolbar(); + }, + ), + ); + } + + toolbarItems.add( + ContextMenuButtonItem( + label: LocaleKeys.editor_paste.tr(), + onPressed: () { + pasteCommand.execute(editorState); + closeToolbar(); + }, + ), + ); + + if (!selection.isCollapsed) { + toolbarItems.add( + ContextMenuButtonItem( + label: LocaleKeys.editor_cut.tr(), + onPressed: () { + cutCommand.execute(editorState); + closeToolbar(); + }, + ), + ); + } + + toolbarItems.add( + ContextMenuButtonItem( + label: LocaleKeys.editor_select.tr(), + onPressed: () { + editorState.selectWord(offset); + closeToolbar(); + }, + ), + ); + + toolbarItems.add( + ContextMenuButtonItem( + label: LocaleKeys.editor_selectAll.tr(), + onPressed: () { + selectAllCommand.execute(editorState); + closeToolbar(); + }, + ), + ); + + return toolbarItems; +} + +extension on EditorState { + void selectWord(Offset offset) { + final node = service.selectionService.getNodeInOffset(offset); + final selection = node?.selectable?.getWordBoundaryInOffset(offset); + if (selection == null) { + return; + } + updateSelectionWithReason(selection); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_color_list.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_color_list.dart index 9ca8902143..c8498cd872 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_color_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/_color_list.dart @@ -9,6 +9,8 @@ import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +const _count = 6; + Future showTextColorAndBackgroundColorPicker( BuildContext context, { required EditorState editorState, @@ -26,7 +28,7 @@ Future showTextColorAndBackgroundColorPicker( backgroundColor: theme.toolbarMenuBackgroundColor, elevation: 20, title: LocaleKeys.grid_selectOption_colorPanelTitle.tr(), - padding: const EdgeInsets.fromLTRB(18, 4, 18, 8), + padding: const EdgeInsets.fromLTRB(10, 4, 10, 8), builder: (context) { return _TextColorAndBackgroundColor( editorState: editorState, @@ -79,6 +81,7 @@ class _TextColorAndBackgroundColorState fontSize: 14.0, ), ), + const VSpace(6.0), _TextColors( selectedColor: selectedTextColor?.tryToColor(), onSelectedColor: (textColor) async { @@ -115,6 +118,7 @@ class _TextColorAndBackgroundColorState fontSize: 14.0, ), ), + const VSpace(6.0), _BackgroundColors( selectedColor: selectedBackgroundColor?.tryToColor(), onSelectedColor: (backgroundColor) async { @@ -202,7 +206,7 @@ class _BackgroundColors extends StatelessWidget { @override Widget build(BuildContext context) { return GridView.count( - crossAxisCount: 6, + crossAxisCount: _count, shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), children: colors.mapIndexed( @@ -236,9 +240,7 @@ class _BackgroundColorItem extends StatelessWidget { return GestureDetector( onTap: onTap, child: Container( - margin: const EdgeInsets.all( - 6.0, - ), + margin: const EdgeInsets.all(6.0), decoration: BoxDecoration( color: color, borderRadius: Corners.s12Border, @@ -283,7 +285,7 @@ class _TextColors extends StatelessWidget { @override Widget build(BuildContext context) { return GridView.count( - crossAxisCount: 6, + crossAxisCount: _count, shrinkWrap: true, padding: EdgeInsets.zero, physics: const NeverScrollableScrollPhysics(), @@ -317,9 +319,7 @@ class _TextColorItem extends StatelessWidget { return GestureDetector( onTap: onTap, child: Container( - margin: const EdgeInsets.all( - 6.0, - ), + margin: const EdgeInsets.all(6.0), decoration: BoxDecoration( borderRadius: Corners.s12Border, border: Border.all( 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 6cfd92f221..2850dd3d1d 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 @@ -29,6 +29,7 @@ export 'link_preview/link_preview_cache.dart'; export 'link_preview/link_preview_menu.dart'; export 'math_equation/math_equation_block_component.dart'; export 'math_equation/mobile_math_equation_toolbar_item.dart'; +export 'mobile_floating_toolbar/custom_mobile_floating_toolbar.dart'; export 'mobile_toolbar_v3/aa_toolbar_item.dart'; export 'mobile_toolbar_v3/add_block_toolbar_item.dart'; export 'mobile_toolbar_v3/appflowy_mobile_toolbar.dart'; diff --git a/frontend/resources/flowy_icons/16x/m_bottom_sheet_close.svg b/frontend/resources/flowy_icons/16x/m_bottom_sheet_close.svg new file mode 100644 index 0000000000..9be5ee420e --- /dev/null +++ b/frontend/resources/flowy_icons/16x/m_bottom_sheet_close.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/resources/flowy_icons/24x/m_bottom_sheet_close.svg b/frontend/resources/flowy_icons/24x/m_bottom_sheet_close.svg new file mode 100644 index 0000000000..9be5ee420e --- /dev/null +++ b/frontend/resources/flowy_icons/24x/m_bottom_sheet_close.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index cdf07499e2..cd703d4a56 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1309,6 +1309,8 @@ "copy": "Copy", "paste": "Paste", "find": "Find", + "select": "Select", + "selectAll": "Select all", "previousMatch": "Previous match", "nextMatch": "Next match", "closeFind": "Close",