feat: support customizing page icon (#3849)

* chore: don't use cache when building release package

* feat: refactor icon widget design

* feat: sync the emoji between page and view

* feat: use cache to store the emoji data to prevent reloading

* feat: customize the emoji item builder

* feat: add i18n and shuffle emoji button

* fix: integration test

* feat: replace emoji picker in Grid and slash menu

* feat: support adding icon on mobile platform

* feat: support adding and removing icon on mobile

* test: add integration tests
This commit is contained in:
Lucas.Xu
2023-11-02 15:24:17 +08:00
committed by GitHub
parent 21d34d1fe0
commit c34a7a92fb
34 changed files with 1116 additions and 256 deletions

View File

@ -42,7 +42,6 @@ void main() {
await tester.hoverRowBanner();
await tester.openEmojiPicker();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
// After select the emoji, the EmojiButton will show up
@ -60,12 +59,10 @@ void main() {
await tester.openFirstRowDetailPage();
await tester.hoverRowBanner();
await tester.openEmojiPicker();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
// Update existing selected emoji
await tester.tapButton(find.byType(EmojiButton));
await tester.switchToEmojiList();
await tester.tapEmoji('😅');
// The emoji already displayed in the row banner
@ -89,7 +86,6 @@ void main() {
await tester.openFirstRowDetailPage();
await tester.hoverRowBanner();
await tester.openEmojiPicker();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
// Remove the emoji

View File

@ -1,4 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:emoji_mart/emoji_mart.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
@ -61,7 +65,6 @@ void main() {
// Insert a document icon
await tester.editor.tapAddIconButton();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
tester.expectToSeeDocumentIcon('😀');
@ -73,13 +76,11 @@ void main() {
// Add the icon back for further testing
await tester.editor.hoverOnCoverToolbar();
await tester.editor.tapAddIconButton();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
tester.expectToSeeDocumentIcon('😀');
// Change the document icon
await tester.editor.tapOnIconWidget();
await tester.switchToEmojiList();
await tester.tapEmoji('😅');
tester.expectToSeeDocumentIcon('😅');
@ -102,7 +103,6 @@ void main() {
// Insert a document icon
await tester.editor.tapAddIconButton();
await tester.switchToEmojiList();
await tester.tapEmoji('😀');
// Insert a document cover
@ -116,5 +116,46 @@ void main() {
await tester.editor.hoverOnCoverToolbar();
tester.expectToSeeEmptyDocumentHeaderToolbar();
});
testWidgets('shuffle icon', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.editor.hoverOnCoverToolbar();
await tester.editor.tapAddIconButton();
// click the shuffle button
await tester.tapButton(
find.byTooltip(LocaleKeys.emoji_random.tr()),
);
tester.expectDocumentIconNotNull();
});
testWidgets('change skin tone', (tester) async {
await tester.initializeAppFlowy();
await tester.tapGoButton();
await tester.editor.hoverOnCoverToolbar();
await tester.editor.tapAddIconButton();
final searchEmojiTextField = find.byWidgetPredicate(
(widget) =>
widget is TextField &&
widget.decoration!.hintText == LocaleKeys.emoji_search.tr(),
);
await tester.enterText(
searchEmojiTextField,
'hand',
);
// change skin tone
await tester.editor.changeEmojiSkinTone(EmojiSkinTone.dark);
// select an icon with skin tone
const hand = '👋🏿';
await tester.tapEmoji(hand);
tester.expectToSeeDocumentIcon(hand);
tester.isPageWithIcon(gettingStarted, hand);
});
});
}

View File

@ -1,9 +1,11 @@
import 'dart:async';
import 'dart:io';
import 'package:appflowy/env/env.dart';
import 'package:appflowy/startup/entry_point.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/presentation.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/application/settings/prelude.dart';
import 'package:flowy_infra/uuid.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -81,9 +83,15 @@ extension AppFlowyTestBase on WidgetTester {
}
Future<void> waitUntilSignInPageShow() async {
final finder = find.byType(GoButton);
await pumpUntilFound(finder);
expect(finder, findsOneWidget);
if (isCloudEnabled) {
final finder = find.byType(SignInAnonymousButton);
await pumpUntilFound(finder);
expect(finder, findsOneWidget);
} else {
final finder = find.byType(GoButton);
await pumpUntilFound(finder);
expect(finder, findsOneWidget);
}
}
Future<void> pumpUntilFound(

View File

@ -7,6 +7,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/screens/screens.dart';
import 'package:appflowy/user/presentation/screens/sign_in_screen/widgets/widgets.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_new_page_button.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
@ -27,8 +28,15 @@ import 'util.dart';
extension CommonOperations on WidgetTester {
/// Tap the GetStart button on the launch page.
Future<void> tapGoButton() async {
// local version
final goButton = find.byType(GoButton);
await tapButton(goButton);
if (goButton.evaluate().isNotEmpty) {
await tapButton(goButton);
} else {
// cloud version
final anonymousButton = find.byType(SignInAnonymousButton);
await tapButton(anonymousButton);
}
if (Platform.isWindows) {
await pumpAndSettle(const Duration(milliseconds: 200));

View File

@ -38,7 +38,6 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_header.dart';
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_property_list.dart';
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cells/checklist_cell/checklist_cell_editor.dart';
@ -53,7 +52,7 @@ import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_document.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_property.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_menu_item.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/setting_property_list.dart';
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
@ -618,7 +617,6 @@ extension AppFlowyDatabaseTest on WidgetTester {
Future<void> openEmojiPicker() async {
await tapButton(find.byType(EmojiPickerButton));
await tapButton(find.byType(EmojiSelectionMenu));
}
Future<void> tapDateCellInRowDetailPage() async {

View File

@ -1,15 +1,18 @@
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/plugins/base/icon/icon_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_add_button.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/cover_editor.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/custom_cover_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_header_node_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_popover.dart';
import 'package:appflowy/plugins/inline_actions/widgets/inline_actions_handler.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:easy_localization/easy_localization.dart';
import 'package:emoji_mart/emoji_mart.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
@ -54,7 +57,18 @@ class EditorOperations {
await tester.tapButtonWithName(
LocaleKeys.document_plugins_cover_addIcon.tr(),
);
expect(find.byType(EmojiPopover), findsOneWidget);
expect(find.byType(FlowyEmojiPicker), findsOneWidget);
}
/// Taps on the 'Skin tone' button
///
/// Must call [tapAddIconButton] first.
Future<void> changeEmojiSkinTone(EmojiSkinTone skinTone) async {
await tester.tapButton(
find.byTooltip(LocaleKeys.emoji_selectSkinTone.tr()),
);
final skinToneButton = find.text(EmojiSkinToneWrapper(skinTone).name);
await tester.tapButton(skinToneButton);
}
/// Taps the 'Remove Icon' button in the cover toolbar and the icon popover
@ -62,7 +76,10 @@ class EditorOperations {
Finder button =
find.text(LocaleKeys.document_plugins_cover_removeIcon.tr());
if (isInPicker) {
button = find.descendant(of: find.byType(EmojiPopover), matching: button);
button = find.descendant(
of: find.byType(FlowyIconPicker),
matching: button,
);
}
await tester.tapButton(button);

View File

@ -1,15 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'base.dart';
extension EmojiTestExtension on WidgetTester {
/// Must call [openEmojiPicker] first
Future<void> switchToEmojiList() async {
final icon = find.byIcon(Icons.tag_faces);
await tapButton(icon);
}
Future<void> tapEmoji(String emoji) async {
final emojiWidget = find.text(emoji);
await tapButton(emojiWidget);

View File

@ -108,6 +108,13 @@ extension Expectation on WidgetTester {
expect(iconWidget, findsOneWidget);
}
void expectDocumentIconNotNull() {
final iconWidget = find.byWidgetPredicate(
(widget) => widget is EmojiIconWidget && widget.emoji.isNotEmpty,
);
expect(iconWidget, findsOneWidget);
}
void expectToSeeDocumentCover(CoverType type) {
final findCover = find.byWidgetPredicate(
(widget) => widget is DocumentCover && widget.coverType == type,
@ -193,4 +200,13 @@ extension Expectation on WidgetTester {
matching: findPageName(name, layout: layout),
);
}
void isPageWithIcon(String name, String emoji) {
final pageName = findPageName(name);
final icon = find.descendant(
of: pageName,
matching: find.text(emoji),
);
expect(icon, findsOneWidget);
}
}