fix: icon picker issues on mobile (#7114)

* 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

* fix: recent icon doesn't work in space icon (#7133)

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
Morn 2025-01-06 20:04:49 +08:00 committed by Lucas.Xu
parent 1d1647d58d
commit 776f70a0c7
20 changed files with 344 additions and 96 deletions

View file

@ -2,6 +2,7 @@ import 'data_migration/data_migration_test_runner.dart'
as data_migration_test_runner;
import 'document/document_test_runner.dart' as document_test_runner;
import 'set_env.dart' as preset_af_cloud_env_test;
import 'sidebar/sidebar_icon_test.dart' as sidebar_icon_test;
import 'sidebar/sidebar_move_page_test.dart' as sidebar_move_page_test;
import 'sidebar/sidebar_rename_untitled_test.dart'
as sidebar_rename_untitled_test;
@ -26,4 +27,5 @@ Future<void> main() async {
// sidebar
sidebar_move_page_test.main();
sidebar_rename_untitled_test.main();
sidebar_icon_test.main();
}

View file

@ -0,0 +1,62 @@
import 'dart:convert';
import 'package:appflowy/env/cloud_env.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_action_type.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_more_popup.dart';
import 'package:flowy_svg/flowy_svg.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../../shared/emoji.dart';
import '../../../shared/util.dart';
void main() {
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
testWidgets('Change slide bar space icon', (tester) async {
await tester.initializeAppFlowy(
cloudType: AuthenticatorType.appflowyCloudSelfHost,
);
await tester.tapGoogleLoginInButton();
await tester.expectToSeeHomePageWithGetStartedPage();
final emojiIconData = await tester.loadIcon();
final firstIcon = IconsData.fromJson(jsonDecode(emojiIconData.emoji));
await tester.hoverOnWidget(
find.byType(SidebarSpaceHeader),
onHover: () async {
final moreOption = find.byType(SpaceMorePopup);
await tester.tapButton(moreOption);
expect(find.byType(FlowyIconEmojiPicker), findsNothing);
await tester.tapSvgButton(SpaceMoreActionType.changeIcon.leftIconSvg);
expect(find.byType(FlowyIconEmojiPicker), findsOneWidget);
},
);
final icons = find.byWidgetPredicate(
(w) => w is FlowySvg && w.svgString == firstIcon.iconContent,
);
expect(icons, findsOneWidget);
await tester.tapIcon(EmojiIconData.icon(firstIcon));
final spaceHeader = find.byType(SidebarSpaceHeader);
final spaceIcon = find.descendant(
of: spaceHeader,
matching: find.byWidgetPredicate(
(w) => w is FlowySvg && w.svgString == firstIcon.iconContent,
),
);
expect(spaceIcon, findsOneWidget);
});
}

View file

@ -1,7 +1,9 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/sub_page/sub_page_block_component.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/view_action_type.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
@ -11,6 +13,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/emoji.dart';
import '../../shared/util.dart';
// Test cases for the Document SubPageBlock that needs to be covered:
@ -37,7 +40,14 @@ import '../../shared/util.dart';
const _defaultPageName = "";
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('Document SubPageBlock tests', () {
testWidgets('Insert a new SubPageBlock from Slash menu items',
@ -498,6 +508,38 @@ void main() {
expect(find.text('Parent'), findsNWidgets(2));
});
testWidgets('Displaying icon of subpage', (tester) async {
const firstPage = 'FirstPage';
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
await tester.createNewPageWithNameUnderParent(name: firstPage);
final icon = await tester.loadIcon();
/// create subpage
await tester.editor.tapLineOfEditorAt(0);
await tester.editor.showSlashMenu();
await tester.editor.tapSlashMenuItemWithName(
LocaleKeys.document_slashMenu_subPage_name.tr(),
offset: 100,
);
/// add icon
await tester.editor.hoverOnCoverToolbar();
await tester.editor.tapAddIconButton();
await tester.tapIcon(icon);
await tester.pumpAndSettle();
await tester.openPage(firstPage);
/// check if there is a icon in document
final iconWidget = find.byWidgetPredicate((w) {
if (w is! RawEmojiIconWidget) return false;
final iconData = w.emoji.emoji;
return iconData == icon.emoji;
});
expect(iconWidget, findsOneWidget);
});
});
}

View file

@ -1,9 +1,7 @@
import 'package:appflowy/plugins/base/emoji/emoji_picker.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/emoji_picker_button.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
import 'package:appflowy/workspace/presentation/widgets/view_title_bar.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_infra_ui/style_widget/text_field.dart';
@ -27,21 +25,6 @@ void main() {
RecentIcons.enable = true;
});
Future<EmojiIconData> loadIcon() async {
await loadIconGroups();
final groups = kIconGroups!;
final firstGroup = groups.first;
final firstIcon = firstGroup.icons.first;
return EmojiIconData.icon(
IconsData(
firstGroup.name,
firstIcon.content,
firstIcon.name,
builtInSpaceColors.first,
),
);
}
testWidgets('Update page emoji in sidebar', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
@ -160,7 +143,7 @@ void main() {
testWidgets('Update page icon in sidebar', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
final iconData = await loadIcon();
final iconData = await tester.loadIcon();
// create document, board, grid and calendar views
for (final value in ViewLayoutPB.values) {
@ -192,7 +175,7 @@ void main() {
testWidgets('Update page icon in title bar', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
final iconData = await loadIcon();
final iconData = await tester.loadIcon();
// create document, board, grid and calendar views
for (final value in ViewLayoutPB.values) {

View file

@ -1,17 +1,30 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/page_style/document_page_style_bloc.dart';
import 'package:appflowy/mobile/presentation/base/view_page/app_bar_buttons.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_buttons.dart';
import 'package:appflowy/mobile/presentation/mobile_bottom_navigation_bar.dart';
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/cover/document_immersive_cover.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/page_style/_page_style_icon.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import '../../shared/emoji.dart';
import '../../shared/util.dart';
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
setUpAll(() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
RecentIcons.enable = false;
});
tearDownAll(() {
RecentIcons.enable = true;
});
group('document page style:', () {
double getCurrentEditorFontSize() {
@ -114,5 +127,37 @@ void main() {
);
expect(builtInCover, findsOneWidget);
});
testWidgets('page style icon', (tester) async {
await tester.launchInAnonymousMode();
final createPageButton =
find.byKey(BottomNavigationBarItemType.add.valueKey);
await tester.tapButton(createPageButton);
/// toggle the preset button
await tester.tapSvgButton(FlowySvgs.m_layout_s);
/// select document plugins emoji
final pageStyleIcon = find.byType(PageStyleIcon);
/// there should be none of emoji
final noneText = find.text(LocaleKeys.pageStyle_none.tr());
expect(noneText, findsOneWidget);
await tester.tapButton(pageStyleIcon);
/// select an emoji
const emoji = '😄';
await tester.tapEmoji(emoji);
await tester.tapSvgButton(FlowySvgs.m_layout_s);
expect(noneText, findsNothing);
expect(
find.descendant(
of: pageStyleIcon,
matching: find.text(emoji),
),
findsOneWidget,
);
});
});
}

View file

@ -175,6 +175,33 @@ extension AppFlowyTestBase on WidgetTester {
}
}
Future<void> tapDown(
Finder finder, {
int? pointer,
int buttons = kPrimaryButton,
PointerDeviceKind kind = PointerDeviceKind.touch,
bool pumpAndSettle = true,
int milliseconds = 500,
}) async {
final location = getCenter(finder);
final TestGesture gesture = await startGesture(
location,
pointer: pointer,
buttons: buttons,
kind: kind,
);
await gesture.cancel();
await gesture.down(location);
await gesture.cancel();
if (pumpAndSettle) {
await this.pumpAndSettle(
Duration(milliseconds: milliseconds),
EnginePhase.sendSemanticsUpdate,
const Duration(seconds: 15),
);
}
}
Future<void> tapButtonWithName(
String tr, {
int milliseconds = 500,

View file

@ -13,6 +13,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/simple_tab
import 'package:appflowy/plugins/shared/share/share_button.dart';
import 'package:appflowy/shared/feature_flags.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart';
import 'package:appflowy/shared/text_field/text_filed_with_metric_lines.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/user/presentation/screens/screens.dart';
@ -23,6 +24,7 @@ import 'package:appflowy/workspace/presentation/home/menu/sidebar/shared/sidebar
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/shared_widget.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_header.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/sidebar_space_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/space/space_icon_popup.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/_sidebar_workspace_menu.dart';
import 'package:appflowy/workspace/presentation/home/menu/sidebar/workspace/sidebar_workspace.dart';
import 'package:appflowy/workspace/presentation/home/menu/view/draggable_view_item.dart';
@ -898,6 +900,22 @@ extension CommonOperations on WidgetTester {
await tapAt(Offset.zero);
await pumpUntilNotFound(finder);
}
/// load icon list and return the first one
Future<EmojiIconData> loadIcon() async {
await loadIconGroups();
final groups = kIconGroups!;
final firstGroup = groups.first;
final firstIcon = firstGroup.icons.first;
return EmojiIconData.icon(
IconsData(
firstGroup.name,
firstIcon.content,
firstIcon.name,
builtInSpaceColors.first,
),
);
}
}
extension SettingsFinder on CommonFinders {

View file

@ -31,10 +31,7 @@ extension EmojiTestExtension on WidgetTester {
matching: find.text(PickerTabType.icon.tr),
);
expect(iconTab, findsOneWidget);
expect(find.byType(FlowyIconPicker), findsNothing);
await tap(iconTab);
await pumpAndSettle();
expect(find.byType(FlowyIconPicker), findsOneWidget);
await tapButton(iconTab);
final selectedSvg = find.descendant(
of: find.byType(FlowyIconPicker),
matching: find.byWidgetPredicate(
@ -42,6 +39,11 @@ extension EmojiTestExtension on WidgetTester {
),
);
expect(find.byType(IconColorPicker), findsNothing);
/// test for tapping down, it should not display the ColorPicker unless tapping up
await tapDown(selectedSvg);
expect(find.byType(IconColorPicker), findsNothing);
await tapButton(selectedSvg);
final colorPicker = find.byType(IconColorPicker);
expect(colorPicker, findsOneWidget);

View file

@ -268,8 +268,8 @@ class _MobileViewPageState extends State<MobileViewPage> {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (icon != null) ...[
EmojiIconWidget(
if (icon != null && icon.value.isNotEmpty) ...[
RawEmojiIconWidget(
emoji: icon.toEmojiIconData(),
emojiSize: 15,
),

View file

@ -5,6 +5,7 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/home/workspaces/workspace_menu_bottom_sheet.dart';
import 'package:appflowy/plugins/base/emoji/emoji_picker_screen.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/tab.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/built_in_svgs.dart';
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
@ -234,6 +235,7 @@ class _UserIcon extends StatelessWidget {
queryParameters: {
MobileEmojiPickerScreen.pageTitle:
LocaleKeys.titleBar_userIcon.tr(),
MobileEmojiPickerScreen.selectTabs: [PickerTabType.emoji.name],
},
).toString(),
);

View file

@ -178,16 +178,18 @@ class MobileViewPage extends StatelessWidget {
overflow: TextOverflow.ellipsis,
text: TextSpan(
children: [
WidgetSpan(
child: SizedBox(
width: 20,
child: EmojiIconWidget(
emoji: icon,
emojiSize: 17.0,
if (icon.isNotEmpty) ...[
WidgetSpan(
child: SizedBox(
width: 20,
child: EmojiIconWidget(
emoji: icon,
emojiSize: 18.0,
),
),
),
),
if (icon.isNotEmpty) const WidgetSpan(child: HSpace(2.0)),
const WidgetSpan(child: HSpace(8.0)),
],
TextSpan(
text: name,
style: Theme.of(context).textTheme.bodyMedium!.copyWith(

View file

@ -79,13 +79,10 @@ class RawEmojiIconWidget extends StatelessWidget {
try {
switch (emoji.type) {
case FlowyIconType.emoji:
return SizedBox(
width: emojiSize,
child: EmojiText(
emoji: emoji.emoji,
fontSize: emojiSize,
textAlign: TextAlign.center,
),
return EmojiText(
emoji: emoji.emoji,
fontSize: emojiSize,
textAlign: TextAlign.center,
);
case FlowyIconType.icon:
final iconData = IconsData.fromJson(jsonDecode(emoji.emoji));

View file

@ -48,12 +48,15 @@ class _PageStyleIconState extends State<PageStyleIcon> {
const HSpace(16.0),
FlowyText(LocaleKeys.document_plugins_emoji.tr()),
const Spacer(),
RawEmojiIconWidget(
emoji: icon.isNotEmpty
? icon
: EmojiIconData.emoji(LocaleKeys.pageStyle_none.tr()),
emojiSize: icon.isNotEmpty ? 22.0 : 16.0,
),
icon.isEmpty
? FlowyText(
LocaleKeys.pageStyle_none.tr(),
fontSize: 16.0,
)
: RawEmojiIconWidget(
emoji: icon,
emojiSize: 22.0,
),
const HSpace(6.0),
const FlowySvg(FlowySvgs.m_page_style_arrow_right_s),
const HSpace(12.0),

View file

@ -1,8 +1,10 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/application/mobile_router.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/emoji_icon_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_page_block.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/shared_context/shared_context.dart';
import 'package:appflowy/plugins/trash/application/trash_listener.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart';
@ -15,7 +17,6 @@ import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_result/appflowy_result.dart';
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/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
@ -244,12 +245,9 @@ class SubPageBlockComponentState extends State<SubPageBlockComponent>
children: [
const HSpace(10),
view.icon.value.isNotEmpty
? FlowyText.emoji(
view.icon.value,
fontSize: textStyle.fontSize,
lineHeight: textStyle.height,
color:
AFThemeExtension.of(context).strongText,
? RawEmojiIconWidget(
emoji: view.icon.toEmojiIconData(),
emojiSize: textStyle.fontSize ?? 16.0,
)
: view.defaultIcon(),
const HSpace(6),

View file

@ -39,8 +39,9 @@ class IconGroup {
final filteredIcons = icons
.where(
(icon) =>
icon.keywords.any((k) => k.contains(lowercaseKey)) ||
icon.name.contains(lowercaseKey),
icon.keywords
.any((k) => k.toLowerCase().contains(lowercaseKey)) ||
icon.name.toLowerCase().contains(lowercaseKey),
)
.toList();
return IconGroup(name: name, icons: filteredIcons);
@ -84,3 +85,23 @@ class Icon {
return '${iconGroup!.name}/$name';
}
}
class RecentIcon {
factory RecentIcon.fromJson(Map<String, dynamic> json) =>
RecentIcon(_$IconFromJson(json), json['groupName'] ?? '');
RecentIcon(this.icon, this.groupName);
final Icon icon;
final String groupName;
String get name => icon.name;
List<String> get keywords => icon.keywords;
String get content => icon.content;
Map<String, dynamic> toJson() => _$IconToJson(
Icon(name: name, keywords: keywords, content: content),
)..addAll({'groupName': groupName});
}

View file

@ -118,10 +118,13 @@ class _FlowyIconPickerState extends State<FlowyIconPicker> {
iconGroups.add(
IconGroup(
name: _kRecentIconGroupName,
icons: recentIcons.sublist(
0,
min(recentIcons.length, widget.iconPerLine),
),
icons: recentIcons
.sublist(
0,
min(recentIcons.length, widget.iconPerLine),
)
.map((e) => e.icon)
.toList(),
),
);
}
@ -171,7 +174,7 @@ class _FlowyIconPickerState extends State<FlowyIconPicker> {
color,
).toResult(isRandom: true),
);
RecentIcons.putIcon(value.$2);
RecentIcons.putIcon(RecentIcon(value.$2, value.$1.name));
},
onKeywordChanged: (keyword) => {
debounce.call(() {
@ -303,30 +306,38 @@ class _IconPickerState extends State<IconPicker> {
icon: icon,
mutex: mutex,
onSelectedColor: (context, color) {
String groupName = iconGroup.name;
if (groupName == _kRecentIconGroupName) {
groupName = getGroupName(index);
}
widget.onSelectedIcon(
IconsData(
iconGroup.name,
groupName,
icon.content,
icon.name,
color,
),
);
RecentIcons.putIcon(icon);
RecentIcons.putIcon(RecentIcon(icon, groupName));
PopoverContainer.of(context).close();
},
)
: _IconNoBackground(
icon: icon,
onSelectedIcon: () {
String groupName = iconGroup.name;
if (groupName == _kRecentIconGroupName) {
groupName = getGroupName(index);
}
widget.onSelectedIcon(
IconsData(
iconGroup.name,
groupName,
icon.content,
icon.name,
null,
),
);
RecentIcons.putIcon(icon);
RecentIcons.putIcon(RecentIcon(icon, groupName));
},
);
},
@ -341,6 +352,16 @@ class _IconPickerState extends State<IconPicker> {
},
);
}
String getGroupName(int index) {
final recentIcons = RecentIcons.getIconsSync();
try {
return recentIcons[index].groupName;
} catch (e) {
Log.error('getGroupName with index: $index error', e);
return '';
}
}
}
class _IconNoBackground extends StatelessWidget {
@ -392,12 +413,20 @@ class _Icon extends StatefulWidget {
class _IconState extends State<_Icon> {
final PopoverController _popoverController = PopoverController();
@override
void dispose() {
super.dispose();
_popoverController.close();
}
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
direction: PopoverDirection.bottomWithCenterAligned,
controller: _popoverController,
offset: const Offset(0, 6),
mutex: widget.mutex,
clickHandler: PopoverClickHandler.gestureDetector,
child: _IconNoBackground(
icon: widget.icon,
onSelectedIcon: () => _popoverController.show(),

View file

@ -22,13 +22,10 @@ class RecentIcons {
await _put(FlowyIconType.emoji, id);
}
static Future<void> putIcon(Icon icon) async {
static Future<void> putIcon(RecentIcon icon) async {
await _put(
FlowyIconType.icon,
jsonEncode(
Icon(name: icon.name, keywords: icon.keywords, content: icon.content)
.toJson(),
),
jsonEncode(icon.toJson()),
);
}
@ -37,12 +34,22 @@ class RecentIcons {
return _dataMap[FlowyIconType.emoji.name] ?? [];
}
static Future<List<Icon>> getIcons() async {
static Future<List<RecentIcon>> getIcons() async {
await _load();
return getIconsSync();
}
static List<RecentIcon> getIconsSync() {
final iconList = _dataMap[FlowyIconType.icon.name] ?? [];
try {
return iconList
.map((e) => Icon.fromJson(jsonDecode(e) as Map<String, dynamic>))
.map(
(e) => RecentIcon.fromJson(jsonDecode(e) as Map<String, dynamic>),
)
/// skip the data that is already stored locally but has an empty
/// groupName to accommodate the issue of destructive data modifications
.skipWhile((e) => e.groupName.isEmpty)
.toList();
} catch (e) {
Log.error('RecentIcons getIcons with :$iconList', e);

View file

@ -29,6 +29,7 @@ import 'package:appflowy/user/application/auth/auth_service.dart';
import 'package:appflowy/user/presentation/presentation.dart';
import 'package:appflowy/workspace/presentation/home/desktop_home_screen.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/feature_flags/mobile_feature_flag_screen.dart';
import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:flowy_infra/time/duration.dart';
import 'package:flutter/foundation.dart';
@ -288,10 +289,15 @@ GoRoute _mobileEmojiPickerPageRoute() {
final selectedType = state
.uri.queryParameters[MobileEmojiPickerScreen.iconSelectedType]
?.toPickerTabType();
final tabs = selectTabs
.split('-')
.map((e) => PickerTabType.values.byName(e))
.toList();
List<PickerTabType> tabs = [];
try {
tabs = selectTabs
.split('-')
.map((e) => PickerTabType.values.byName(e))
.toList();
} on ArgumentError catch (e) {
Log.error('convert selectTabs to pickerTab error', e);
}
return MaterialExtendedPage(
child: tabs.isEmpty
? MobileEmojiPickerScreen(title: title, selectedType: selectedType)

View file

@ -173,21 +173,22 @@ class _SidebarSpaceHeaderState extends State<SidebarSpaceHeader> {
await _showRenameDialog();
break;
case SpaceMoreActionType.changeIcon:
final result = data as EmojiIconData;
if (data.type == FlowyIconType.icon) {
try {
final iconsData = IconsData.fromJson(jsonDecode(result.emoji));
context.read<SpaceBloc>().add(
SpaceEvent.changeIcon(
icon: '${iconsData.groupName}/${iconsData.iconName}',
iconColor: iconsData.color,
),
);
} on FormatException catch (e) {
context
.read<SpaceBloc>()
.add(const SpaceEvent.changeIcon(icon: ''));
Log.warn('SidebarSpaceHeader changeIcon error:$e');
if (data is SelectedEmojiIconResult) {
if (data.type == FlowyIconType.icon) {
try {
final iconsData = IconsData.fromJson(jsonDecode(data.emoji));
context.read<SpaceBloc>().add(
SpaceEvent.changeIcon(
icon: '${iconsData.groupName}/${iconsData.iconName}',
iconColor: iconsData.color,
),
);
} on FormatException catch (e) {
context
.read<SpaceBloc>()
.add(const SpaceEvent.changeIcon(icon: ''));
Log.warn('SidebarSpaceHeader changeIcon error:$e');
}
}
}
break;

View file

@ -46,16 +46,17 @@ void main() {
});
test('putIcons', () async {
List<Icon> icons = await RecentIcons.getIcons();
List<RecentIcon> icons = await RecentIcons.getIcons();
assert(icons.isEmpty);
await loadIconGroups();
final groups = kIconGroups!;
final List<Icon> localIcons = [];
final List<RecentIcon> localIcons = [];
for (final e in groups) {
localIcons.addAll(e.icons);
localIcons.addAll(e.icons.map((e) => RecentIcon(e, e.name)).toList());
}
bool equalIcon(Icon a, Icon b) =>
bool equalIcon(RecentIcon a, RecentIcon b) =>
a.groupName == b.groupName &&
a.name == b.name &&
a.keywords.equals(b.keywords) &&
a.content == b.content;