diff --git a/frontend/appflowy_flutter/integration_test/desktop/database/database_row_page_test.dart b/frontend/appflowy_flutter/integration_test/desktop/database/database_row_page_test.dart index 5dc1f612da53b..8874cefb1c640 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/database/database_row_page_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/database/database_row_page_test.dart @@ -22,8 +22,15 @@ import '../../shared/emoji.dart'; import '../../shared/util.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - RecentIcons.enable = false; + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); + group('grid row detail page:', () { testWidgets('opens', (tester) async { await tester.initializeAppFlowy(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart index bd4e17124a122..c9f844f374d51 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/document/document_with_cover_image_test.dart @@ -13,8 +13,15 @@ import '../../shared/emoji.dart'; import '../../shared/util.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - RecentIcons.enable = false; + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); + group('cover image:', () { testWidgets('document cover tests', (tester) async { await tester.initializeAppFlowy(); diff --git a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart index 52e443cbf051d..93b67483fa07c 100644 --- a/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart +++ b/frontend/appflowy_flutter/integration_test/desktop/sidebar/sidebar_icon_test.dart @@ -11,10 +11,17 @@ import '../../shared/common_operations.dart'; import '../../shared/expectation.dart'; void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - RecentIcons.enable = false; final emoji = EmojiIconData.emoji('😁'); + setUpAll(() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + RecentIcons.enable = false; + }); + + tearDownAll(() { + RecentIcons.enable = true; + }); + Future loadIcon() async { await loadIconGroups(); final groups = kIconGroups!; diff --git a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart index c4cc33f4e3938..dfc170c595892 100644 --- a/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart +++ b/frontend/appflowy_flutter/lib/plugins/base/emoji/emoji_picker.dart @@ -11,6 +11,7 @@ import 'package:flutter_emoji_mart/flutter_emoji_mart.dart'; // use a global value to store the selected emoji to prevent reloading every time. EmojiData? kCachedEmojiData; +const _kRecentEmojiCategoryId = 'Recent'; class FlowyEmojiPicker extends StatefulWidget { const FlowyEmojiPicker({ @@ -30,26 +31,6 @@ class _FlowyEmojiPickerState extends State { late EmojiData emojiData; bool loaded = false; - void loadEmojis(EmojiData data) { - RecentIcons.getEmojiIds().then((v) { - if (v.isEmpty) { - emojiData = data; - setState(() => loaded = true); - return; - } - final categories = List.of(data.categories); - categories.insert( - 0, - Category( - id: 'Recent', - emojiIds: v.sublist(0, min(widget.emojiPerLine, v.length)), - ), - ); - emojiData = EmojiData(categories: categories, emojis: data.emojis); - setState(() => loaded = true); - }); - } - @override void initState() { super.initState(); @@ -132,4 +113,24 @@ class _FlowyEmojiPickerState extends State { }, ); } + + void loadEmojis(EmojiData data) { + RecentIcons.getEmojiIds().then((v) { + if (v.isEmpty) { + emojiData = data; + setState(() => loaded = true); + return; + } + final categories = List.of(data.categories); + categories.insert( + 0, + Category( + id: _kRecentEmojiCategoryId, + emojiIds: v.sublist(0, min(widget.emojiPerLine, v.length)), + ), + ); + emojiData = EmojiData(categories: categories, emojis: data.emojis); + setState(() => loaded = true); + }); + } } diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart index 4b5a3cca9856c..75f5633ee5378 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/icon_picker.dart @@ -24,6 +24,7 @@ import 'icon_color_picker.dart'; // cache the icon groups to avoid loading them multiple times List? kIconGroups; +const _kRecentIconGroupName = 'Recent'; extension IconGroupFilter on List { String? findSvgContent(String key) { @@ -96,13 +97,13 @@ class _FlowyIconPickerState extends State { final ValueNotifier keyword = ValueNotifier(''); final debounce = Debounce(duration: const Duration(milliseconds: 150)); - Future loadIcons() async { + Future loadIcons() async { final localIcons = await loadIconGroups(); final recentIcons = await RecentIcons.getIcons(); if (recentIcons.isNotEmpty) { iconGroups.add( IconGroup( - name: 'Recent', + name: _kRecentIconGroupName, icons: recentIcons.sublist( 0, min(recentIcons.length, widget.iconPerLine), diff --git a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/recent_icons.dart b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/recent_icons.dart index 0db362c49d459..15067e8e60696 100644 --- a/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/recent_icons.dart +++ b/frontend/appflowy_flutter/lib/shared/icon_emoji_picker/recent_icons.dart @@ -13,46 +13,11 @@ class RecentIcons { static final Map> _dataMap = {}; static bool _loaded = false; static const maxLength = 20; + + ///prevent the Recent Icon feature from affecting the unit tests of the Icon Selector. @visibleForTesting static bool enable = true; - static Future _save() async { - await getIt().set( - KVKeys.kRecentIcons, - jsonEncode(_dataMap), - ); - } - - static Future _load() async { - if (_loaded || !enable) return; - final v = await getIt().get(KVKeys.kRecentIcons); - try { - final data = jsonDecode(v ?? '') as Map; - _dataMap.clear(); - _dataMap.addAll( - data.map( - (k, v) => MapEntry>(k, List.from(v)), - ), - ); - } on FormatException catch (e) { - Log.error('RecentIcons load failed with :$v', e); - } on TypeError catch (e) { - Log.error('RecentIcons load failed with :$v', e); - } - _loaded = true; - } - - static Future _put(FlowyIconType key, String value) async { - await _load(); - if (!enable) return; - final list = _dataMap[key.name] ?? []; - list.remove(value); - list.insert(0, value); - if (list.length > maxLength) list.removeLast(); - _dataMap[key.name] = list; - await _save(); - } - static Future putEmoji(String id) async { await _put(FlowyIconType.emoji, id); } @@ -86,4 +51,47 @@ class RecentIcons { } return []; } + + static Future _save() async { + await getIt().set( + KVKeys.kRecentIcons, + jsonEncode(_dataMap), + ); + } + + static Future _load() async { + if (_loaded || !enable) { + return; + } + final storage = getIt(); + final value = await storage.get(KVKeys.kRecentIcons); + if (value == null || value.isEmpty) { + _loaded = true; + return; + } + try { + final data = jsonDecode(value) as Map; + _dataMap + ..clear() + ..addAll( + Map>.from( + data.map((k, v) => MapEntry(k, List.from(v))), + ), + ); + } catch (e) { + Log.error('RecentIcons load failed with: $value', e); + } + _loaded = true; + } + + static Future _put(FlowyIconType key, String value) async { + await _load(); + if (!enable) return; + final list = _dataMap[key.name] ?? []; + list.remove(value); + list.insert(0, value); + if (list.length > maxLength) list.removeLast(); + _dataMap[key.name] = list; + await _save(); + } } diff --git a/frontend/appflowy_flutter/test/unit_test/util/recent_icons.dart b/frontend/appflowy_flutter/test/unit_test/util/recent_icons.dart deleted file mode 100644 index a378ce05e53db..0000000000000 --- a/frontend/appflowy_flutter/test/unit_test/util/recent_icons.dart +++ /dev/null @@ -1,93 +0,0 @@ -import 'package:appflowy/core/config/kv.dart'; -import 'package:appflowy/shared/icon_emoji_picker/icon.dart'; -import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; -import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; -import 'package:appflowy/startup/startup.dart'; -import 'package:appflowy_backend/log.dart'; -import 'package:collection/collection.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -void main() { - group('Testing for RecentIcons', () { - TestWidgetsFlutterBinding.ensureInitialized(); - SharedPreferences.setMockInitialValues({}); - getIt.registerFactory(() => DartKeyValue()); - Log.shared.disableLog = true; - - test('putEmoji', () async { - List emojiIds = await RecentIcons.getEmojiIds(); - assert(emojiIds.isEmpty); - - await RecentIcons.putEmoji('1'); - emojiIds = await RecentIcons.getEmojiIds(); - assert(emojiIds.equals(['1'])); - - await RecentIcons.putEmoji('2'); - assert(emojiIds.equals(['2', '1'])); - - await RecentIcons.putEmoji('1'); - emojiIds = await RecentIcons.getEmojiIds(); - assert(emojiIds.equals(['1', '2'])); - - for (var i = 0; i < RecentIcons.maxLength; ++i) { - await RecentIcons.putEmoji('${i + 100}'); - } - emojiIds = await RecentIcons.getEmojiIds(); - assert(emojiIds.length == RecentIcons.maxLength); - assert( - emojiIds.equals( - List.generate(RecentIcons.maxLength, (i) => '${i + 100}') - .reversed - .toList(), - ), - ); - }); - - test('putIcons', () async { - List icons = await RecentIcons.getIcons(); - assert(icons.isEmpty); - await loadIconGroups(); - final groups = kIconGroups!; - final List localIcons = []; - for (final e in groups) { - localIcons.addAll(e.icons); - } - - bool equalIcon(Icon a, Icon b) => - a.name == b.name && - a.keywords.equals(b.keywords) && - a.content == b.content; - - await RecentIcons.putIcon(localIcons.first); - icons = await RecentIcons.getIcons(); - assert(icons.length == 1); - assert(equalIcon(icons.first, localIcons.first)); - - await RecentIcons.putIcon(localIcons[1]); - icons = await RecentIcons.getIcons(); - assert(icons.length == 2); - assert(equalIcon(icons[0], localIcons[1])); - assert(equalIcon(icons[1], localIcons[0])); - - await RecentIcons.putIcon(localIcons.first); - icons = await RecentIcons.getIcons(); - assert(icons.length == 2); - assert(equalIcon(icons[1], localIcons[1])); - assert(equalIcon(icons[0], localIcons[0])); - - for (var i = 0; i < RecentIcons.maxLength; ++i) { - await RecentIcons.putIcon(localIcons[10 + i]); - } - - icons = await RecentIcons.getIcons(); - assert(icons.length == RecentIcons.maxLength); - - for (var i = 0; i < RecentIcons.maxLength; ++i) { - assert( - equalIcon(icons[RecentIcons.maxLength - i - 1], localIcons[10 + i]), - ); - } - }); - }); -} diff --git a/frontend/appflowy_flutter/test/unit_test/util/recent_icons_test.dart b/frontend/appflowy_flutter/test/unit_test/util/recent_icons_test.dart new file mode 100644 index 0000000000000..9212467d84fcb --- /dev/null +++ b/frontend/appflowy_flutter/test/unit_test/util/recent_icons_test.dart @@ -0,0 +1,93 @@ +import 'package:appflowy/core/config/kv.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon.dart'; +import 'package:appflowy/shared/icon_emoji_picker/icon_picker.dart'; +import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart'; +import 'package:appflowy/startup/startup.dart'; +import 'package:appflowy_backend/log.dart'; +import 'package:collection/collection.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +void main() { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues({}); + getIt.registerFactory(() => DartKeyValue()); + Log.shared.disableLog = true; + }); + + test('putEmoji', () async { + List emojiIds = await RecentIcons.getEmojiIds(); + assert(emojiIds.isEmpty); + + await RecentIcons.putEmoji('1'); + emojiIds = await RecentIcons.getEmojiIds(); + assert(emojiIds.equals(['1'])); + + await RecentIcons.putEmoji('2'); + assert(emojiIds.equals(['2', '1'])); + + await RecentIcons.putEmoji('1'); + emojiIds = await RecentIcons.getEmojiIds(); + assert(emojiIds.equals(['1', '2'])); + + for (var i = 0; i < RecentIcons.maxLength; ++i) { + await RecentIcons.putEmoji('${i + 100}'); + } + emojiIds = await RecentIcons.getEmojiIds(); + assert(emojiIds.length == RecentIcons.maxLength); + assert( + emojiIds.equals( + List.generate(RecentIcons.maxLength, (i) => '${i + 100}') + .reversed + .toList(), + ), + ); + }); + + test('putIcons', () async { + List icons = await RecentIcons.getIcons(); + assert(icons.isEmpty); + await loadIconGroups(); + final groups = kIconGroups!; + final List localIcons = []; + for (final e in groups) { + localIcons.addAll(e.icons); + } + + bool equalIcon(Icon a, Icon b) => + a.name == b.name && + a.keywords.equals(b.keywords) && + a.content == b.content; + + await RecentIcons.putIcon(localIcons.first); + icons = await RecentIcons.getIcons(); + assert(icons.length == 1); + assert(equalIcon(icons.first, localIcons.first)); + + await RecentIcons.putIcon(localIcons[1]); + icons = await RecentIcons.getIcons(); + assert(icons.length == 2); + assert(equalIcon(icons[0], localIcons[1])); + assert(equalIcon(icons[1], localIcons[0])); + + await RecentIcons.putIcon(localIcons.first); + icons = await RecentIcons.getIcons(); + assert(icons.length == 2); + assert(equalIcon(icons[1], localIcons[1])); + assert(equalIcon(icons[0], localIcons[0])); + + for (var i = 0; i < RecentIcons.maxLength; ++i) { + await RecentIcons.putIcon(localIcons[10 + i]); + } + + icons = await RecentIcons.getIcons(); + assert(icons.length == RecentIcons.maxLength); + + for (var i = 0; i < RecentIcons.maxLength; ++i) { + assert( + equalIcon(icons[RecentIcons.maxLength - i - 1], localIcons[10 + i]), + ); + } + }); +}