diff --git a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart index c18571906a..3e594b47f9 100644 --- a/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart +++ b/frontend/appflowy_flutter/lib/mobile/presentation/bottom_sheet/bottom_sheet_action_widget.dart @@ -41,7 +41,10 @@ class BottomSheetActionWidget extends StatelessWidget { size: const Size.square(22.0), color: iconColor, ), - label: FlowyText(text), + label: FlowyText( + text, + overflow: TextOverflow.ellipsis, + ), style: Theme.of(context) .outlinedButtonTheme .style diff --git a/frontend/appflowy_flutter/lib/plugins/base/color/color_picker.dart b/frontend/appflowy_flutter/lib/plugins/base/color/color_picker.dart new file mode 100644 index 0000000000..e48f926cf4 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/base/color/color_picker.dart @@ -0,0 +1,88 @@ +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/theme_extension.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; + +class FlowyMobileColorPicker extends StatelessWidget { + const FlowyMobileColorPicker({ + super.key, + required this.onSelectedColor, + }); + + final void Function(FlowyColorOption? option) onSelectedColor; + + @override + Widget build(BuildContext context) { + const defaultColor = Colors.transparent; + final colors = [ + // reset to default background color + FlowyColorOption( + color: defaultColor, + i18n: LocaleKeys.document_plugins_optionAction_defaultColor.tr(), + id: optionActionColorDefaultColor, + ), + ...FlowyTint.values.map( + (e) => FlowyColorOption( + color: e.color(context), + i18n: e.tintName(AppFlowyEditorL10n.current), + id: e.id, + ), + ), + ]; + return ListView.separated( + itemBuilder: (context, index) { + final color = colors[index]; + return SizedBox( + height: 56, + child: FlowyButton( + useIntrinsicWidth: true, + text: FlowyText( + color.i18n, + ), + leftIcon: _ColorIcon( + color: color.color, + size: 24.0, + ), + leftIconSize: const Size.square(36.0), + iconPadding: 12.0, + margin: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 16.0, + ), + onTap: () => onSelectedColor(color), + ), + ); + }, + separatorBuilder: (_, __) => const Divider( + height: 1, + ), + itemCount: colors.length, + ); + } +} + +class _ColorIcon extends StatelessWidget { + const _ColorIcon({ + this.size = 24.0, + required this.color, + }); + + final double size; + final Color color; + + @override + Widget build(BuildContext context) { + return SizedBox.square( + dimension: size, + child: DecoratedBox( + decoration: BoxDecoration( + color: color, + shape: BoxShape.circle, + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/base/color/color_picker_screen.dart b/frontend/appflowy_flutter/lib/plugins/base/color/color_picker_screen.dart new file mode 100644 index 0000000000..085ff8dcb7 --- /dev/null +++ b/frontend/appflowy_flutter/lib/plugins/base/color/color_picker_screen.dart @@ -0,0 +1,40 @@ +import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart'; +import 'package:appflowy/plugins/base/color/color_picker.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; + +class MobileColorPickerScreen extends StatelessWidget { + static const routeName = '/color_picker'; + static const pageTitle = 'title'; + + const MobileColorPickerScreen({ + super.key, + this.title, + }); + + final String? title; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + titleSpacing: 0, + title: FlowyText.semibold( + title ?? LocaleKeys.titleBar_pageIcon.tr(), + fontSize: 14.0, + ), + leading: AppBarBackButton( + onTap: () => context.pop(), + ), + ), + body: SafeArea( + child: FlowyMobileColorPicker( + onSelectedColor: (option) => context.pop(option), + ), + ), + ); + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart index eb5ddde345..eed1a06794 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_add_block_toolbar_item.dart @@ -39,11 +39,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_text.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, paragraphNode(), ); - service.closeItemMenu(); }, ), @@ -54,11 +54,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_checkbox.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, todoListNode(checked: false), ); - service.closeItemMenu(); }, ), @@ -69,11 +69,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_heading1.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, headingNode(level: 1), ); - service.closeItemMenu(); }, ), BlockMenuItem( @@ -82,11 +82,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_heading2.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, headingNode(level: 2), ); - service.closeItemMenu(); }, ), BlockMenuItem( @@ -95,11 +95,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_heading3.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, headingNode(level: 3), ); - service.closeItemMenu(); }, ), @@ -110,11 +110,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_bulletedList.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, bulletedListNode(), ); - service.closeItemMenu(); }, ), @@ -125,11 +125,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_numberedList.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, numberedListNode(), ); - service.closeItemMenu(); }, ), @@ -140,11 +140,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.document_plugins_toggleList.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, toggleListBlockNode(), ); - service.closeItemMenu(); }, ), @@ -155,11 +155,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_quote.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, quoteNode(), ); - service.closeItemMenu(); }, ), @@ -171,11 +171,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.document_plugins_callout.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, calloutNode(), ); - service.closeItemMenu(); }, ), @@ -186,11 +186,11 @@ final _addBlockMenuItems = [ label: LocaleKeys.document_selectionMenu_codeBlock.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { + service.closeItemMenu(); await editorState.insertBlockOrReplaceCurrentBlock( selection, codeBlockNode(), ); - service.closeItemMenu(); }, ), @@ -201,8 +201,8 @@ final _addBlockMenuItems = [ label: LocaleKeys.editor_divider.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { - await editorState.insertDivider(selection); service.closeItemMenu(); + await editorState.insertDivider(selection); }, ), @@ -216,8 +216,8 @@ final _addBlockMenuItems = [ label: LocaleKeys.document_plugins_mathEquation_name.tr(), isSelected: _unSelectable, onTap: (editorState, selection, service) async { - await editorState.insertMathEquation(selection); service.closeItemMenu(); + await editorState.insertMathEquation(selection); }, ), ]; diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_block_settings_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_block_settings_toolbar_item.dart index dea42b1bd0..ece4c9b819 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_block_settings_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_block_settings_toolbar_item.dart @@ -1,9 +1,13 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; +import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart'; import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_block_action_widget.dart'; import 'package:appflowy/mobile/presentation/widgets/widgets.dart'; +import 'package:appflowy/plugins/base/color/color_picker_screen.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:go_router/go_router.dart'; final mobileBlockSettingsToolbarItem = MobileToolbarItem.action( @@ -24,55 +28,105 @@ final mobileBlockSettingsToolbarItem = MobileToolbarItem.action( return; } - final result = await showFlowyMobileBottomSheet( + await _showBlockActionSheet( context, - title: LocaleKeys.document_plugins_action.tr(), - builder: (context) { - return BlockActionBottomSheet( - onAction: (action) async { - context.pop(true); - - final transaction = editorState.transaction; - switch (action) { - case BlockActionBottomSheetType.delete: - transaction.deleteNode(node); - break; - case BlockActionBottomSheetType.duplicate: - transaction.insertNode( - node.path.next, - node.copyWith(), - ); - break; - case BlockActionBottomSheetType.insertAbove: - case BlockActionBottomSheetType.insertBelow: - final path = action == BlockActionBottomSheetType.insertAbove - ? node.path - : node.path.next; - transaction - ..insertNode( - path, - paragraphNode(), - ) - ..afterSelection = Selection.collapsed( - Position( - path: path, - ), - ); - break; - default: - } - - if (transaction.operations.isNotEmpty) { - await editorState.apply(transaction); - } - }, - ); - }, + editorState, + node, + selection, ); - - if (result != true) { - // restore the selection - editorState.selection = selection; - } }, ); + +Future _showBlockActionSheet( + BuildContext context, + EditorState editorState, + Node node, + Selection selection, +) async { + final result = await showFlowyMobileBottomSheet( + context, + title: LocaleKeys.document_plugins_action.tr(), + builder: (context) { + return BlockActionBottomSheet( + extendActionWidgets: [ + const VSpace(8), + Row( + children: [ + Expanded( + child: BottomSheetActionWidget( + svg: FlowySvgs.m_color_m, + text: LocaleKeys.document_plugins_optionAction_color.tr(), + onTap: () async { + final option = await context.push( + Uri( + path: MobileColorPickerScreen.routeName, + queryParameters: { + MobileColorPickerScreen.pageTitle: LocaleKeys + .document_plugins_optionAction_color + .tr(), + }, + ).toString(), + ); + if (option != null) { + final transaction = editorState.transaction; + transaction.updateNode(node, { + blockComponentBackgroundColor: option.id, + }); + await editorState.apply(transaction); + } + if (context.mounted) { + context.pop(true); + } + }, + ), + ), + // more options ... + ], + ), + ], + onAction: (action) async { + context.pop(true); + + final transaction = editorState.transaction; + switch (action) { + case BlockActionBottomSheetType.delete: + transaction.deleteNode(node); + break; + case BlockActionBottomSheetType.duplicate: + transaction.insertNode( + node.path.next, + node.copyWith(), + ); + break; + case BlockActionBottomSheetType.insertAbove: + case BlockActionBottomSheetType.insertBelow: + final path = action == BlockActionBottomSheetType.insertAbove + ? node.path + : node.path.next; + transaction + ..insertNode( + path, + paragraphNode(), + ) + ..afterSelection = Selection.collapsed( + Position( + path: path, + ), + ); + break; + default: + } + + if (transaction.operations.isNotEmpty) { + await editorState.apply(transaction); + } + }, + ); + }, + ); + + if (result != true) { + // restore the selection + editorState.selection = selection; + } +} diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/redo_mobile_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/undo_redo/redo_mobile_toolbar_item.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/redo_mobile_toolbar_item.dart rename to frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/undo_redo/redo_mobile_toolbar_item.dart diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/undo_mobile_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/undo_redo/undo_mobile_toolbar_item.dart similarity index 100% rename from frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/undo_redo/undo_mobile_toolbar_item.dart rename to frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/mobile_toolbar_item/undo_redo/undo_mobile_toolbar_item.dart 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 eb0c13a815..22acdfc938 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 @@ -32,6 +32,8 @@ export 'mobile_toolbar_item/mobile_block_settings_toolbar_item.dart'; export 'mobile_toolbar_item/mobile_convert_block_toolbar_item.dart'; export 'mobile_toolbar_item/mobile_indent_toolbar_item.dart'; export 'mobile_toolbar_item/mobile_text_decoration_item.dart'; +export 'mobile_toolbar_item/undo_redo/redo_mobile_toolbar_item.dart'; +export 'mobile_toolbar_item/undo_redo/undo_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'; @@ -41,5 +43,3 @@ export 'table/table_menu.dart'; export 'table/table_option_action.dart'; export 'toggle/toggle_block_component.dart'; export 'toggle/toggle_block_shortcut_event.dart'; -export 'undo_redo/redo_mobile_toolbar_item.dart'; -export 'undo_redo/undo_mobile_toolbar_item.dart'; diff --git a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart index b73739427f..f078944ed5 100644 --- a/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart +++ b/frontend/appflowy_flutter/lib/startup/tasks/generate_router.dart @@ -9,9 +9,10 @@ import 'package:appflowy/mobile/presentation/favorite/mobile_favorite_page.dart' import 'package:appflowy/mobile/presentation/presentation.dart'; import 'package:appflowy/mobile/presentation/setting/font/font_picker_screen.dart'; import 'package:appflowy/mobile/presentation/setting/language/language_picker_screen.dart'; +import 'package:appflowy/plugins/base/color/color_picker_screen.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart'; -import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart'; +import 'package:appflowy/plugins/document/presentation/editor_plugins/code_block/code_language_screen.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_picker_screen.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_toolbar_item/mobile_block_settings_screen.dart'; import 'package:appflowy/startup/startup.dart'; @@ -69,6 +70,9 @@ GoRouter generateRouter(Widget child) { _mobileEmojiPickerPageRoute(), _mobileImagePickerPageRoute(), + // color picker + _mobileColorPickerPageRoute(), + // code language picker _mobileCodeLanguagePickerPageRoute(), _mobileLanguagePickerPageRoute(), @@ -263,6 +267,22 @@ GoRoute _mobileEmojiPickerPageRoute() { ); } +GoRoute _mobileColorPickerPageRoute() { + return GoRoute( + parentNavigatorKey: AppGlobals.rootNavKey, + path: MobileColorPickerScreen.routeName, + pageBuilder: (context, state) { + final title = + state.uri.queryParameters[MobileColorPickerScreen.pageTitle] ?? ''; + return MaterialPage( + child: MobileColorPickerScreen( + title: title, + ), + ); + }, + ); +} + GoRoute _mobileImagePickerPageRoute() { return GoRoute( parentNavigatorKey: AppGlobals.rootNavKey, diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart index 4d803f0126..a5ad456107 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/button.dart @@ -27,6 +27,7 @@ class FlowyButton extends StatelessWidget { final bool expandText; final MainAxisAlignment mainAxisAlignment; final bool showDefaultBoxDecorationOnMobile; + final double iconPadding; const FlowyButton({ Key? key, @@ -48,6 +49,7 @@ class FlowyButton extends StatelessWidget { this.expandText = true, this.mainAxisAlignment = MainAxisAlignment.center, this.showDefaultBoxDecorationOnMobile = false, + this.iconPadding = 6, }) : super(key: key); @override @@ -92,7 +94,7 @@ class FlowyButton extends StatelessWidget { child: leftIcon!, ), ); - children.add(const HSpace(6)); + children.add(HSpace(iconPadding)); } if (expandText) {