From afc6473582d02fce03dde6f7d3b8ead79515390f Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 8 Nov 2023 21:10:29 +0800 Subject: [PATCH] feat: adjust toggle list, callout, quote and divider on mobile (#3894) * feat: adjust toggle list block * feat: show block actions when tapping divider * feat: add toggle list and callout to toolbar * feat: refactor the emoji picker button * fix: toggle list integration tests --- .../document_with_toggle_list_test.dart | 23 +++- .../base/emoji/emoji_picker_screen.dart | 11 +- .../lib/plugins/base/icon/icon_picker.dart | 26 +++- .../plugins/base/icon/icon_picker_page.dart | 14 +- .../presentation/editor_configuration.dart | 16 ++- .../document/presentation/editor_page.dart | 4 +- .../actions/mobile_block_action_buttons.dart | 12 +- .../base/emoji_picker_button.dart | 75 +++++++---- .../callout/callout_block_component.dart | 2 +- .../error/error_block_component_builder.dart | 24 +++- .../header/document_header_node_widget.dart | 44 +++---- ...=> mobile_math_equation_toolbar_item.dart} | 0 .../list_mobile_toolbar_item.dart | 121 ++++++++++++++++++ .../presentation/editor_plugins/plugins.dart | 3 +- .../toggle/toggle_block_component.dart | 16 ++- .../lib/startup/tasks/generate_router.dart | 7 +- .../home/menu/view/view_item.dart | 4 +- frontend/appflowy_flutter/pubspec.lock | 4 +- frontend/appflowy_flutter/pubspec.yaml | 3 +- frontend/resources/translations/en.json | 7 +- 20 files changed, 308 insertions(+), 108 deletions(-) rename frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/{mobile_math_eqaution_toolbar_item.dart => mobile_math_equation_toolbar_item.dart} (100%) create mode 100644 frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/list_mobile_toolbar_item.dart diff --git a/frontend/appflowy_flutter/integration_test/document/document_with_toggle_list_test.dart b/frontend/appflowy_flutter/integration_test/document/document_with_toggle_list_test.dart index 50b84ed521..80bdcbe983 100644 --- a/frontend/appflowy_flutter/integration_test/document/document_with_toggle_list_test.dart +++ b/frontend/appflowy_flutter/integration_test/document/document_with_toggle_list_test.dart @@ -15,14 +15,23 @@ void main() { TestWidgetsFlutterBinding.ensureInitialized(); group('toggle list in document', () { + Finder findToggleListIcon({ + required bool isExpanded, + }) { + final turns = isExpanded ? 0.25 : 0.0; + return find.byWidgetPredicate( + (widget) => widget is AnimatedRotation && widget.turns == turns, + ); + } + void expectToggleListOpened() { - expect(find.byIcon(Icons.arrow_drop_down), findsOneWidget); - expect(find.byIcon(Icons.arrow_right), findsNothing); + expect(findToggleListIcon(isExpanded: true), findsOneWidget); + expect(findToggleListIcon(isExpanded: false), findsNothing); } void expectToggleListClosed() { - expect(find.byIcon(Icons.arrow_drop_down), findsNothing); - expect(find.byIcon(Icons.arrow_right), findsOneWidget); + expect(findToggleListIcon(isExpanded: false), findsOneWidget); + expect(findToggleListIcon(isExpanded: true), findsNothing); } testWidgets('convert > to toggle list, and click the icon to close it', @@ -63,7 +72,7 @@ void main() { expect(find.text(text2, findRichText: true), findsOneWidget); // Click the toggle list icon to close it - final toggleListIcon = find.byIcon(Icons.arrow_drop_down); + final toggleListIcon = find.byIcon(Icons.arrow_right); await tester.tapButton(toggleListIcon); // expect the toggle list to be closed @@ -88,7 +97,7 @@ void main() { await tester.ime.insertText('> $text'); // Click the toggle list icon to close it - final toggleListIcon = find.byIcon(Icons.arrow_drop_down); + final toggleListIcon = find.byIcon(Icons.arrow_right); await tester.tapButton(toggleListIcon); // Press the enter key @@ -164,7 +173,7 @@ void main() { // Press the enter key // Click the toggle list icon to close it - final toggleListIcon = find.byIcon(Icons.arrow_drop_down); + final toggleListIcon = find.byIcon(Icons.arrow_right); await tester.tapButton(toggleListIcon); await tester.editor.updateSelection( diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart index fa578bb132..6aaa307ef5 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker_screen.dart @@ -1,22 +1,21 @@ +import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/plugins/base/icon/icon_picker_page.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class MobileEmojiPickerScreen extends StatelessWidget { static const routeName = '/emoji_picker'; - static const viewId = 'id'; const MobileEmojiPickerScreen({ super.key, - required this.id, }); - /// view id - final String id; - @override Widget build(BuildContext context) { return IconPickerPage( - id: id, + onSelected: (result) { + context.pop(result); + }, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker.dart b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker.dart index e60555a1ef..6b27e5a865 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker.dart @@ -12,13 +12,23 @@ enum FlowyIconType { custom; } +class EmojiPickerResult { + const EmojiPickerResult( + this.type, + this.emoji, + ); + + final FlowyIconType type; + final String emoji; +} + class FlowyIconPicker extends StatefulWidget { const FlowyIconPicker({ super.key, required this.onSelected, }); - final void Function(FlowyIconType type, String value) onSelected; + final void Function(EmojiPickerResult result) onSelected; @override State createState() => _FlowyIconPickerState(); @@ -45,7 +55,12 @@ class _FlowyIconPickerState extends State const Spacer(), _RemoveIconButton( onTap: () { - widget.onSelected(FlowyIconType.icon, ''); + widget.onSelected( + const EmojiPickerResult( + FlowyIconType.icon, + '', + ), + ); }, ), ], @@ -58,7 +73,12 @@ class _FlowyIconPickerState extends State children: [ FlowyEmojiPicker( onEmojiSelected: (_, emoji) { - widget.onSelected(FlowyIconType.emoji, emoji); + widget.onSelected( + EmojiPickerResult( + FlowyIconType.emoji, + emoji, + ), + ); }, ), ], diff --git a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker_page.dart b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker_page.dart index 43cc4c67de..292910b6f4 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/icon/icon_picker_page.dart @@ -1,6 +1,5 @@ import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; import 'package:appflowy/plugins/base/icon/icon_picker.dart'; -import 'package:appflowy/workspace/application/view/view_service.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; @@ -8,11 +7,10 @@ import 'package:go_router/go_router.dart'; class IconPickerPage extends StatefulWidget { const IconPickerPage({ super.key, - required this.id, + required this.onSelected, }); - /// view id - final String id; + final void Function(EmojiPickerResult) onSelected; @override State createState() => _IconPickerPageState(); @@ -34,13 +32,7 @@ class _IconPickerPageState extends State { ), body: SafeArea( child: FlowyIconPicker( - onSelected: (_, emoji) { - ViewBackendService.updateViewIcon( - viewId: widget.id, - viewIcon: emoji, - ); - context.pop(); - }, + onSelected: widget.onSelected, ), ), ); 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 5618431420..74c83cfdaf 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,4 @@ +import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; @@ -26,6 +27,9 @@ Map getEditorBuilderMap({ final configuration = BlockComponentConfiguration( padding: (_) => const EdgeInsets.symmetric(vertical: 5.0), + indentPadding: (node, textDirection) => textDirection == TextDirection.ltr + ? const EdgeInsets.only(left: 26.0) + : const EdgeInsets.only(right: 26.0), ); final customBlockComponentBuilderMap = { @@ -119,6 +123,14 @@ Map getEditorBuilderMap({ DividerBlockKeys.type: DividerBlockComponentBuilder( configuration: configuration, height: 28.0, + wrapper: (context, node, child) { + return MobileBlockActionButtons( + showThreeDots: false, + node: node, + editorState: editorState, + child: child, + ); + }, ), MathEquationBlockKeys.type: MathEquationBlockComponentBuilder( configuration: configuration, @@ -146,9 +158,7 @@ Map getEditorBuilderMap({ ), ), errorBlockComponentBuilderKey: ErrorBlockComponentBuilder( - configuration: configuration.copyWith( - padding: (_) => const EdgeInsets.symmetric(vertical: 10), - ), + configuration: configuration, ), }; 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 542ff548ea..be877ea1ca 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -286,10 +286,8 @@ class _AppFlowyEditorPageState extends State { textDecorationMobileToolbarItem, buildTextAndBackgroundColorMobileToolbarItem(), headingMobileToolbarItem, - todoListMobileToolbarItem, - listMobileToolbarItem, + customListMobileToolbarItem, linkMobileToolbarItem, - quoteMobileToolbarItem, dividerMobileToolbarItem, imageMobileToolbarItem, mathEquationMobileToolbarItem, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart index b5fab17694..6738e8d028 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart @@ -21,6 +21,7 @@ class MobileBlockActionButtons extends StatelessWidget { const MobileBlockActionButtons({ super.key, this.extendActionWidgets = const [], + this.showThreeDots = true, required this.node, required this.editorState, required this.child, @@ -30,6 +31,7 @@ class MobileBlockActionButtons extends StatelessWidget { final EditorState editorState; final List extendActionWidgets; final Widget child; + final bool showThreeDots; @override Widget build(BuildContext context) { @@ -37,7 +39,15 @@ class MobileBlockActionButtons extends StatelessWidget { return child; } - const padding = 5.0; + if (!showThreeDots) { + return GestureDetector( + behavior: HitTestBehavior.opaque, + onTap: () => _showBottomSheet(context), + child: child, + ); + } + + const padding = 10.0; return Stack( children: [ child, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart index 2d4e48379b..61e505cf61 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart @@ -1,7 +1,11 @@ +import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; +import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; class EmojiPickerButton extends StatelessWidget { EmojiPickerButton({ @@ -15,20 +19,43 @@ class EmojiPickerButton extends StatelessWidget { final String emoji; final double emojiSize; final Size emojiPickerSize; - final void Function(String emoji, PopoverController controller) onSubmitted; + final void Function(String emoji, PopoverController? controller) onSubmitted; final PopoverController popoverController = PopoverController(); @override Widget build(BuildContext context) { - return AppFlowyPopover( - controller: popoverController, - triggerActions: PopoverTriggerFlags.click, - constraints: BoxConstraints.expand( - width: emojiPickerSize.width, - height: emojiPickerSize.height, - ), - popupBuilder: (context) => _buildEmojiPicker(), - child: FlowyTextButton( + if (PlatformExtension.isDesktopOrWeb) { + return AppFlowyPopover( + controller: popoverController, + triggerActions: PopoverTriggerFlags.click, + constraints: BoxConstraints.expand( + width: emojiPickerSize.width, + height: emojiPickerSize.height, + ), + popupBuilder: (context) => Container( + width: emojiPickerSize.width, + height: emojiPickerSize.height, + padding: const EdgeInsets.all(4.0), + child: EmojiSelectionMenu( + onSubmitted: (emoji) => onSubmitted(emoji, popoverController), + onExit: () {}, + ), + ), + child: FlowyTextButton( + emoji, + overflow: TextOverflow.visible, + fontSize: emojiSize, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(minWidth: 35.0), + fillColor: Colors.transparent, + mainAxisAlignment: MainAxisAlignment.center, + onPressed: () { + popoverController.show(); + }, + ), + ); + } else { + return FlowyTextButton( emoji, overflow: TextOverflow.visible, fontSize: emojiSize, @@ -36,22 +63,18 @@ class EmojiPickerButton extends StatelessWidget { constraints: const BoxConstraints(minWidth: 35.0), fillColor: Colors.transparent, mainAxisAlignment: MainAxisAlignment.center, - onPressed: () { - popoverController.show(); + onPressed: () async { + final result = await context.push( + MobileEmojiPickerScreen.routeName, + ); + if (result != null) { + onSubmitted( + result.emoji, + null, + ); + } }, - ), - ); - } - - Widget _buildEmojiPicker() { - return Container( - width: emojiPickerSize.width, - height: emojiPickerSize.height, - padding: const EdgeInsets.all(4.0), - child: EmojiSelectionMenu( - onSubmitted: (emoji) => onSubmitted(emoji, popoverController), - onExit: () {}, - ), - ); + ); + } } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart index 7ede37eb72..b8e48c8c30 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/callout/callout_block_component.dart @@ -187,7 +187,7 @@ class _CalloutBlockComponentWidgetState emoji: emoji, onSubmitted: (emoji, controller) { setEmoji(emoji); - controller.close(); + controller?.close(); }, ), ), diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart index a50895d298..914a4bef57 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/error/error_block_component_builder.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/mobile_block_action_buttons.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/copy_and_paste/clipboard_service.dart'; import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/workspace/presentation/home/toast.dart'; @@ -8,6 +9,7 @@ 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 ErrorBlockComponentBuilder extends BlockComponentBuilder { ErrorBlockComponentBuilder({ @@ -72,11 +74,15 @@ class _DividerBlockComponentWidgetState extends State ClipboardServiceData(plainText: jsonEncode(node.toJson())), ); }, - text: Container( - height: 48, - alignment: Alignment.center, - child: FlowyText( - LocaleKeys.document_errorBlock_theBlockIsNotSupported.tr(), + text: SizedBox( + height: 52, + child: Row( + children: [ + const HSpace(4), + FlowyText( + LocaleKeys.document_errorBlock_theBlockIsNotSupported.tr(), + ), + ], ), ), ), @@ -95,6 +101,14 @@ class _DividerBlockComponentWidgetState extends State ); } + if (PlatformExtension.isMobile) { + child = MobileBlockActionButtons( + node: node, + editorState: context.read(), + child: child, + ); + } + return child; } } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart index 358c85ff2d..59355d25c1 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart @@ -6,7 +6,6 @@ import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; import 'package:appflowy/plugins/base/icon/icon_picker.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart'; import 'package:appflowy/plugins/document/presentation/editor_style.dart'; -import 'package:appflowy/workspace/application/view/view_bloc.dart'; import 'package:appflowy/workspace/application/view/view_listener.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_editor/appflowy_editor.dart'; @@ -16,7 +15,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/widget/rounded_button.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; -import 'package:provider/provider.dart'; import 'cover_editor.dart'; @@ -303,15 +301,14 @@ class _DocumentHeaderToolbarState extends State { ), onTap: PlatformExtension.isDesktop ? null - : () => context.push( - Uri( - path: MobileEmojiPickerScreen.routeName, - queryParameters: { - MobileEmojiPickerScreen.viewId: - context.read().state.view.id, - }, - ).toString(), - ), + : () async { + final result = await context.push( + MobileEmojiPickerScreen.routeName, + ); + if (result != null) { + widget.onCoverChanged(icon: result.emoji); + } + }, ); if (PlatformExtension.isDesktop) { @@ -325,8 +322,8 @@ class _DocumentHeaderToolbarState extends State { popupBuilder: (BuildContext popoverContext) { isPopoverOpen = true; return FlowyIconPicker( - onSelected: (type, value) { - widget.onCoverChanged(icon: value); + onSelected: (result) { + widget.onCoverChanged(icon: result.emoji); _popoverController.close(); }, ); @@ -532,8 +529,8 @@ class _DocumentIconState extends State { child: child, popupBuilder: (BuildContext popoverContext) { return FlowyIconPicker( - onSelected: (type, value) { - widget.onIconChanged(value); + onSelected: (result) { + widget.onIconChanged(result.emoji); _popoverController.close(); }, ); @@ -542,15 +539,14 @@ class _DocumentIconState extends State { } else { child = GestureDetector( child: child, - onTap: () => context.push( - Uri( - path: MobileEmojiPickerScreen.routeName, - queryParameters: { - MobileEmojiPickerScreen.viewId: - context.read().state.view.id, - }, - ).toString(), - ), + onTap: () async { + final result = await context.push( + MobileEmojiPickerScreen.routeName, + ); + if (result != null) { + widget.onIconChanged(result.emoji); + } + }, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/mobile_math_eqaution_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/mobile_math_equation_toolbar_item.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/mobile_math_eqaution_toolbar_item.dart rename to frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/math_equation/mobile_math_equation_toolbar_item.dart 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/list_mobile_toolbar_item.dart new file mode 100644 index 0000000000..d1f633eb60 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/list_mobile_toolbar_item.dart @@ -0,0 +1,121 @@ +import 'package:appflowy/generated/flowy_svgs.g.dart'; +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.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 customListMobileToolbarItem = MobileToolbarItem.withMenu( + itemIcon: const AFMobileIcon(afMobileIcons: AFMobileIcons.list), + itemMenuBuilder: (editorState, selection, _) { + return _MobileListMenu( + editorState: editorState, + selection: selection, + ); + }, +); + +class _MobileListMenu extends StatelessWidget { + const _MobileListMenu({ + required this.editorState, + required this.selection, + }); + + final Selection selection; + final EditorState editorState; + + @override + Widget build(BuildContext context) { + return GridView.count( + crossAxisCount: 2, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + childAspectRatio: 5, + shrinkWrap: true, + children: [ + // bulleted list, numbered list + _buildListButton( + context, + BulletedListBlockKeys.type, + const AFMobileIcon(afMobileIcons: AFMobileIcons.bulletedList), + LocaleKeys.document_plugins_bulletedList.tr(), + ), + _buildListButton( + context, + NumberedListBlockKeys.type, + const AFMobileIcon(afMobileIcons: AFMobileIcons.numberedList), + LocaleKeys.document_plugins_numberedList.tr(), + ), + + // todo list, quote list + _buildListButton( + context, + TodoListBlockKeys.type, + const AFMobileIcon(afMobileIcons: AFMobileIcons.checkbox), + LocaleKeys.document_plugins_todoList.tr(), + ), + _buildListButton( + context, + QuoteBlockKeys.type, + const AFMobileIcon(afMobileIcons: AFMobileIcons.quote), + LocaleKeys.document_plugins_quoteList.tr(), + ), + + // toggle list, callout + _buildListButton( + context, + ToggleListBlockKeys.type, + const FlowySvg( + FlowySvgs.toggle_list_s, + size: Size.square(24), + ), + LocaleKeys.document_plugins_toggleList.tr(), + ), + _buildListButton( + context, + CalloutBlockKeys.type, + const Icon(Icons.note_rounded), + LocaleKeys.document_plugins_callout.tr(), + ), + ], + ); + } + + Widget _buildListButton( + BuildContext context, + String listBlockType, + Widget icon, + String label, + ) { + final node = editorState.getNodeAtPath(selection.start.path); + final type = node?.type; + if (node == null || type == null) { + const SizedBox.shrink(); + } + final isSelected = type == listBlockType; + return MobileToolbarItemMenuBtn( + icon: icon, + label: FlowyText(label), + isSelected: isSelected, + onPressed: () async { + await editorState.formatNode( + selection, + (node) { + final attributes = { + ParagraphBlockKeys.delta: (node.delta ?? Delta()).toJson(), + if (listBlockType == TodoListBlockKeys.type) + TodoListBlockKeys.checked: false, + if (listBlockType == CalloutBlockKeys.type) + CalloutBlockKeys.icon: '📌', + }; + return node.copyWith( + type: isSelected ? ParagraphBlockKeys.type : listBlockType, + attributes: attributes, + ); + }, + ); + }, + ); + } +} 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 49b212c40e..f1f816f8a0 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 @@ -25,7 +25,8 @@ export 'image/mobile_image_toolbar_item.dart'; 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_eqaution_toolbar_item.dart'; +export 'math_equation/mobile_math_equation_toolbar_item.dart'; +export 'mobile_toolbar_item/list_mobile_toolbar_item.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/toggle/toggle_block_component.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart index a9ff605b03..003ae01d7b 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/toggle/toggle_block_component.dart @@ -166,13 +166,17 @@ class _ToggleListBlockComponentWidgetState Container( constraints: const BoxConstraints(minWidth: 26, minHeight: 22), padding: const EdgeInsets.only(right: 4.0), - child: FlowyIconButton( - width: 18.0, - icon: Icon( - collapsed ? Icons.arrow_right : Icons.arrow_drop_down, - size: 18.0, + child: AnimatedRotation( + turns: collapsed ? 0.0 : 0.25, + duration: const Duration(milliseconds: 200), + child: FlowyIconButton( + width: 18.0, + icon: const Icon( + Icons.arrow_right, + size: 18.0, + ), + onPressed: onCollapsed, ), - onPressed: onCollapsed, ), ), diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index a9415f14d6..bdb70ab5c4 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -209,11 +209,8 @@ GoRoute _mobileEmojiPickerPageRoute() { parentNavigatorKey: AppGlobals.rootNavKey, path: MobileEmojiPickerScreen.routeName, pageBuilder: (context, state) { - final id = state.uri.queryParameters[MobileEmojiPickerScreen.viewId]!; - return MaterialPage( - child: MobileEmojiPickerScreen( - id: id, - ), + return const MaterialPage( + child: MobileEmojiPickerScreen(), ); }, ); diff --git a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart index 05ce8902e2..dac51e7232 100644 --- a/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart +++ b/frontend/appflowy_flutter/lib/workspace/presentation/home/menu/view/view_item.dart @@ -361,10 +361,10 @@ class _SingleInnerViewItemState extends State { popupBuilder: (context) { isIconPickerOpened = true; return FlowyIconPicker( - onSelected: (_, emoji) { + onSelected: (result) { ViewBackendService.updateViewIcon( viewId: widget.view.id, - viewIcon: emoji, + viewIcon: result.emoji, ); controller.close(); }, diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 9efa52c66d..7c005d9ab5 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -54,8 +54,8 @@ packages: dependency: "direct main" description: path: "." - ref: a47fc6f - resolved-ref: a47fc6fc712b06991f578ae2ab314cbe23034e96 + ref: "50117b6" + resolved-ref: "50117b6900e4b239603ee48f6f3e7b7bc603c865" 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 862d1cbea9..2bb798ad59 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -47,7 +47,8 @@ dependencies: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: a47fc6f + ref: 50117b6 + appflowy_popover: path: packages/appflowy_popover diff --git a/frontend/resources/translations/en.json b/frontend/resources/translations/en.json index 4d9c464790..f504956308 100644 --- a/frontend/resources/translations/en.json +++ b/frontend/resources/translations/en.json @@ -616,7 +616,12 @@ "smartEditDisabled": "Connect OpenAI in Settings", "discardResponse": "Do you want to discard the AI responses?", "createInlineMathEquation": "Create equation", - "toggleList": "Toggle List", + "toggleList": "Toggle list", + "quoteList":"Quote list", + "numberedList":"Numbered list", + "bulletedList":"Bulleted list", + "todoList": "Todo List", + "callout": "Callout", "cover": { "changeCover": "Change Cover", "colors": "Colors",