mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2025-04-24 06:37:14 -04:00
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:
parent
1d1647d58d
commit
776f70a0c7
20 changed files with 344 additions and 96 deletions
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
|
|
|
@ -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(),
|
||||
);
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
|
@ -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(),
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue