Skip to content

Commit b8e3a3f

Browse files
committed
fix: icon picker issues on mobile (#7113)
* fix: error displaying in Page style * fix: error displaying in Favorite/Recent page * fix: complete the filter logic of icon picker * fix: the color picker showed when tapping down * fix: icons are not supported in subpage blocks * chore: add some tests * fix: recent icons not working for grid header icon
1 parent d175735 commit b8e3a3f

File tree

13 files changed

+239
-63
lines changed

13 files changed

+239
-63
lines changed

frontend/appflowy_flutter/integration_test/desktop/document/document_sub_page_test.dart

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import 'dart:io';
22

33
import 'package:appflowy/generated/locale_keys.g.dart';
4+
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
45
import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart';
6+
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
57
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
68
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
79
import 'package:appflowy_editor/appflowy_editor.dart';
@@ -11,6 +13,7 @@ import 'package:flutter/services.dart';
1113
import 'package:flutter_test/flutter_test.dart';
1214
import 'package:integration_test/integration_test.dart';
1315

16+
import '../../shared/emoji.dart';
1417
import '../../shared/util.dart';
1518

1619
// Test cases for the Document SubPageBlock that needs to be covered:
@@ -37,7 +40,14 @@ import '../../shared/util.dart';
3740
const _defaultPageName = "";
3841

3942
void main() {
40-
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
43+
setUpAll(() {
44+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
45+
RecentIcons.enable = false;
46+
});
47+
48+
tearDownAll(() {
49+
RecentIcons.enable = true;
50+
});
4151

4252
group('Document SubPageBlock tests', () {
4353
testWidgets('Insert a new SubPageBlock from Slash menu items',
@@ -498,6 +508,38 @@ void main() {
498508

499509
expect(find.text('Parent'), findsNWidgets(2));
500510
});
511+
512+
testWidgets('Displaying icon of subpage', (tester) async {
513+
const firstPage = 'FirstPage';
514+
515+
await tester.initializeAppFlowy();
516+
await tester.tapAnonymousSignInButton();
517+
await tester.createNewPageWithNameUnderParent(name: firstPage);
518+
final icon = await tester.loadIcon();
519+
520+
/// create subpage
521+
await tester.editor.tapLineOfEditorAt(0);
522+
await tester.editor.showSlashMenu();
523+
await tester.editor.tapSlashMenuItemWithName(
524+
LocaleKeys.document_slashMenu_subPage_name.tr(),
525+
offset: 100,
526+
);
527+
528+
/// add icon
529+
await tester.editor.hoverOnCoverToolbar();
530+
await tester.editor.tapAddIconButton();
531+
await tester.tapIcon(icon);
532+
await tester.pumpAndSettle();
533+
await tester.openPage(firstPage);
534+
535+
/// check if there is a icon in document
536+
final iconWidget = find.byWidgetPredicate((w) {
537+
if (w is! RawEmojiIconWidget) return false;
538+
final iconData = w.emoji.emoji;
539+
return iconData == icon.emoji;
540+
});
541+
expect(iconWidget, findsOneWidget);
542+
});
501543
});
502544
}
503545

frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
22
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
33
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
4-
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
54
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
6-
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
75
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
86
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
97
import 'package:flowy_infra_ui/style_widget/text_field.dart';
@@ -27,21 +25,6 @@ void main() {
2725
RecentIcons.enable = true;
2826
});
2927

30-
Future<EmojiIconData> loadIcon() async {
31-
await loadIconGroups();
32-
final groups = kIconGroups!;
33-
final firstGroup = groups.first;
34-
final firstIcon = firstGroup.icons.first;
35-
return EmojiIconData.icon(
36-
IconsData(
37-
firstGroup.name,
38-
firstIcon.content,
39-
firstIcon.name,
40-
builtInSpaceColors.first,
41-
),
42-
);
43-
}
44-
4528
testWidgets('Update page emoji in sidebar', (tester) async {
4629
await tester.initializeAppFlowy();
4730
await tester.tapAnonymousSignInButton();
@@ -160,7 +143,7 @@ void main() {
160143
testWidgets('Update page icon in sidebar', (tester) async {
161144
await tester.initializeAppFlowy();
162145
await tester.tapAnonymousSignInButton();
163-
final iconData = await loadIcon();
146+
final iconData = await tester.loadIcon();
164147

165148
// create document, board, grid and calendar views
166149
for (final value in ViewLayoutPB.values) {
@@ -192,7 +175,7 @@ void main() {
192175
testWidgets('Update page icon in title bar', (tester) async {
193176
await tester.initializeAppFlowy();
194177
await tester.tapAnonymousSignInButton();
195-
final iconData = await loadIcon();
178+
final iconData = await tester.loadIcon();
196179

197180
// create document, board, grid and calendar views
198181
for (final value in ViewLayoutPB.values) {

frontend/appflowy_flutter/integration_test/mobile/document/page_style_test.dart

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,30 @@
11
import 'package:appflowy/generated/flowy_svgs.g.dart';
2+
import 'package:appflowy/generated/locale_keys.g.dart';
23
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
34
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
45
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
6+
import 'package:appflowy/mobile/presentation/mobile_bottom_navigation_bar.dart';
57
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
68
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
9+
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart';
10+
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
11+
import 'package:easy_localization/easy_localization.dart';
712
import 'package:flutter/material.dart';
813
import 'package:flutter_test/flutter_test.dart';
914
import 'package:integration_test/integration_test.dart';
1015

16+
import '../../shared/emoji.dart';
1117
import '../../shared/util.dart';
1218

1319
void main() {
14-
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
20+
setUpAll(() {
21+
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
22+
RecentIcons.enable = false;
23+
});
24+
25+
tearDownAll(() {
26+
RecentIcons.enable = true;
27+
});
1528

1629
group('document page style:', () {
1730
double getCurrentEditorFontSize() {
@@ -114,5 +127,37 @@ void main() {
114127
);
115128
expect(builtInCover, findsOneWidget);
116129
});
130+
131+
testWidgets('page style icon', (tester) async {
132+
await tester.launchInAnonymousMode();
133+
134+
final createPageButton =
135+
find.byKey(BottomNavigationBarItemType.add.valueKey);
136+
await tester.tapButton(createPageButton);
137+
138+
/// toggle the preset button
139+
await tester.tapSvgButton(FlowySvgs.m_layout_s);
140+
141+
/// select document plugins emoji
142+
final pageStyleIcon = find.byType(PageStyleIcon);
143+
144+
/// there should be none of emoji
145+
final noneText = find.text(LocaleKeys.pageStyle_none.tr());
146+
expect(noneText, findsOneWidget);
147+
await tester.tapButton(pageStyleIcon);
148+
149+
/// select an emoji
150+
const emoji = '😄';
151+
await tester.tapEmoji(emoji);
152+
await tester.tapSvgButton(FlowySvgs.m_layout_s);
153+
expect(noneText, findsNothing);
154+
expect(
155+
find.descendant(
156+
of: pageStyleIcon,
157+
matching: find.text(emoji),
158+
),
159+
findsOneWidget,
160+
);
161+
});
117162
});
118163
}

frontend/appflowy_flutter/integration_test/shared/base.dart

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,33 @@ extension AppFlowyTestBase on WidgetTester {
175175
}
176176
}
177177

178+
Future<void> tapDown(
179+
Finder finder, {
180+
int? pointer,
181+
int buttons = kPrimaryButton,
182+
PointerDeviceKind kind = PointerDeviceKind.touch,
183+
bool pumpAndSettle = true,
184+
int milliseconds = 500,
185+
}) async {
186+
final location = getCenter(finder);
187+
final TestGesture gesture = await startGesture(
188+
location,
189+
pointer: pointer,
190+
buttons: buttons,
191+
kind: kind,
192+
);
193+
await gesture.cancel();
194+
await gesture.down(location);
195+
await gesture.cancel();
196+
if (pumpAndSettle) {
197+
await this.pumpAndSettle(
198+
Duration(milliseconds: milliseconds),
199+
EnginePhase.sendSemanticsUpdate,
200+
const Duration(seconds: 15),
201+
);
202+
}
203+
}
204+
178205
Future<void> tapButtonWithName(
179206
String tr, {
180207
int milliseconds = 500,

frontend/appflowy_flutter/integration_test/shared/common_operations.dart

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_tab
1313
import 'package:appflowy/plugins/shared/share/share_button.dart';
1414
import 'package:appflowy/shared/feature_flags.dart';
1515
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
16+
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
1617
import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart';
1718
import 'package:appflowy/startup/startup.dart';
1819
import 'package:appflowy/user/presentation/screens/screens.dart';
@@ -23,6 +24,7 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar
2324
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
2425
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
2526
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
27+
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
2628
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
2729
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
2830
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
@@ -898,6 +900,22 @@ extension CommonOperations on WidgetTester {
898900
await tapAt(Offset.zero);
899901
await pumpUntilNotFound(finder);
900902
}
903+
904+
/// load icon list and return the first one
905+
Future<EmojiIconData> loadIcon() async {
906+
await loadIconGroups();
907+
final groups = kIconGroups!;
908+
final firstGroup = groups.first;
909+
final firstIcon = firstGroup.icons.first;
910+
return EmojiIconData.icon(
911+
IconsData(
912+
firstGroup.name,
913+
firstIcon.content,
914+
firstIcon.name,
915+
builtInSpaceColors.first,
916+
),
917+
);
918+
}
901919
}
902920

903921
extension SettingsFinder on CommonFinders {

frontend/appflowy_flutter/integration_test/shared/emoji.dart

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ extension EmojiTestExtension on WidgetTester {
4242
),
4343
);
4444
expect(find.byType(IconColorPicker), findsNothing);
45+
46+
/// test for tapping down, it should not display the ColorPicker unless tapping up
47+
await tapDown(selectedSvg);
48+
expect(find.byType(IconColorPicker), findsNothing);
49+
4550
await tapButton(selectedSvg);
4651
final colorPicker = find.byType(IconColorPicker);
4752
expect(colorPicker, findsOneWidget);

frontend/appflowy_flutter/lib/mobile/presentation/home/shared/mobile_page_card.dart

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -178,15 +178,16 @@ class MobileViewPage extends StatelessWidget {
178178
overflow: TextOverflow.ellipsis,
179179
text: TextSpan(
180180
children: [
181-
WidgetSpan(
182-
child: SizedBox(
183-
width: 20,
184-
child: EmojiIconWidget(
185-
emoji: icon,
186-
emojiSize: 17.0,
181+
if (icon.isNotEmpty)
182+
WidgetSpan(
183+
child: SizedBox(
184+
width: 20,
185+
child: EmojiIconWidget(
186+
emoji: icon,
187+
emojiSize: 17.0,
188+
),
187189
),
188190
),
189-
),
190191
if (icon.isNotEmpty) const WidgetSpan(child: HSpace(2.0)),
191192
TextSpan(
192193
text: name,

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ class _PageStyleIconState extends State<PageStyleIcon> {
4848
const HSpace(16.0),
4949
FlowyText(LocaleKeys.document_plugins_emoji.tr()),
5050
const Spacer(),
51-
RawEmojiIconWidget(
52-
emoji: icon.isNotEmpty
53-
? icon
54-
: EmojiIconData.emoji(LocaleKeys.pageStyle_none.tr()),
55-
emojiSize: icon.isNotEmpty ? 22.0 : 16.0,
56-
),
51+
icon.isEmpty
52+
? FlowyText(
53+
LocaleKeys.pageStyle_none.tr(),
54+
fontSize: 16.0,
55+
)
56+
: RawEmojiIconWidget(
57+
emoji: icon,
58+
emojiSize: 22.0,
59+
),
5760
const HSpace(6.0),
5861
const FlowySvg(FlowySvgs.m_page_style_arrow_right_s),
5962
const HSpace(12.0),

frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import 'package:appflowy/generated/locale_keys.g.dart';
22
import 'package:appflowy/mobile/application/mobile_router.dart';
3+
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
34
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
45
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
56
import 'package:appflowy/plugins/trash/application/trash_listener.dart';
7+
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
68
import 'package:appflowy/startup/startup.dart';
79
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
810
import 'package:appflowy/workspace/application/view/view_ext.dart';
@@ -15,7 +17,6 @@ import 'package:appflowy_editor/appflowy_editor.dart';
1517
import 'package:appflowy_result/appflowy_result.dart';
1618
import 'package:collection/collection.dart';
1719
import 'package:easy_localization/easy_localization.dart';
18-
import 'package:flowy_infra/theme_extension.dart';
1920
import 'package:flowy_infra_ui/style_widget/text.dart';
2021
import 'package:flowy_infra_ui/widget/spacing.dart';
2122
import 'package:flutter/material.dart';
@@ -242,12 +243,9 @@ class SubPageBlockComponentState extends State<SubPageBlockComponent>
242243
children: [
243244
const HSpace(10),
244245
view.icon.value.isNotEmpty
245-
? FlowyText.emoji(
246-
view.icon.value,
247-
fontSize: textStyle.fontSize,
248-
lineHeight: textStyle.height,
249-
color:
250-
AFThemeExtension.of(context).strongText,
246+
? RawEmojiIconWidget(
247+
emoji: view.icon.toEmojiIconData(),
248+
emojiSize: textStyle.fontSize ?? 16.0,
251249
)
252250
: view.defaultIcon(),
253251
const HSpace(6),

frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon.dart

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,9 @@ class IconGroup {
3939
final filteredIcons = icons
4040
.where(
4141
(icon) =>
42-
icon.keywords.any((k) => k.contains(lowercaseKey)) ||
43-
icon.name.contains(lowercaseKey),
42+
icon.keywords
43+
.any((k) => k.toLowerCase().contains(lowercaseKey)) ||
44+
icon.name.toLowerCase().contains(lowercaseKey),
4445
)
4546
.toList();
4647
return IconGroup(name: name, icons: filteredIcons);
@@ -84,3 +85,23 @@ class Icon {
8485
return '${iconGroup!.name}/$name';
8586
}
8687
}
88+
89+
class RecentIcon {
90+
factory RecentIcon.fromJson(Map<String, dynamic> json) =>
91+
RecentIcon(_$IconFromJson(json), json['groupName'] ?? '');
92+
93+
RecentIcon(this.icon, this.groupName);
94+
95+
final Icon icon;
96+
final String groupName;
97+
98+
String get name => icon.name;
99+
100+
List<String> get keywords => icon.keywords;
101+
102+
String get content => icon.content;
103+
104+
Map<String, dynamic> toJson() => _$IconToJson(
105+
Icon(name: name, keywords: keywords, content: content),
106+
)..addAll({'groupName': groupName});
107+
}

0 commit comments

Comments
 (0)