diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart index 27f02f17cd..c4a8e71a02 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_inline_math_equation_test.dart @@ -33,7 +33,7 @@ void main() { ); // tap the inline math equation button - final inlineMathEquationButton = find.byTooltip( + final inlineMathEquationButton = find.findFlowyTooltip( LocaleKeys.document_plugins_createInlineMathEquation.tr(), ); await tester.tapButton(inlineMathEquationButton); @@ -78,7 +78,7 @@ void main() { ); // tap the inline math equation button - var inlineMathEquationButton = find.byTooltip( + var inlineMathEquationButton = find.findFlowyTooltip( LocaleKeys.document_plugins_createInlineMathEquation.tr(), ); await tester.tapButton(inlineMathEquationButton); @@ -93,11 +93,11 @@ void main() { ); // expect to the see the inline math equation button is highlighted - inlineMathEquationButton = find.byWidgetPredicate( - (widget) => - widget is SVGIconItemWidget && - widget.tooltip == - LocaleKeys.document_plugins_createInlineMathEquation.tr(), + inlineMathEquationButton = find.descendant( + of: find.findFlowyTooltip( + LocaleKeys.document_plugins_createInlineMathEquation.tr(), + ), + matching: find.byType(SVGIconItemWidget), ); expect( tester.widget(inlineMathEquationButton).isHighlight, diff --git a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/open_ai_smart_menu_test.dart b/frontend/appflowy_flutter/integration_test/desktop/uncategorized/open_ai_smart_menu_test.dart deleted file mode 100644 index 1d42cd8d28..0000000000 --- a/frontend/appflowy_flutter/integration_test/desktop/uncategorized/open_ai_smart_menu_test.dart +++ /dev/null @@ -1,113 +0,0 @@ -import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/ai_client.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy_editor/appflowy_editor.dart'; -import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart'; -import 'package:flowy_infra_ui/flowy_infra_ui.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import '../../shared/mock/mock_openai_repository.dart'; -import '../../shared/util.dart'; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - const service = TestWorkspaceService(TestWorkspace.aiWorkSpace); - - group('integration tests for open-ai smart menu', () { - setUpAll(() async => service.setUpAll()); - setUp(() async => service.setUp()); - - testWidgets('testing selection on open-ai smart menu replace', - (tester) async { - final appFlowyEditor = await setUpOpenAITesting(tester); - final editorState = appFlowyEditor.editorState; - - editorState.service.selectionService.updateSelection( - Selection( - start: Position(path: [1], offset: 4), - end: Position(path: [1], offset: 10), - ), - ); - await tester.pumpAndSettle(const Duration(milliseconds: 500)); - await tester.pumpAndSettle(); - - expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1)); - - await tester.tap(find.byTooltip('AI Assistants')); - await tester.pumpAndSettle(const Duration(milliseconds: 500)); - - await tester.tap(find.text('Summarize')); - await tester.pumpAndSettle(); - - await tester - .tap(find.byType(FlowyRichTextButton, skipOffstage: false).first); - await tester.pumpAndSettle(); - - expect( - editorState.service.selectionService.currentSelection.value, - Selection( - start: Position(path: [1], offset: 4), - end: Position(path: [1], offset: 84), - ), - ); - }); - testWidgets('testing selection on open-ai smart menu insert', - (tester) async { - final appFlowyEditor = await setUpOpenAITesting(tester); - final editorState = appFlowyEditor.editorState; - - editorState.service.selectionService.updateSelection( - Selection( - start: Position(path: [1]), - end: Position(path: [1], offset: 5), - ), - ); - await tester.pumpAndSettle(const Duration(milliseconds: 500)); - await tester.pumpAndSettle(); - expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1)); - - await tester.tap(find.byTooltip('AI Assistants')); - await tester.pumpAndSettle(const Duration(milliseconds: 500)); - - await tester.tap(find.text('Summarize')); - await tester.pumpAndSettle(); - - await tester - .tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1)); - await tester.pumpAndSettle(); - - expect( - editorState.service.selectionService.currentSelection.value, - Selection( - start: Position(path: [2]), - end: Position(path: [3]), - ), - ); - }); - }); -} - -Future setUpOpenAITesting(WidgetTester tester) async { - await tester.initializeAppFlowy(); - await mockOpenAIRepository(); - - await simulateKeyDownEvent(LogicalKeyboardKey.controlLeft); - await simulateKeyDownEvent(LogicalKeyboardKey.backslash); - await tester.pumpAndSettle(); - - final Finder editor = find.byType(AppFlowyEditor); - await tester.tap(editor); - await tester.pumpAndSettle(); - return tester.state(editor).widget as AppFlowyEditor; -} - -Future mockOpenAIRepository() async { - await getIt.unregister(); - getIt.registerFactoryAsync( - () => Future.value( - MockOpenAIRepository(), - ), - ); - return; -} diff --git a/frontend/appflowy_flutter/integration_test/shared/base.dart b/frontend/appflowy_flutter/integration_test/shared/base.dart index ab72247c24..16a576154f 100644 --- a/frontend/appflowy_flutter/integration_test/shared/base.dart +++ b/frontend/appflowy_flutter/integration_test/shared/base.dart @@ -1,9 +1,6 @@ import 'dart:async'; import 'dart:io'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/services.dart'; - import 'package:appflowy/env/cloud_env.dart'; import 'package:appflowy/env/cloud_env_test.dart'; import 'package:appflowy/startup/entry_point.dart'; @@ -16,6 +13,8 @@ import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widget import 'package:appflowy/workspace/application/settings/prelude.dart'; import 'package:flowy_infra/uuid.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as p; import 'package:path_provider/path_provider.dart'; @@ -231,6 +230,16 @@ extension AppFlowyFinderTestBase on CommonFinders { (widget) => widget is FlowyText && widget.text == text, ); } + + Finder findFlowyTooltip(String richMessage, {bool skipOffstage = true}) { + return byWidgetPredicate( + (widget) => + widget is FlowyTooltip && + widget.richMessage != null && + widget.richMessage!.toPlainText().contains(richMessage), + skipOffstage: skipOffstage, + ); + } } Future useTestSupabaseCloud() async { diff --git a/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart b/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart index a92f6548bb..0c712f8e24 100644 --- a/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart +++ b/frontend/appflowy_flutter/integration_test/shared/editor_test_operations.dart @@ -3,7 +3,7 @@ import 'dart:ui'; import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/plugins/base/emoji/emoji_picker.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart'; +import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_option_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart'; diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart index f2c9f53551..43e7035e38 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart @@ -1,5 +1,5 @@ import 'package:appflowy/plugins/base/emoji/emoji_picker_header.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart'; +import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.dart'; import 'package:appflowy/shared/icon_emoji_picker/emoji_search_bar.dart'; import 'package:flowy_infra/size.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart'; 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 c7f8643b68..54b45d0397 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_page.dart @@ -387,6 +387,8 @@ class _AppFlowyEditorPageState extends State { editorState: editorState, editorScrollController: editorScrollController, textDirection: textDirection, + tooltipBuilder: (context, id, message, child) => widget.styleCustomizer + .buildToolbarItemTooltip(context, id, message, child,), child: editor, ), ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart index 9723158be5..4fd81395e9 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/align_toolbar_item/align_toolbar_item.dart @@ -9,12 +9,13 @@ import 'package:flutter/material.dart'; const String leftAlignmentKey = 'left'; const String centerAlignmentKey = 'center'; const String rightAlignmentKey = 'right'; +const String kAlignToolbarItemId = 'editor.align'; final alignToolbarItem = ToolbarItem( - id: 'editor.align', + id: kAlignToolbarItemId, group: 4, isActive: onlyShowInTextType, - builder: (context, editorState, highlightColor, _) { + builder: (context, editorState, highlightColor, _, tooltipBuilder) { final selection = editorState.selection!; final nodes = editorState.getNodesInSelection(selection); @@ -37,35 +38,37 @@ final alignToolbarItem = ToolbarItem( data = FlowySvgs.toolbar_align_right_s; } - final child = FlowySvg( + Widget child = FlowySvg( data, size: const Size.square(16), color: isHighlight ? highlightColor : Colors.white, ); - return MouseRegion( - cursor: SystemMouseCursors.click, - child: FlowyTooltip( - message: LocaleKeys.document_plugins_optionAction_align.tr(), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: _AlignmentButtons( - child: child, - onAlignChanged: (align) async { - await editorState.updateNode( - selection, - (node) => node.copyWith( - attributes: { - ...node.attributes, - blockComponentAlign: align, - }, - ), - ); + child = _AlignmentButtons( + child: child, + onAlignChanged: (align) async { + await editorState.updateNode( + selection, + (node) => node.copyWith( + attributes: { + ...node.attributes, + blockComponentAlign: align, }, ), - ), - ), + ); + }, ); + + if (tooltipBuilder != null) { + child = tooltipBuilder( + context, + kAlignToolbarItemId, + LocaleKeys.document_plugins_optionAction_align.tr(), + child, + ); + } + + return child; }, ); @@ -83,13 +86,15 @@ class _AlignmentButtons extends StatefulWidget { } class _AlignmentButtonsState extends State<_AlignmentButtons> { + final controller = PopoverController(); + @override Widget build(BuildContext context) { return AppFlowyPopover( windowPadding: const EdgeInsets.all(0), - margin: const EdgeInsets.all(4), + margin: const EdgeInsets.symmetric(vertical: 2.0), direction: PopoverDirection.bottomWithCenterAligned, - offset: const Offset(0, 10), + offset: const Offset(0, 12), decorationColor: Theme.of(context).colorScheme.onTertiary, borderRadius: const BorderRadius.all(Radius.circular(4)), popupBuilder: (_) { @@ -99,7 +104,12 @@ class _AlignmentButtonsState extends State<_AlignmentButtons> { onClose: () { keepEditorFocusNotifier.decrease(); }, - child: widget.child, + child: FlowyButton( + useIntrinsicWidth: true, + text: widget.child, + hoverColor: Colors.grey.withOpacity(0.3), + onTap: () => controller.show(), + ), ); } } @@ -114,7 +124,7 @@ class _AlignButtons extends StatelessWidget { @override Widget build(BuildContext context) { return SizedBox( - height: 32, + height: 28, child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -156,17 +166,16 @@ class _AlignButton extends StatelessWidget { @override Widget build(BuildContext context) { - return MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: onTap, - child: FlowyTooltip( - message: tooltips, - child: FlowySvg( - icon, - size: const Size.square(16), - color: Colors.white, - ), + return FlowyButton( + useIntrinsicWidth: true, + hoverColor: Colors.grey.withOpacity(0.3), + onTap: onTap, + text: FlowyTooltip( + message: tooltips, + child: FlowySvg( + icon, + size: const Size.square(16), + color: Colors.white, ), ), ); @@ -179,7 +188,7 @@ class _Divider extends StatelessWidget { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(4), child: Container( width: 1, color: Colors.grey, diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart index c610be4dbf..da4e0f2546 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/font/customize_font_toolbar_item.dart @@ -1,6 +1,3 @@ -import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; -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'; @@ -9,6 +6,7 @@ import 'package:appflowy/util/font_family_extension.dart'; import 'package:appflowy/util/levenshtein.dart'; import 'package:appflowy/workspace/application/appearance_defaults.dart'; import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart'; +import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart'; import 'package:appflowy/workspace/presentation/settings/shared/setting_list_tile.dart'; import 'package:appflowy/workspace/presentation/settings/shared/setting_value_dropdown.dart'; import 'package:appflowy_backend/log.dart'; @@ -20,56 +18,66 @@ import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra_ui/style_widget/button.dart'; import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text_field.dart'; -import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:google_fonts/google_fonts.dart'; +const kFontToolbarItemId = 'editor.font'; + final customizeFontToolbarItem = ToolbarItem( - id: 'editor.font', + id: kFontToolbarItemId, group: 4, isActive: onlyShowInTextType, - builder: (context, editorState, highlightColor, _) { + builder: (context, editorState, highlightColor, _, tooltipBuilder) { final selection = editorState.selection!; final popoverController = PopoverController(); final String? currentFontFamily = editorState .getDeltaAttributeValueInSelection(AppFlowyRichTextKeys.fontFamily); - return MouseRegion( - cursor: SystemMouseCursors.click, - child: FontFamilyDropDown( - currentFontFamily: currentFontFamily ?? '', - offset: const Offset(0, 12), - popoverController: popoverController, - onOpen: () => keepEditorFocusNotifier.increase(), - onClose: () => keepEditorFocusNotifier.decrease(), - showResetButton: true, - onFontFamilyChanged: (fontFamily) async { - popoverController.close(); - try { - await editorState.formatDelta(selection, { - AppFlowyRichTextKeys.fontFamily: fontFamily, - }); - } catch (e) { - Log.error('Failed to set font family: $e'); - } - }, - onResetFont: () async { - popoverController.close(); - await editorState - .formatDelta(selection, {AppFlowyRichTextKeys.fontFamily: null}); - }, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 4.0), - child: FlowyTooltip( - message: LocaleKeys.document_plugins_fonts.tr(), - child: const FlowySvg( - FlowySvgs.font_family_s, - size: Size.square(16.0), - color: Colors.white, - ), - ), + + Widget child = FontFamilyDropDown( + currentFontFamily: currentFontFamily ?? '', + offset: const Offset(0, 12), + popoverController: popoverController, + onOpen: () => keepEditorFocusNotifier.increase(), + onClose: () => keepEditorFocusNotifier.decrease(), + showResetButton: true, + onFontFamilyChanged: (fontFamily) async { + popoverController.close(); + try { + await editorState.formatDelta(selection, { + AppFlowyRichTextKeys.fontFamily: fontFamily, + }); + } catch (e) { + Log.error('Failed to set font family: $e'); + } + }, + onResetFont: () async { + popoverController.close(); + await editorState + .formatDelta(selection, {AppFlowyRichTextKeys.fontFamily: null}); + }, + child: FlowyButton( + useIntrinsicWidth: true, + hoverColor: Colors.grey.withOpacity(0.3), + onTap: () => popoverController.show(), + text: const FlowySvg( + FlowySvgs.font_family_s, + size: Size.square(16.0), + color: Colors.white, ), ), ); + + if (tooltipBuilder != null) { + child = tooltipBuilder( + context, + kFontToolbarItemId, + LocaleKeys.document_plugins_fonts.tr(), + child, + ); + } + + return child; }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart index 08c23df05b..a66a9ee317 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation_toolbar_item.dart @@ -5,11 +5,13 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +const _kInlineMathEquationToolbarItemId = 'editor.inline_math_equation'; + final ToolbarItem inlineMathEquationItem = ToolbarItem( - id: 'editor.inline_math_equation', + id: _kInlineMathEquationToolbarItemId, group: 2, isActive: onlyShowInSingleSelectionAndTextType, - builder: (context, editorState, highlightColor, _) { + builder: (context, editorState, highlightColor, _, tooltipBuilder) { final selection = editorState.selection!; final nodes = editorState.getNodesInSelection(selection); final isHighlight = nodes.allSatisfyInSelection(selection, (delta) { @@ -17,7 +19,7 @@ final ToolbarItem inlineMathEquationItem = ToolbarItem( (attributes) => attributes[InlineMathEquationKeys.formula] != null, ); }); - return SVGIconItemWidget( + final child = SVGIconItemWidget( iconBuilder: (_) => FlowySvg( FlowySvgs.math_lg, size: const Size.square(16), @@ -25,7 +27,6 @@ final ToolbarItem inlineMathEquationItem = ToolbarItem( ), isHighlight: isHighlight, highlightColor: highlightColor, - tooltip: LocaleKeys.document_plugins_createInlineMathEquation.tr(), onPressed: () async { final selection = editorState.selection; if (selection == null || selection.isCollapsed) { @@ -71,5 +72,16 @@ final ToolbarItem inlineMathEquationItem = ToolbarItem( await editorState.apply(transaction); }, ); + + if (tooltipBuilder != null) { + return tooltipBuilder( + context, + _kInlineMathEquationToolbarItemId, + LocaleKeys.document_plugins_createInlineMathEquation.tr(), + child, + ); + } + + return child; }, ); diff --git a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_toolbar_item.dart b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_toolbar_item.dart index 84d122cf18..434857891e 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_toolbar_item.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/openai/widgets/smart_edit_toolbar_item.dart @@ -11,12 +11,15 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flowy_infra_ui/style_widget/icon_button.dart'; import 'package:flutter/material.dart'; +const _kSmartEditToolbarItemId = 'appflowy.editor.smart_edit'; + final ToolbarItem smartEditItem = ToolbarItem( - id: 'appflowy.editor.smart_edit', + id: _kSmartEditToolbarItemId, group: 0, isActive: onlyShowInSingleSelectionAndTextType, - builder: (context, editorState, _, __) => SmartEditActionList( + builder: (context, editorState, _, __, tooltipBuilder) => SmartEditActionList( editorState: editorState, + tooltipBuilder: tooltipBuilder, ), ); @@ -24,9 +27,11 @@ class SmartEditActionList extends StatefulWidget { const SmartEditActionList({ super.key, required this.editorState, + this.tooltipBuilder, }); final EditorState editorState; + final ToolbarTooltipBuilder? tooltipBuilder; @override State createState() => _SmartEditActionListState(); @@ -60,11 +65,8 @@ class _SmartEditActionListState extends State { onClosed: () => keepEditorFocusNotifier.decrease(), buildChild: (controller) { keepEditorFocusNotifier.increase(); - return FlowyIconButton( + final child = FlowyIconButton( hoverColor: Colors.transparent, - tooltipText: isAIEnabled - ? LocaleKeys.document_plugins_smartEdit.tr() - : LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(), preferBelow: false, icon: const Icon( Icons.lightbulb_outline, @@ -83,6 +85,19 @@ class _SmartEditActionListState extends State { } }, ); + + if (widget.tooltipBuilder != null) { + return widget.tooltipBuilder!( + context, + _kSmartEditToolbarItemId, + isAIEnabled + ? LocaleKeys.document_plugins_smartEdit.tr() + : LocaleKeys.document_plugins_appflowyAIEditDisabled.tr(), + child, + ); + } + + return child; }, onSelected: (action, controller) { controller.close(); 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 529c2a549c..7464b81a81 100644 --- a/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart +++ b/frontend/appflowy_flutter/lib/plugins/document/presentation/editor_style.dart @@ -1,4 +1,7 @@ +import 'dart:io'; + import 'package:appflowy/core/helpers/url_launcher.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_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/base/font_colors.dart'; @@ -13,7 +16,10 @@ 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:easy_localization/easy_localization.dart'; import 'package:flowy_infra/theme_extension.dart'; +import 'package:flowy_infra_ui/style_widget/hover.dart'; +import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -394,4 +400,83 @@ class EditorStyleCustomizer { after, ); } + + Widget buildToolbarItemTooltip( + BuildContext context, + String id, + String message, + Widget child, + ) { + final tooltipMessage = _buildTooltipMessage(id, message); + child = FlowyTooltip( + richMessage: tooltipMessage, + preferBelow: false, + verticalOffset: 20, + child: child, + ); + + // the align/font toolbar item doesn't need the hover effect + final toolbarItemsWithoutHover = { + kFontToolbarItemId, + kAlignToolbarItemId, + }; + + if (!toolbarItemsWithoutHover.contains(id)) { + child = Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: FlowyHover( + style: HoverStyle( + hoverColor: Colors.grey.withOpacity(0.3), + ), + child: child, + ), + ); + } + + return child; + } + + TextSpan _buildTooltipMessage(String id, String message) { + final markdownItemTooltips = { + 'underline': (LocaleKeys.toolbar_underline.tr(), 'U'), + 'bold': (LocaleKeys.toolbar_bold.tr(), 'B'), + 'italic': (LocaleKeys.toolbar_italic.tr(), 'I'), + 'strikethrough': (LocaleKeys.toolbar_strike.tr(), 'Shift+S'), + 'code': (LocaleKeys.toolbar_inlineCode.tr(), 'E'), + }; + + final markdownItemIds = markdownItemTooltips.keys.toSet(); + // the items without shortcuts + if (!markdownItemIds.contains(id)) { + return TextSpan( + text: message, + style: context.tooltipTextStyle(), + ); + } + + final tooltip = markdownItemTooltips[id]; + if (tooltip == null) { + return TextSpan( + text: message, + style: context.tooltipTextStyle(), + ); + } + + final textSpan = TextSpan( + children: [ + TextSpan( + text: '${tooltip.$1}\n', + style: context.tooltipTextStyle(), + ), + TextSpan( + text: (Platform.isMacOS ? '⌘+' : 'Ctrl+\\') + tooltip.$2, + style: context + .tooltipTextStyle() + ?.copyWith(color: Theme.of(context).hintColor), + ), + ], + ); + + return textSpan; + } } diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/colors.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/colors.dart new file mode 100644 index 0000000000..8728c3be4a --- /dev/null +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/colors.dart @@ -0,0 +1,26 @@ +import 'package:appflowy/util/theme_extension.dart'; +import 'package:flutter/material.dart'; + +extension PickerColors on BuildContext { + Color get pickerTextColor { + return Theme.of(this).isLightMode + ? const Color(0x80171717) + : Colors.white.withOpacity(0.5); + } + + Color get pickerIconColor { + return Theme.of(this).isLightMode ? const Color(0xFF171717) : Colors.white; + } + + Color get pickerSearchBarBorderColor { + return Theme.of(this).isLightMode + ? const Color(0x1E171717) + : Colors.white.withOpacity(0.12); + } + + Color get pickerButtonBoarderColor { + return Theme.of(this).isLightMode + ? const Color(0x1E171717) + : Colors.white.withOpacity(0.12); + } +} diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_search_bar.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_search_bar.dart index 57dba3c93e..8dbee9ec29 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_search_bar.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_search_bar.dart @@ -1,12 +1,14 @@ import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; -import 'package:appflowy/plugins/base/emoji/emoji_skin_tone.dart'; +import 'package:appflowy/shared/icon_emoji_picker/emoji_skin_tone.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_emoji_mart/flutter_emoji_mart.dart'; +import 'colors.dart'; + typedef EmojiKeywordChangedCallback = void Function(String keyword); typedef EmojiSkinToneChanged = void Function(EmojiSkinTone skinTone); @@ -82,7 +84,7 @@ class _RandomEmojiButton extends StatelessWidget { height: 36, decoration: ShapeDecoration( shape: RoundedRectangleBorder( - side: const BorderSide(color: Color(0x1E171717)), + side: BorderSide(color: context.pickerButtonBoarderColor), borderRadius: BorderRadius.circular(8), ), ), @@ -141,7 +143,7 @@ class _SearchTextFieldState extends State<_SearchTextField> { fontWeight: FontWeight.w400, color: Theme.of(context).hintColor, ), - enableBorderColor: const Color(0x1E171717), + enableBorderColor: context.pickerSearchBarBorderColor, controller: controller, onChanged: widget.onKeywordChanged, prefixIcon: const Padding( diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_skin_tone.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_skin_tone.dart similarity index 96% rename from frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_skin_tone.dart rename to frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_skin_tone.dart index 5206075769..e802e0dba0 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_skin_tone.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/emoji_skin_tone.dart @@ -5,6 +5,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; +import 'colors.dart'; + // use a temporary global value to store last selected skin tone EmojiSkinTone? lastSelectedEmojiSkinTone; @@ -68,7 +70,7 @@ class _FlowyEmojiSkinToneSelectorState width: 36, height: 36, decoration: BoxDecoration( - border: Border.all(color: const Color(0x1E171717)), + border: Border.all(color: context.pickerButtonBoarderColor), borderRadius: BorderRadius.circular(8), ), child: FlowyButton( diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart index f9d5cbf9b5..707e2ffe79 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart @@ -13,6 +13,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart' hide Icon; import 'package:flutter/services.dart'; +import 'colors.dart'; import 'icon_color_picker.dart'; // cache the icon groups to avoid loading them multiple times @@ -200,7 +201,7 @@ class _IconPickerState extends State { iconGroup.displayName, fontSize: 12, figmaLineHeight: 18.0, - color: const Color(0x80171717), + color: context.pickerTextColor, ), const VSpace(4.0), Wrap( @@ -252,7 +253,7 @@ class _Icon extends StatelessWidget { child: FlowySvg.string( icon.content, size: const Size.square(20), - color: const Color(0xFF171717), + color: context.pickerIconColor, opacity: 0.7, ), ), diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_search_bar.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_search_bar.dart index 06a3566d21..31e1e285c0 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_search_bar.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_search_bar.dart @@ -6,6 +6,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flutter/material.dart'; import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; +import 'colors.dart'; + typedef IconKeywordChangedCallback = void Function(String keyword); typedef EmojiSkinToneChanged = void Function(EmojiSkinTone skinTone); @@ -70,7 +72,7 @@ class _RandomIconButton extends StatelessWidget { height: 36, decoration: ShapeDecoration( shape: RoundedRectangleBorder( - side: const BorderSide(color: Color(0x1E171717)), + side: BorderSide(color: context.pickerButtonBoarderColor), borderRadius: BorderRadius.circular(8), ), ), @@ -123,7 +125,7 @@ class _SearchTextFieldState extends State<_SearchTextField> { fontWeight: FontWeight.w400, color: Theme.of(context).hintColor, ), - enableBorderColor: const Color(0x1E171717), + enableBorderColor: context.pickerSearchBarBorderColor, controller: controller, onChanged: widget.onKeywordChanged, prefixIcon: const Padding( diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/flowy_tooltip.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/flowy_tooltip.dart index 960b8b3ad4..d723a67a3f 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/flowy_tooltip.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/widget/flowy_tooltip.dart @@ -10,6 +10,7 @@ class FlowyTooltip extends StatelessWidget { this.preferBelow, this.showDuration, this.margin, + this.verticalOffset, this.child, }); @@ -19,6 +20,7 @@ class FlowyTooltip extends StatelessWidget { final Duration? showDuration; final EdgeInsetsGeometry? margin; final Widget? child; + final double? verticalOffset; @override Widget build(BuildContext context) { @@ -28,16 +30,14 @@ class FlowyTooltip extends StatelessWidget { return Tooltip( margin: margin, - verticalOffset: 16.0, - padding: const EdgeInsets.only( - left: 12.0, - right: 12.0, - top: 5.0, - bottom: 8.0, + verticalOffset: verticalOffset ?? 16.0, + padding: const EdgeInsets.symmetric( + horizontal: 12.0, + vertical: 8.0, ), decoration: BoxDecoration( color: context.tooltipBackgroundColor(), - borderRadius: BorderRadius.circular(12.0), + borderRadius: BorderRadius.circular(10.0), ), waitDuration: _tooltipWaitDuration, message: message, @@ -50,7 +50,7 @@ class FlowyTooltip extends StatelessWidget { } extension FlowyToolTipExtension on BuildContext { - double tooltipFontSize() => 13.0; + double tooltipFontSize() => 14.0; double tooltipHeight() => 18.0 / tooltipFontSize(); Color tooltipFontColor() => Theme.of(this).brightness == Brightness.light ? Colors.white diff --git a/frontend/appflowy_flutter/pubspec.lock b/frontend/appflowy_flutter/pubspec.lock index 4827c94bbc..10f14ba563 100644 --- a/frontend/appflowy_flutter/pubspec.lock +++ b/frontend/appflowy_flutter/pubspec.lock @@ -53,8 +53,8 @@ packages: dependency: "direct main" description: path: "." - ref: c064543 - resolved-ref: c064543e0e40f862f1db2db36725d48465a8aea5 + ref: "4536488faf458ab45e304c1715850d4d1ae517ee" + resolved-ref: "4536488faf458ab45e304c1715850d4d1ae517ee" url: "https://github.com/AppFlowy-IO/appflowy-editor.git" source: git version: "3.1.0" diff --git a/frontend/appflowy_flutter/pubspec.yaml b/frontend/appflowy_flutter/pubspec.yaml index 17c37f3287..928766a845 100644 --- a/frontend/appflowy_flutter/pubspec.yaml +++ b/frontend/appflowy_flutter/pubspec.yaml @@ -199,7 +199,7 @@ dependency_overrides: appflowy_editor: git: url: https://github.com/AppFlowy-IO/appflowy-editor.git - ref: "c064543" + ref: "4536488faf458ab45e304c1715850d4d1ae517ee" appflowy_editor_plugins: git: