From e1e8747f15fcbff623d2f0ab0b4e3c39f03e53b1 Mon Sep 17 00:00:00 2001 From: Mathias Mogensen <42929161+Xazin@users.noreply.github.com> Date: Thu, 2 May 2024 02:10:56 +0200 Subject: [PATCH] feat: support mention page on mobile (#5158) * feat: support mention page on mobile * chore: clean up toggle notifier * fix: changes after merge * fix: depends on inherited widget error * fix: amend after merge * feat: add icon to search * chore: slight style changes * chore: revert podfile change * ci: fix disposal --- .../database/widgets/row/row_document.dart | 7 +- .../lib/plugins/document/document.dart | 3 +- .../lib/plugins/document/document_page.dart | 26 ++-- .../collaborator_avater_stack.dart | 11 +- .../presentation/editor_configuration.dart | 95 ++++-------- .../presentation/editor_notification.dart | 34 ++--- .../document/presentation/editor_page.dart | 66 ++++----- .../editor_plugins/mention/mention_block.dart | 8 +- .../mention/mention_page_block.dart | 99 ++++++++++--- .../mention/mobile_page_selector_sheet.dart | 135 ++++++++++++++++++ .../add_block_toolbar_item.dart | 73 +++++++--- .../appflowy_mobile_toolbar.dart | 22 +-- .../mobile_toolbar_v3/util.dart | 53 +++---- .../document/presentation/editor_style.dart | 77 +++------- .../lib/startup/tasks/app_widget.dart | 4 +- .../command_palette/command_palette.dart | 39 ++--- frontend/resources/translations/en.json | 6 + 17 files changed, 425 insertions(+), 333 deletions(-) create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart index 6c5a9b1910..4d58d6fc32 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/row/row_document.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/database/grid/application/row/row_document_bloc.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; @@ -8,7 +10,6 @@ import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; 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'; class RowDocument extends StatelessWidget { @@ -82,9 +83,7 @@ class _RowEditorState extends State { @override Widget build(BuildContext context) { return MultiBlocProvider( - providers: [ - BlocProvider.value(value: documentBloc), - ], + providers: [BlocProvider.value(value: documentBloc)], child: BlocListener( listenWhen: (previous, current) => previous.isDocumentEmpty != current.isDocumentEmpty, diff --git a/frontend/appflowy_flutter/lib/plugins/document/document.dart b/frontend/appflowy_flutter/lib/plugins/document/document.dart index e1501d6dbf..13242bbe0a 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document.dart @@ -1,5 +1,7 @@ library document_plugin; +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; @@ -20,7 +22,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.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'; import 'package:flutter_bloc/flutter_bloc.dart'; class DocumentPluginBuilder extends PluginBuilder { diff --git a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart index f1e88de628..231ff37bc9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/document_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/document_page.dart @@ -1,3 +1,5 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; @@ -16,7 +18,6 @@ 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'; class DocumentPage extends StatefulWidget { @@ -153,9 +154,9 @@ class _DocumentPageState extends State onRestore: () => context.read().add( const DocumentEvent.restorePage(), ), - onDelete: () => context.read().add( - const DocumentEvent.deletePermanently(), - ), + onDelete: () => context + .read() + .add(const DocumentEvent.deletePermanently()), ); } @@ -178,12 +179,10 @@ class _DocumentPageState extends State node: page, editorState: editorState, view: widget.view, - onIconChanged: (icon) async { - await ViewBackendService.updateViewIcon( - viewId: widget.view.id, - viewIcon: icon, - ); - }, + onIconChanged: (icon) async => ViewBackendService.updateViewIcon( + viewId: widget.view.id, + viewIcon: icon, + ), ); } @@ -196,10 +195,9 @@ class _DocumentPageState extends State undoCommand.execute(editorState); } else if (type == EditorNotificationType.redo) { redoCommand.execute(editorState); - } else if (type == EditorNotificationType.exitEditing) { - if (editorState.selection != null) { - editorState.selection = null; - } + } else if (type == EditorNotificationType.exitEditing && + editorState.selection != null) { + editorState.selection = null; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/collaborator_avater_stack.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/collaborator_avater_stack.dart index 2324c8bf08..72913a68ed 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/collaborator_avater_stack.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/collaborator_avater_stack.dart @@ -1,6 +1,7 @@ +import 'package:flutter/material.dart'; + import 'package:avatar_stack/avatar_stack.dart'; import 'package:avatar_stack/positions.dart'; -import 'package:flutter/material.dart'; class CollaboratorAvatarStack extends StatelessWidget { const CollaboratorAvatarStack({ @@ -17,21 +18,13 @@ class CollaboratorAvatarStack extends StatelessWidget { }); final List avatars; - final Positions? settings; - final InfoWidgetBuilder? infoWidgetBuilder; - final double? width; - final double? height; - final double? borderWidth; - final Color? borderColor; - final Color? backgroundColor; - final Widget Function(int value, BorderSide border) plusWidgetBuilder; @override 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 1c860e5906..8f18cb1d58 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_configuration.dart @@ -1,3 +1,6 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_page.dart'; @@ -10,8 +13,6 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_editor_plugins/appflowy_editor_plugins.dart'; import 'package:easy_localization/easy_localization.dart' hide TextDirection; import 'package:flowy_infra/theme_extension.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; Map getEditorBuilderMap({ @@ -23,25 +24,17 @@ Map getEditorBuilderMap({ ShowPlaceholder? showParagraphPlaceholder, String Function(Node)? placeholderText, }) { - final standardActions = [ - OptionAction.delete, - OptionAction.duplicate, - // OptionAction.divider, - // OptionAction.moveUp, - // OptionAction.moveDown, - ]; + final standardActions = [OptionAction.delete, OptionAction.duplicate]; final calloutBGColor = AFThemeExtension.of(context).calloutBGColor; final configuration = BlockComponentConfiguration( // use EdgeInsets.zero to remove the default padding. - padding: (node) { + padding: (_) { if (PlatformExtension.isMobile) { final pageStyle = context.read().state; final factor = pageStyle.fontLayout.factor; final padding = pageStyle.lineHeightLayout.padding * factor; - return EdgeInsets.only( - top: padding, - ); + return EdgeInsets.only(top: padding); } return const EdgeInsets.symmetric(vertical: 5.0); @@ -62,10 +55,7 @@ Map getEditorBuilderMap({ placeholderText: (_) => LocaleKeys.blockPlaceholders_todoList.tr(), ), iconBuilder: PlatformExtension.isMobile - ? (context, node, onCheck) => TodoListIcon( - node: node, - onCheck: onCheck, - ) + ? (_, node, onCheck) => TodoListIcon(node: node, onCheck: onCheck) : null, toggleChildrenTriggers: [ LogicalKeyboardKey.shift, @@ -78,9 +68,7 @@ Map getEditorBuilderMap({ placeholderText: (_) => LocaleKeys.blockPlaceholders_bulletList.tr(), ), iconBuilder: PlatformExtension.isMobile - ? (context, node) => BulletedListIcon( - node: node, - ) + ? (_, node) => BulletedListIcon(node: node) : null, ), NumberedListBlockKeys.type: NumberedListBlockComponentBuilder( @@ -88,10 +76,8 @@ Map getEditorBuilderMap({ placeholderText: (_) => LocaleKeys.blockPlaceholders_numberList.tr(), ), iconBuilder: PlatformExtension.isMobile - ? (context, node, textDirection) => NumberedListIcon( - node: node, - textDirection: textDirection, - ) + ? (_, node, textDirection) => + NumberedListIcon(node: node, textDirection: textDirection) : null, ), QuoteBlockKeys.type: QuoteBlockComponentBuilder( @@ -108,9 +94,7 @@ Map getEditorBuilderMap({ final headingPaddings = pageStyle.lineHeightLayout.headingPaddings .map((e) => e * factor); final level = node.attributes[HeadingBlockKeys.level] ?? 6; - return EdgeInsets.only( - top: headingPaddings.elementAt(level), - ); + return EdgeInsets.only(top: headingPaddings.elementAt(level)); } return const EdgeInsets.only(top: 12.0, bottom: 4.0); @@ -128,10 +112,7 @@ Map getEditorBuilderMap({ Positioned( top: 0, right: 10, - child: ImageMenu( - node: node, - state: state, - ), + child: ImageMenu(node: node, state: state), ), ), TableBlockKeys.type: TableBlockComponentBuilder( @@ -188,14 +169,12 @@ Map getEditorBuilderMap({ DividerBlockKeys.type: DividerBlockComponentBuilder( configuration: configuration, height: 28.0, - wrapper: (context, node, child) { - return MobileBlockActionButtons( - showThreeDots: false, - node: node, - editorState: editorState, - child: child, - ); - }, + wrapper: (_, node, child) => MobileBlockActionButtons( + showThreeDots: false, + node: node, + editorState: editorState, + child: child, + ), ), MathEquationBlockKeys.type: MathEquationBlockComponentBuilder( configuration: configuration, @@ -223,10 +202,7 @@ Map getEditorBuilderMap({ configuration: configuration.copyWith( placeholderTextStyle: (_) => styleCustomizer.outlineBlockPlaceholderStyleBuilder(), - padding: (_) => const EdgeInsets.only( - top: 12.0, - bottom: 4.0, - ), + padding: (_) => const EdgeInsets.only(top: 12.0, bottom: 4.0), ), ), LinkPreviewBlockKeys.type: LinkPreviewBlockComponentBuilder( @@ -238,12 +214,9 @@ Map getEditorBuilderMap({ menuBuilder: (context, node, state) => Positioned( top: 10, right: 0, - child: LinkPreviewMenu( - node: node, - state: state, - ), + child: LinkPreviewMenu(node: node, state: state), ), - builder: (context, node, url, title, description, imageUrl) => + builder: (_, node, url, title, description, imageUrl) => CustomLinkPreviewWidget( node: node, url: url, @@ -283,27 +256,11 @@ Map getEditorBuilderMap({ ToggleListBlockKeys.type, ]; - final supportAlignBuilderType = [ - ImageBlockKeys.type, - ]; - - final supportDepthBuilderType = [ - OutlineBlockKeys.type, - ]; - - final colorAction = [ - OptionAction.divider, - OptionAction.color, - ]; - - final alignAction = [ - OptionAction.divider, - OptionAction.align, - ]; - - final depthAction = [ - OptionAction.depth, - ]; + final supportAlignBuilderType = [ImageBlockKeys.type]; + final supportDepthBuilderType = [OutlineBlockKeys.type]; + final colorAction = [OptionAction.divider, OptionAction.color]; + final alignAction = [OptionAction.divider, OptionAction.align]; + final depthAction = [OptionAction.depth]; final List actions = [ ...standardActions, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_notification.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_notification.dart index 9ec6090b5b..109a9e9915 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_notification.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_notification.dart @@ -1,46 +1,30 @@ -import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; -enum EditorNotificationType { - none, - undo, - redo, - exitEditing, -} +import 'package:appflowy_editor/appflowy_editor.dart'; + +enum EditorNotificationType { none, undo, redo, exitEditing } class EditorNotification { - const EditorNotification({ - required this.type, - }); + 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, - ); + PropertyValueNotifier(EditorNotificationType.none); final EditorNotificationType type; - void post() { - _notifier.value = type; - } + void post() => _notifier.value = type; static void addListener(ValueChanged listener) { - _notifier.addListener(() { - listener(_notifier.value); - }); + _notifier.addListener(() => listener(_notifier.value)); } static void removeListener(ValueChanged listener) { - _notifier.removeListener(() { - listener(_notifier.value); - }); + _notifier.removeListener(() => listener(_notifier.value)); } - static void dispose() { - _notifier.dispose(); - } + 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 fd7af4fd18..2a6e0ca917 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -1,5 +1,8 @@ import 'dart:ui' as ui; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_configuration.dart'; @@ -26,8 +29,6 @@ import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; final codeBlockLocalization = CodeBlockLocalizations( @@ -210,9 +211,7 @@ class _AppFlowyEditorPageState extends State { if (widget.useViewInfoBloc) { viewInfoBloc.add( - ViewInfoEvent.registerEditorState( - editorState: widget.editorState, - ), + ViewInfoEvent.registerEditorState(editorState: widget.editorState), ); } @@ -326,24 +325,24 @@ class _AppFlowyEditorPageState extends State { final editorState = widget.editorState; if (PlatformExtension.isMobile) { - return AppFlowyMobileToolbar( - toolbarHeight: 42.0, - editorState: editorState, - toolbarItemsBuilder: (selection) => buildMobileToolbarItems( - editorState, - selection, - ), - child: MobileFloatingToolbar( + return BlocProvider.value( + value: documentBloc, + child: AppFlowyMobileToolbar( + toolbarHeight: 42.0, editorState: editorState, - editorScrollController: editorScrollController, - toolbarBuilder: (context, anchor, closeToolbar) { - return CustomMobileFloatingToolbar( + toolbarItemsBuilder: (selection) => + buildMobileToolbarItems(editorState, selection), + child: MobileFloatingToolbar( + editorState: editorState, + editorScrollController: editorScrollController, + toolbarBuilder: (_, anchor, closeToolbar) => + CustomMobileFloatingToolbar( editorState: editorState, anchor: anchor, closeToolbar: closeToolbar, - ); - }, - child: editor, + ), + child: editor, + ), ), ); } @@ -362,9 +361,8 @@ class _AppFlowyEditorPageState extends State { List _customSlashMenuItems() { final items = [...standardSelectionMenuItems]; - final imageItem = items.firstWhereOrNull( - (element) => element.name == AppFlowyEditorL10n.current.image, - ); + final imageItem = items + .firstWhereOrNull((e) => e.name == AppFlowyEditorL10n.current.image); if (imageItem != null) { final imageItemIndex = items.indexOf(imageItem); if (imageItemIndex != -1) { @@ -393,15 +391,11 @@ class _AppFlowyEditorPageState extends State { (bool, Selection?) _computeAutoFocusParameters() { if (widget.editorState.document.isEmpty) { - return ( - true, - Selection.collapsed(Position(path: [0])), - ); + return (true, Selection.collapsed(Position(path: [0]))); } - final nodes = widget.editorState.document.root.children - .where((element) => element.delta != null); - final isAllEmpty = - nodes.isNotEmpty && nodes.every((element) => element.delta!.isEmpty); + final nodes = + widget.editorState.document.root.children.where((e) => e.delta != null); + final isAllEmpty = nodes.isNotEmpty && nodes.every((e) => e.delta!.isEmpty); if (isAllEmpty) { return (true, Selection.collapsed(Position(path: nodes.first.path))); } @@ -458,12 +452,8 @@ class _AppFlowyEditorPageState extends State { } void _customizeBlockComponentBackgroundColorDecorator() { - blockComponentBackgroundColorDecorator = - (Node node, String colorString) => buildEditorCustomizedColor( - context, - node, - colorString, - ); + blockComponentBackgroundColorDecorator = (Node node, String colorString) => + buildEditorCustomizedColor(context, node, colorString); } void _initEditorL10n() => AppFlowyEditorL10n.current = EditorI18n(); @@ -528,7 +518,5 @@ bool showInAnyTextType(EditorState editorState) { } final nodes = editorState.getNodesInSelection(selection); - return nodes.any( - (node) => toolbarItemWhiteList.contains(node.type), - ); + return nodes.any((node) => toolbarItemWhiteList.contains(node.type)); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart index 00793adb4b..29007ada98 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_block.dart @@ -1,9 +1,10 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_date_block.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart'; import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/reminder_selector.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; enum MentionType { @@ -69,18 +70,21 @@ class MentionBlock extends StatelessWidget { @override Widget build(BuildContext context) { final type = MentionType.fromString(mention[MentionBlockKeys.type]); + final editorState = context.read(); switch (type) { case MentionType.page: final String pageId = mention[MentionBlockKeys.pageId]; return MentionPageBlock( key: ValueKey(pageId), + editorState: editorState, pageId: pageId, + node: node, textStyle: textStyle, + index: index, ); case MentionType.date: final String date = mention[MentionBlockKeys.date]; - final editorState = context.read(); final reminderOption = ReminderOption.values.firstWhereOrNull( (o) => o.name == mention[MentionBlockKeys.reminderOption], ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart index 34d392883f..af58ce48af 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart @@ -1,6 +1,11 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/mobile/application/mobile_router.dart'; import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; +import 'package:appflowy/plugins/document/application/document_bloc.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart'; import 'package:appflowy/plugins/trash/application/trash_service.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart'; @@ -9,26 +14,58 @@ import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/protobuf.dart'; import 'package:appflowy_editor/appflowy_editor.dart' - show EditorState, PlatformExtension; + show + Delta, + EditorState, + Node, + PlatformExtension, + TextInsert, + TextTransaction, + paragraphNode; import 'package:collection/collection.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:provider/provider.dart'; final pageMemorizer = {}; +Node pageMentionNode(String viewId) { + return paragraphNode( + delta: Delta( + operations: [ + TextInsert( + '\$', + attributes: { + MentionBlockKeys.mention: { + MentionBlockKeys.type: MentionType.page.name, + MentionBlockKeys.pageId: viewId, + }, + }, + ), + ], + ), + ); +} + class MentionPageBlock extends StatefulWidget { const MentionPageBlock({ super.key, + required this.editorState, required this.pageId, + required this.node, required this.textStyle, + required this.index, }); + final EditorState editorState; final String pageId; + final Node node; final TextStyle? textStyle; + // Used to update the block + final int index; + @override State createState() => _MentionPageBlockState(); } @@ -71,14 +108,15 @@ class _MentionPageBlockState extends State { if (view == null) { return const SizedBox.shrink(); } - // updateSelection(); + final iconSize = widget.textStyle?.fontSize ?? 16.0; return Padding( padding: const EdgeInsets.symmetric(horizontal: 2), child: FlowyHover( cursor: SystemMouseCursors.click, child: GestureDetector( - onTap: () => openPage(widget.pageId), + onTap: handleTap, + onDoubleTap: handleDoubleTap, behavior: HitTestBehavior.translucent, child: Row( mainAxisSize: MainAxisSize.min, @@ -112,23 +150,50 @@ class _MentionPageBlockState extends State { ); } - void openPage(String pageId) async { - final view = await fetchView(pageId); + Future handleTap() async { + final view = await fetchView(widget.pageId); if (view == null) { - Log.error('Page($pageId) not found'); + Log.error('Page(${widget.pageId}) not found'); return; } - if (PlatformExtension.isDesktopOrWeb) { - getIt().add( - TabsEvent.openPlugin( - plugin: view.plugin(), - view: view, - ), - ); + + if (PlatformExtension.isMobile && mounted) { + await context.pushView(view); } else { - if (mounted) { - await context.pushView(view); - } + getIt().add( + TabsEvent.openPlugin(plugin: view.plugin(), view: view), + ); + } + } + + Future handleDoubleTap() async { + if (!PlatformExtension.isMobile) { + return; + } + + final currentViewId = context.read().documentId; + final viewId = await showPageSelectorSheet( + context, + currentViewId: currentViewId, + selectedViewId: widget.pageId, + ); + + if (viewId != null) { + // Update this nodes pageId + final transaction = widget.editorState.transaction + ..formatText( + widget.node, + widget.index, + 1, + { + MentionBlockKeys.mention: { + MentionBlockKeys.type: MentionType.page.name, + MentionBlockKeys.pageId: viewId, + }, + }, + ); + + await widget.editorState.apply(transaction, withUpdateSelection: false); } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart new file mode 100644 index 0000000000..d15d24aab7 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mention/mobile_page_selector_sheet.dart @@ -0,0 +1,135 @@ +import 'package:flutter/material.dart'; + +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/flowy_search_text_field.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/show_mobile_bottom_sheet.dart'; +import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; +import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; +import 'package:appflowy/workspace/application/view/view_ext.dart'; +import 'package:appflowy/workspace/application/view/view_service.dart'; +import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/style_widget/text.dart'; + +Future showPageSelectorSheet( + BuildContext context, { + String? currentViewId, + String? selectedViewId, +}) async { + return showMobileBottomSheet( + context, + title: LocaleKeys.document_mobilePageSelector_title.tr(), + showHeader: true, + showCloseButton: true, + showDragHandle: true, + builder: (context) => Container( + margin: const EdgeInsets.only(top: 12.0), + constraints: const BoxConstraints( + maxHeight: 340, + minHeight: 80, + ), + child: _MobilePageSelectorBody( + currentViewId: currentViewId, + selectedViewId: selectedViewId, + ), + ), + ); +} + +class _MobilePageSelectorBody extends StatefulWidget { + const _MobilePageSelectorBody({this.currentViewId, this.selectedViewId}); + + final String? currentViewId; + final String? selectedViewId; + + @override + State<_MobilePageSelectorBody> createState() => + _MobilePageSelectorBodyState(); +} + +class _MobilePageSelectorBodyState extends State<_MobilePageSelectorBody> { + final searchController = TextEditingController(); + late final Future> _viewsFuture = _fetchViews(); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + margin: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), + height: 44.0, + child: FlowySearchTextField( + controller: searchController, + onChanged: (_) => setState(() {}), + ), + ), + FutureBuilder( + future: _viewsFuture, + builder: (_, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator.adaptive()); + } + + if (snapshot.hasError || snapshot.data == null) { + return Center( + child: FlowyText( + LocaleKeys.document_mobilePageSelector_failedToLoad.tr(), + ), + ); + } + + final views = snapshot.data!; + if (widget.currentViewId != null) { + views.removeWhere((v) => v.id == widget.currentViewId); + } + + final filtered = views.where( + (v) => + searchController.text.isEmpty || + v.name + .toLowerCase() + .contains(searchController.text.toLowerCase()), + ); + + if (filtered.isEmpty) { + return Center( + child: FlowyText( + LocaleKeys.document_mobilePageSelector_noPagesFound.tr(), + ), + ); + } + + return Flexible( + child: ListView( + children: filtered + .map( + (view) => FlowyOptionTile.checkbox( + leftIcon: view.icon.value.isNotEmpty + ? EmojiText( + emoji: view.icon.value, + fontSize: 18, + textAlign: TextAlign.center, + lineHeight: 1.3, + ) + : FlowySvg( + view.layout.icon, + size: const Size.square(20), + ), + text: view.name, + isSelected: view.id == widget.selectedViewId, + onTap: () => Navigator.of(context).pop(view.id), + ), + ) + .toList(), + ), + ); + }, + ), + ], + ); + } + + Future> _fetchViews() async => + (await ViewBackendService.getAllViews()).toNullable()?.items ?? []; +} 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 d05f88da65..2a046165a2 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 @@ -6,8 +6,11 @@ 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/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.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'; @@ -15,6 +18,7 @@ import 'package:appflowy/startup/tasks/app_widget.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_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; final addBlockToolbarItem = AppFlowyMobileToolbarItem( @@ -41,15 +45,12 @@ final addBlockToolbarItem = AppFlowyMobileToolbarItem( keepEditorFocusNotifier.increase(); final didAddBlock = await showAddBlockMenu( AppGlobals.rootNavKey.currentContext!, + documentBloc: context.read(), editorState: editorState, selection: selection!, ); if (didAddBlock != true) { - unawaited( - editorState.updateSelectionWithReason( - selection, - ), - ); + unawaited(editorState.updateSelectionWithReason(selection)); } }); }, @@ -59,6 +60,7 @@ final addBlockToolbarItem = AppFlowyMobileToolbarItem( Future showAddBlockMenu( BuildContext context, { + required DocumentBloc documentBloc, required EditorState editorState, required Selection selection, }) async { @@ -73,15 +75,16 @@ Future showAddBlockMenu( backgroundColor: theme.toolbarMenuBackgroundColor, elevation: 20, enableDraggableScrollable: true, - builder: (context) { - return Padding( - padding: EdgeInsets.all(16 * context.scale), + builder: (_) => Padding( + padding: EdgeInsets.all(16 * context.scale), + child: BlocProvider.value( + value: documentBloc, child: _AddBlockMenu( selection: selection, editorState: editorState, ), - ); - }, + ), + ), ); } @@ -96,20 +99,21 @@ class _AddBlockMenu extends StatelessWidget { @override Widget build(BuildContext context) { - return TypeOptionMenu( - values: buildTypeOptionMenuItemValues(context), - scaleFactor: context.scale, + return BlocProvider.value( + value: context.read(), + child: TypeOptionMenu( + values: buildTypeOptionMenuItemValues(context), + scaleFactor: context.scale, + ), ); } Future _insertBlock(Node node) async { AppGlobals.rootNavKey.currentContext?.pop(true); - Future.delayed(const Duration(milliseconds: 100), () { - editorState.insertBlockAfterCurrentSelection( - selection, - node, - ); - }); + Future.delayed( + const Duration(milliseconds: 100), + () => editorState.insertBlockAfterCurrentSelection(selection, node), + ); } List> buildTypeOptionMenuItemValues( @@ -208,11 +212,36 @@ class _AddBlockMenu extends StatelessWidget { // date TypeOptionMenuItemValue( value: ParagraphBlockKeys.type, - backgroundColor: colorMap['date']!, + 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.document_s, + onTap: (_, __) async { + AppGlobals.rootNavKey.currentContext?.pop(true); + + final currentViewId = context.read().documentId; + final viewId = await showPageSelectorSheet( + context, + currentViewId: currentViewId, + ); + + if (viewId != null) { + Future.delayed(const Duration(milliseconds: 100), () { + editorState.insertBlockAfterCurrentSelection( + selection, + pageMentionNode(viewId), + ); + }); + } + }, + ), // divider TypeOptionMenuItemValue( @@ -270,7 +299,7 @@ class _AddBlockMenu extends StatelessWidget { NumberedListBlockKeys.type: const Color(0xFFA35F94), ToggleListBlockKeys.type: const Color(0xFFA35F94), ImageBlockKeys.type: const Color(0xFFBAAC74), - 'date': const Color(0xFF40AAB8), + MentionBlockKeys.type: const Color(0xFF40AAB8), DividerBlockKeys.type: const Color(0xFF4BB299), CalloutBlockKeys.type: const Color(0xFF66599B), CodeBlockKeys.type: const Color(0xFF66599B), @@ -286,7 +315,7 @@ class _AddBlockMenu extends StatelessWidget { NumberedListBlockKeys.type: const Color(0xFFFFB9EF), ToggleListBlockKeys.type: const Color(0xFFFFB9EF), ImageBlockKeys.type: const Color(0xFFFDEDA7), - 'date': const Color(0xFF91EAF5), + MentionBlockKeys.type: const Color(0xFF91EAF5), DividerBlockKeys.type: const Color(0xFF98F4CD), CalloutBlockKeys.type: const Color(0xFFCABDFF), CodeBlockKeys.type: const Color(0xFFCABDFF), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart index bf7d7d098f..ae23a83925 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/appflowy_mobile_toolbar.dart @@ -1,6 +1,10 @@ import 'dart:async'; import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import 'package:appflowy/plugins/document/application/document_bloc.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_close_keyboard_or_menu_button.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/mobile_toolbar_v3/appflowy_mobile_toolbar_item.dart'; @@ -8,8 +12,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_too import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:collection/collection.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; @@ -66,9 +69,7 @@ class _AppFlowyMobileToolbarState extends State { Widget build(BuildContext context) { return Column( children: [ - Expanded( - child: widget.child, - ), + Expanded(child: widget.child), // add a bottom offset to make sure the toolbar is above the keyboard ValueListenableBuilder( valueListenable: isKeyboardShow, @@ -108,10 +109,13 @@ class _AppFlowyMobileToolbarState extends State { } return RepaintBoundary( - child: _MobileToolbar( - editorState: widget.editorState, - toolbarItems: widget.toolbarItemsBuilder(selection), - toolbarHeight: widget.toolbarHeight, + child: BlocProvider.value( + value: context.read(), + child: _MobileToolbar( + editorState: widget.editorState, + toolbarItems: widget.toolbarItemsBuilder(selection), + toolbarHeight: widget.toolbarHeight, + ), ), ); }, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart index 07983ab078..a016a0863f 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/util.dart @@ -1,8 +1,9 @@ +import 'package:flutter/material.dart'; + import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_v3/aa_menu/_toolbar_theme.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/material.dart'; class MobileToolbarMenuItemWrapper extends StatelessWidget { const MobileToolbarMenuItemWrapper({ @@ -66,10 +67,7 @@ class MobileToolbarMenuItemWrapper extends StatelessWidget { final radius = Radius.circular(12 * scale); final Widget child; if (icon != null) { - child = FlowySvg( - icon!, - color: iconColor, - ); + child = FlowySvg(icon!, color: iconColor); } else if (text != null) { child = Padding( padding: textPadding * scale, @@ -137,16 +135,12 @@ class ScaledVerticalDivider extends StatelessWidget { @override Widget build(BuildContext context) { - return HSpace( - 1.5 * context.scale, - ); + return HSpace(1.5 * context.scale); } } class ScaledVSpace extends StatelessWidget { - const ScaledVSpace({ - super.key, - }); + const ScaledVSpace({super.key}); @override Widget build(BuildContext context) { @@ -166,10 +160,7 @@ final _blocksCanContainChildren = [ ]; extension MobileToolbarEditorState on EditorState { - bool isBlockTypeSelected( - String blockType, { - int? level, - }) { + bool isBlockTypeSelected(String blockType, {int? level}) { final selection = this.selection; if (selection == null) { return false; @@ -186,9 +177,7 @@ extension MobileToolbarEditorState on EditorState { return type == blockType; } - bool isTextDecorationSelected( - String richTextKey, - ) { + bool isTextDecorationSelected(String richTextKey) { final selection = this.selection; if (selection == null) { return false; @@ -203,22 +192,20 @@ extension MobileToolbarEditorState on EditorState { if (selection.startIndex != 0) { // get previous index text style isSelected = nodes.allSatisfyInSelection( - selection.copyWith( - start: selection.start.copyWith( - offset: selection.startIndex - 1, - ), - ), (delta) { - return delta.everyAttributes( + selection.copyWith( + start: selection.start.copyWith( + offset: selection.startIndex - 1, + ), + ), + (delta) => delta.everyAttributes( (attributes) => attributes[richTextKey] == true, - ); - }); + ), + ); } } } else { isSelected = nodes.allSatisfyInSelection(selection, (delta) { - return delta.everyAttributes( - (attributes) => attributes[richTextKey] == true, - ); + return delta.everyAttributes((attr) => attr[richTextKey] == true); }); } return isSelected; @@ -321,9 +308,7 @@ extension MobileToolbarEditorState on EditorState { text.isNotEmpty && selection.isCollapsed) { final attributes = href != null && href.isNotEmpty - ? { - AppFlowyRichTextKeys.href: href, - } + ? {AppFlowyRichTextKeys.href: href} : null; transaction.insertText( node, @@ -348,9 +333,7 @@ extension MobileToolbarEditorState on EditorState { node, selection.startIndex, text.length, - { - AppFlowyRichTextKeys.href: href?.isEmpty == true ? null : href, - }, + {AppFlowyRichTextKeys.href: href?.isEmpty == true ? null : href}, ); } 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 ef5031fa69..e7e4f750de 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart @@ -1,5 +1,8 @@ import 'dart:math'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; + import 'package:appflowy/core/helpers/url_launcher.dart'; import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart'; import 'package:appflowy/plugins/document/application/document_appearance_cubit.dart'; @@ -13,17 +16,12 @@ import 'package:appflowy/workspace/application/settings/appearance/appearance_cu import 'package:appflowy/workspace/application/settings/appearance/base_appearance.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:go_router/go_router.dart'; import 'package:google_fonts/google_fonts.dart'; class EditorStyleCustomizer { - EditorStyleCustomizer({ - required this.context, - required this.padding, - }); + EditorStyleCustomizer({required this.context, required this.padding}); final BuildContext context; final EdgeInsets padding; @@ -63,9 +61,7 @@ class EditorStyleCustomizer { bold: baseTextStyle(fontFamily, fontWeight: FontWeight.bold).copyWith( fontWeight: FontWeight.w600, ), - italic: baseTextStyle(fontFamily).copyWith( - fontStyle: FontStyle.italic, - ), + italic: baseTextStyle(fontFamily).copyWith(fontStyle: FontStyle.italic), underline: baseTextStyle(fontFamily).copyWith( decoration: TextDecoration.underline, ), @@ -110,15 +106,9 @@ class EditorStyleCustomizer { color: theme.colorScheme.onBackground, height: lineHeight, ), - bold: baseTextStyle.copyWith( - fontWeight: FontWeight.w600, - ), - italic: baseTextStyle.copyWith( - fontStyle: FontStyle.italic, - ), - underline: baseTextStyle.copyWith( - decoration: TextDecoration.underline, - ), + bold: baseTextStyle.copyWith(fontWeight: FontWeight.w600), + italic: baseTextStyle.copyWith(fontStyle: FontStyle.italic), + underline: baseTextStyle.copyWith(decoration: TextDecoration.underline), strikethrough: baseTextStyle.copyWith( decoration: TextDecoration.lineThrough, ), @@ -175,25 +165,23 @@ class EditorStyleCustomizer { } TextStyle codeBlockStyleBuilder() { - final theme = Theme.of(context); final fontSize = context.read().state.fontSize; final fontFamily = context.read().state.codeFontFamily; return baseTextStyle(fontFamily).copyWith( fontSize: fontSize, height: 1.5, - color: theme.colorScheme.onBackground, + color: Theme.of(context).colorScheme.onBackground, ); } TextStyle outlineBlockPlaceholderStyleBuilder() { - final theme = Theme.of(context); final fontSize = context.read().state.fontSize; return TextStyle( fontFamily: builtInFontFamily(), fontSize: fontSize, height: 1.5, - color: theme.colorScheme.onBackground.withOpacity(0.6), + color: Theme.of(context).colorScheme.onBackground.withOpacity(0.6), ); } @@ -220,38 +208,22 @@ class EditorStyleCustomizer { ); } - FloatingToolbarStyle floatingToolbarStyleBuilder() { - final theme = Theme.of(context); - return FloatingToolbarStyle( - backgroundColor: theme.colorScheme.onTertiary, - ); - } - - TextStyle baseTextStyle( - String? fontFamily, { - FontWeight? fontWeight, - }) { - if (fontFamily == null) { - return TextStyle( - fontWeight: fontWeight, + FloatingToolbarStyle floatingToolbarStyleBuilder() => FloatingToolbarStyle( + backgroundColor: Theme.of(context).colorScheme.onTertiary, ); + + TextStyle baseTextStyle(String? fontFamily, {FontWeight? fontWeight}) { + if (fontFamily == null) { + return TextStyle(fontWeight: fontWeight); } try { - return GoogleFonts.getFont( - fontFamily, - fontWeight: fontWeight, - ); + return GoogleFonts.getFont(fontFamily, fontWeight: fontWeight); } on Exception { if ([builtInFontFamily(), builtInCodeFontFamily].contains(fontFamily)) { - return TextStyle( - fontFamily: fontFamily, - fontWeight: fontWeight, - ); + return TextStyle(fontFamily: fontFamily, fontWeight: fontWeight); } - return TextStyle( - fontWeight: fontWeight, - ); + return TextStyle(fontWeight: fontWeight); } } @@ -281,7 +253,7 @@ class EditorStyleCustomizer { ), ); } - } catch (e) { + } catch (_) { // ignore } } @@ -334,18 +306,13 @@ class EditorStyleCustomizer { ..onTap = () { final editorState = context.read(); if (editorState.selection == null) { - afLaunchUrlString( - href, - addingHttpSchemeWhenFailed: true, - ); + afLaunchUrlString(href, addingHttpSchemeWhenFailed: true); return; } editorState.updateSelectionWithReason( editorState.selection, - extraInfo: { - selectionExtraInfoDisableMobileToolbarKey: true, - }, + extraInfo: {selectionExtraInfoDisableMobileToolbarKey: true}, ); showEditLinkBottomSheet( diff --git a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart index 305bda78b1..ad1fc542f5 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/app_widget.dart @@ -204,9 +204,9 @@ class _ApplicationWidgetState extends State { ), child: overlayManagerBuilder( context, - FeatureFlag.search.isOn + !PlatformExtension.isMobile && FeatureFlag.search.isOn ? CommandPalette( - toggleNotifier: _commandPaletteNotifier, + notifier: _commandPaletteNotifier, child: child, ) : child, diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/command_palette.dart b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/command_palette.dart index b289132309..187cbaf544 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/command_palette.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/command_palette/command_palette.dart @@ -16,18 +16,12 @@ class CommandPalette extends InheritedWidget { CommandPalette({ super.key, required Widget? child, - required ValueNotifier toggleNotifier, - }) : _toggleNotifier = toggleNotifier, - super( - child: _CommandPaletteController( - toggleNotifier: toggleNotifier, - child: child, - ), + required this.notifier, + }) : super( + child: _CommandPaletteController(notifier: notifier, child: child), ); - final ValueNotifier _toggleNotifier; - - void toggle() => _toggleNotifier.value = !_toggleNotifier.value; + final ValueNotifier notifier; static CommandPalette of(BuildContext context) { final CommandPalette? result = @@ -38,6 +32,8 @@ class CommandPalette extends InheritedWidget { return result!; } + void toggle() => notifier.value = !notifier.value; + @override bool updateShouldNotify(covariant InheritedWidget oldWidget) => false; } @@ -48,12 +44,12 @@ class _ToggleCommandPaletteIntent extends Intent { class _CommandPaletteController extends StatefulWidget { const _CommandPaletteController({ - required this.toggleNotifier, required this.child, + required this.notifier, }); final Widget? child; - final ValueNotifier toggleNotifier; + final ValueNotifier notifier; @override State<_CommandPaletteController> createState() => @@ -61,26 +57,9 @@ class _CommandPaletteController extends StatefulWidget { } class _CommandPaletteControllerState extends State<_CommandPaletteController> { - late ValueNotifier _toggleNotifier = widget.toggleNotifier; + late final ValueNotifier _toggleNotifier = widget.notifier; bool _isOpen = false; - @override - void didUpdateWidget(covariant _CommandPaletteController oldWidget) { - if (oldWidget.toggleNotifier != widget.toggleNotifier) { - _toggleNotifier.removeListener(_onToggle); - _toggleNotifier.dispose(); - _toggleNotifier = widget.toggleNotifier; - - // If widget is changed, eg. on theme mode hotkey used - // while modal is shown, set the value before listening - _toggleNotifier.value = _isOpen; - - _toggleNotifier.addListener(_onToggle); - } - - super.didUpdateWidget(oldWidget); - } - @override void initState() { super.initState(); diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 8ab3124939..fa691016ca 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -1082,6 +1082,11 @@ "errorBlock": { "theBlockIsNotSupported": "The current version does not support this block.", "blockContentHasBeenCopied": "The block content has been copied." + }, + "mobilePageSelector": { + "title": "Select page", + "failedToLoad": "Failed to load page list", + "noPagesFound": "No pages found" } }, "board": { @@ -1328,6 +1333,7 @@ "color": "Color", "image": "Image", "date": "Date", + "page": "Page", "italic": "Italic", "link": "Link", "numberedList": "Numbered List",