mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
feat: optimize editor toolbar tooltip (#5889)
* chore: improve icon picker dark mode color * feat: optimize editor toolbar tooltip style * feat: customize markdown item tooltip message * chore: find the tooltip by rich message * feat: add hover effect in toolbar item * feat: add hover effect in toolbar item * chore: optimize hover color in light mode * chore: fix integration test * chore: optimize align & font toolbar item * chore: fix integration test
This commit is contained in:
parent
a798b037db
commit
e279ad1cc7
@ -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<SVGIconItemWidget>(inlineMathEquationButton).isHighlight,
|
||||
|
@ -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<AppFlowyEditor> 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<void> mockOpenAIRepository() async {
|
||||
await getIt.unregister<AIRepository>();
|
||||
getIt.registerFactoryAsync<AIRepository>(
|
||||
() => Future.value(
|
||||
MockOpenAIRepository(),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
@ -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<void> useTestSupabaseCloud() async {
|
||||
|
@ -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';
|
||||
|
@ -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';
|
||||
|
@ -387,6 +387,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
editorState: editorState,
|
||||
editorScrollController: editorScrollController,
|
||||
textDirection: textDirection,
|
||||
tooltipBuilder: (context, id, message, child) => widget.styleCustomizer
|
||||
.buildToolbarItemTooltip(context, id, message, child,),
|
||||
child: editor,
|
||||
),
|
||||
);
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -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;
|
||||
},
|
||||
);
|
||||
|
@ -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<SmartEditActionList> createState() => _SmartEditActionListState();
|
||||
@ -60,11 +65,8 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||
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<SmartEditActionList> {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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(
|
||||
|
@ -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(
|
@ -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<IconPicker> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user