diff --git a/frontend/app_flowy/assets/images/editor/insert_emoticon.svg b/frontend/app_flowy/assets/images/editor/insert_emoticon.svg new file mode 100644 index 0000000000..8bb960e52d --- /dev/null +++ b/frontend/app_flowy/assets/images/editor/insert_emoticon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/app_flowy/assets/images/editor/insert_emoticon_2.svg b/frontend/app_flowy/assets/images/editor/insert_emoticon_2.svg new file mode 100644 index 0000000000..66bbf7a626 --- /dev/null +++ b/frontend/app_flowy/assets/images/editor/insert_emoticon_2.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/tool_bar.dart b/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/tool_bar.dart index 54d6c12ea1..faf49e8027 100644 --- a/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/tool_bar.dart +++ b/frontend/app_flowy/lib/workspace/presentation/stack_page/doc/widget/toolbar/tool_bar.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:app_flowy/workspace/presentation/stack_page/doc/widget/toolbar/history_button.dart'; +import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter/material.dart'; @@ -151,6 +152,11 @@ class EditorToolbar extends StatelessWidget implements PreferredSizeWidget { controller: controller, iconSize: toolbarIconSize, ), + FlowyEmojiStyleButton( + normalIcon: 'editor/insert_emoticon', + controller: controller, + tooltipText: "Emoji Picker", + ), ], ); } diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/emoji_picker.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/emoji_picker.dart new file mode 100644 index 0000000000..f2cc8bb440 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/emoji_picker.dart @@ -0,0 +1,5 @@ +export 'src/config.dart'; +export 'src/models/emoji_model.dart'; +export 'src/emoji_picker.dart'; +export 'src/emoji_picker_builder.dart'; +export 'src/emoji_button.dart'; diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/config.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/config.dart new file mode 100644 index 0000000000..b7e71e87b9 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/config.dart @@ -0,0 +1,164 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; + +import 'models/category_models.dart'; +import 'emoji_picker.dart'; + +/// Config for customizations +class Config { + /// Constructor + const Config( + {this.columns = 7, + this.emojiSizeMax = 32.0, + this.verticalSpacing = 0, + this.horizontalSpacing = 0, + this.initCategory = Category.RECENT, + this.bgColor = const Color(0xFFEBEFF2), + this.indicatorColor = Colors.blue, + this.iconColor = Colors.grey, + this.iconColorSelected = Colors.blue, + this.progressIndicatorColor = Colors.blue, + this.backspaceColor = Colors.blue, + this.showRecentsTab = true, + this.recentsLimit = 28, + this.noRecentsText = 'No Recents', + this.noRecentsStyle = const TextStyle(fontSize: 20, color: Colors.black26), + this.tabIndicatorAnimDuration = kTabScrollDuration, + this.categoryIcons = const CategoryIcons(), + this.buttonMode = ButtonMode.MATERIAL}); + + /// Number of emojis per row + final int columns; + + /// Width and height the emoji will be maximal displayed + /// Can be smaller due to screen size and amount of columns + final double emojiSizeMax; + + /// Verical spacing between emojis + final double verticalSpacing; + + /// Horizontal spacing between emojis + final double horizontalSpacing; + + /// The initial [Category] that will be selected + /// This [Category] will have its button in the bottombar darkened + final Category initCategory; + + /// The background color of the Widget + final Color bgColor; + + /// The color of the category indicator + final Color indicatorColor; + + /// The color of the category icons + final Color iconColor; + + /// The color of the category icon when selected + final Color iconColorSelected; + + /// The color of the loading indicator during initalization + final Color progressIndicatorColor; + + /// The color of the backspace icon button + final Color backspaceColor; + + /// Show extra tab with recently used emoji + final bool showRecentsTab; + + /// Limit of recently used emoji that will be saved + final int recentsLimit; + + /// The text to be displayed if no recent emojis to display + final String noRecentsText; + + /// The text style for [noRecentsText] + final TextStyle noRecentsStyle; + + /// Duration of tab indicator to animate to next category + final Duration tabIndicatorAnimDuration; + + /// Determines the icon to display for each [Category] + final CategoryIcons categoryIcons; + + /// Change between Material and Cupertino button style + final ButtonMode buttonMode; + + /// Get Emoji size based on properties and screen width + double getEmojiSize(double width) { + final maxSize = width / columns; + return min(maxSize, emojiSizeMax); + } + + /// Returns the icon for the category + IconData getIconForCategory(Category category) { + switch (category) { + case Category.RECENT: + return categoryIcons.recentIcon; + case Category.SMILEYS: + return categoryIcons.smileyIcon; + case Category.ANIMALS: + return categoryIcons.animalIcon; + case Category.FOODS: + return categoryIcons.foodIcon; + case Category.TRAVEL: + return categoryIcons.travelIcon; + case Category.ACTIVITIES: + return categoryIcons.activityIcon; + case Category.OBJECTS: + return categoryIcons.objectIcon; + case Category.SYMBOLS: + return categoryIcons.symbolIcon; + case Category.FLAGS: + return categoryIcons.flagIcon; + case Category.SEARCH: + return categoryIcons.searchIcon; + default: + throw Exception('Unsupported Category'); + } + } + + @override + bool operator ==(other) { + return (other is Config) && + other.columns == columns && + other.emojiSizeMax == emojiSizeMax && + other.verticalSpacing == verticalSpacing && + other.horizontalSpacing == horizontalSpacing && + other.initCategory == initCategory && + other.bgColor == bgColor && + other.indicatorColor == indicatorColor && + other.iconColor == iconColor && + other.iconColorSelected == iconColorSelected && + other.progressIndicatorColor == progressIndicatorColor && + other.backspaceColor == backspaceColor && + other.showRecentsTab == showRecentsTab && + other.recentsLimit == recentsLimit && + other.noRecentsText == noRecentsText && + other.noRecentsStyle == noRecentsStyle && + other.tabIndicatorAnimDuration == tabIndicatorAnimDuration && + other.categoryIcons == categoryIcons && + other.buttonMode == buttonMode; + } + + @override + int get hashCode => + columns.hashCode ^ + emojiSizeMax.hashCode ^ + verticalSpacing.hashCode ^ + horizontalSpacing.hashCode ^ + initCategory.hashCode ^ + bgColor.hashCode ^ + indicatorColor.hashCode ^ + iconColor.hashCode ^ + iconColorSelected.hashCode ^ + progressIndicatorColor.hashCode ^ + backspaceColor.hashCode ^ + showRecentsTab.hashCode ^ + recentsLimit.hashCode ^ + noRecentsText.hashCode ^ + noRecentsStyle.hashCode ^ + tabIndicatorAnimDuration.hashCode ^ + categoryIcons.hashCode ^ + buttonMode.hashCode; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/default_emoji_picker_view.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/default_emoji_picker_view.dart new file mode 100644 index 0000000000..afdfa5e6eb --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/default_emoji_picker_view.dart @@ -0,0 +1,276 @@ +import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'models/category_models.dart'; +import 'config.dart'; +import 'models/emoji_model.dart'; +import 'emoji_picker.dart'; +import 'emoji_picker_builder.dart'; +import 'emoji_view_state.dart'; + +class DefaultEmojiPickerView extends EmojiPickerBuilder { + const DefaultEmojiPickerView(Config config, EmojiViewState state, {Key? key}) : super(config, state, key: key); + + @override + _DefaultEmojiPickerViewState createState() => _DefaultEmojiPickerViewState(); +} + +class _DefaultEmojiPickerViewState extends State with TickerProviderStateMixin { + PageController? _pageController; + TabController? _tabController; + final TextEditingController _emojiController = TextEditingController(); + final FocusNode _emojiFocusNode = FocusNode(); + final CategoryEmoji _categoryEmoji = CategoryEmoji(Category.SEARCH, List.empty(growable: true)); + CategoryEmoji searchEmojiList = CategoryEmoji(Category.SEARCH, []); + + @override + void initState() { + var initCategory = + widget.state.categoryEmoji.indexWhere((element) => element.category == widget.config.initCategory); + if (initCategory == -1) { + initCategory = 0; + } + _tabController = TabController(initialIndex: initCategory, length: widget.state.categoryEmoji.length, vsync: this); + _pageController = PageController(initialPage: initCategory); + _emojiFocusNode.requestFocus(); + + _emojiController.addListener(() { + String query = _emojiController.text.toLowerCase(); + if (query.isEmpty) { + searchEmojiList.emoji.clear(); + _pageController!.jumpToPage( + _tabController!.index, + ); + } else { + searchEmojiList.emoji.clear(); + for (var element in widget.state.categoryEmoji) { + searchEmojiList.emoji.addAll( + element.emoji.where((item) { + return item.name.toLowerCase().contains(query); + }).toList(), + ); + } + } + setState(() {}); + }); + super.initState(); + } + + @override + void dispose() { + _emojiController.dispose(); + _emojiFocusNode.dispose(); + super.dispose(); + } + + Widget _buildBackspaceButton() { + if (widget.state.onBackspacePressed != null) { + return Material( + type: MaterialType.transparency, + child: IconButton( + padding: const EdgeInsets.only(bottom: 2), + icon: Icon( + Icons.backspace, + color: widget.config.backspaceColor, + ), + onPressed: () { + widget.state.onBackspacePressed!(); + }), + ); + } + return Container(); + } + + bool isEmojiSearching() { + bool result = searchEmojiList.emoji.isNotEmpty || _emojiController.text.isNotEmpty; + + return result; + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + final emojiSize = widget.config.getEmojiSize(constraints.maxWidth); + + return Container( + color: widget.config.bgColor, + padding: const EdgeInsets.all(5.0), + child: Column( + children: [ + SizedBox( + height: 25.0, + child: TextField( + controller: _emojiController, + focusNode: _emojiFocusNode, + autofocus: true, + style: const TextStyle(fontSize: 14.0), + cursorWidth: 1.0, + cursorColor: Colors.black, + decoration: InputDecoration( + contentPadding: const EdgeInsets.symmetric(horizontal: 5.0), + hintText: "Search emoji", + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: const BorderSide(), + gapPadding: 0.0, + ), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(4.0), + borderSide: const BorderSide(), + gapPadding: 0.0, + ), + filled: true, + fillColor: Colors.white, + hoverColor: Colors.white, + ), + ), + ), + Row( + children: [ + Expanded( + child: TabBar( + labelColor: widget.config.iconColorSelected, + unselectedLabelColor: widget.config.iconColor, + controller: isEmojiSearching() ? TabController(length: 1, vsync: this) : _tabController, + labelPadding: EdgeInsets.zero, + indicatorColor: widget.config.indicatorColor, + padding: const EdgeInsets.symmetric(vertical: 5.0), + indicator: BoxDecoration( + border: Border.all(color: Colors.transparent), + borderRadius: BorderRadius.circular(4.0), + color: Colors.grey.withOpacity(0.5), + ), + onTap: (index) { + _pageController!.animateToPage( + index, + duration: widget.config.tabIndicatorAnimDuration, + curve: Curves.ease, + ); + }, + tabs: isEmojiSearching() + ? [_buildCategory(Category.SEARCH, emojiSize)] + : widget.state.categoryEmoji + .asMap() + .entries + .map((item) => _buildCategory(item.value.category, emojiSize)) + .toList(), + ), + ), + _buildBackspaceButton(), + ], + ), + Flexible( + child: PageView.builder( + itemCount: searchEmojiList.emoji.isNotEmpty ? 1 : widget.state.categoryEmoji.length, + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + // onPageChanged: (index) { + // _tabController!.animateTo( + // index, + // duration: widget.config.tabIndicatorAnimDuration, + // ); + // }, + itemBuilder: (context, index) { + CategoryEmoji catEmoji = isEmojiSearching() ? searchEmojiList : widget.state.categoryEmoji[index]; + return _buildPage(emojiSize, catEmoji); + }, + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildCategory(Category category, double categorySize) { + return Tab( + height: categorySize, + child: Icon( + widget.config.getIconForCategory(category), + size: categorySize / 1.3, + ), + ); + } + + Widget _buildButtonWidget({required VoidCallback onPressed, required Widget child}) { + if (widget.config.buttonMode == ButtonMode.MATERIAL) { + return TextButton( + onPressed: onPressed, + child: child, + style: ButtonStyle(padding: MaterialStateProperty.all(EdgeInsets.zero)), + ); + } + return CupertinoButton(padding: EdgeInsets.zero, onPressed: onPressed, child: child); + } + + Widget _buildPage(double emojiSize, CategoryEmoji categoryEmoji) { + // Display notice if recent has no entries yet + final scrollController = ScrollController(); + + if (categoryEmoji.category == Category.RECENT && categoryEmoji.emoji.isEmpty) { + return _buildNoRecent(); + } else if (categoryEmoji.category == Category.SEARCH && categoryEmoji.emoji.isEmpty) { + return const Center(child: Text("No Emoji Found")); + } + // Build page normally + return ScrollbarListStack( + axis: Axis.vertical, + controller: scrollController, + barSize: 4.0, + scrollbarPadding: const EdgeInsets.symmetric(horizontal: 5.0), + handleColor: const Color(0xffDFE0E0), + trackColor: const Color(0xffDFE0E0), + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false), + child: GridView.count( + scrollDirection: Axis.vertical, + physics: const ScrollPhysics(), + controller: scrollController, + shrinkWrap: true, + // primary: true, + padding: const EdgeInsets.all(0), + crossAxisCount: widget.config.columns, + mainAxisSpacing: widget.config.verticalSpacing, + crossAxisSpacing: widget.config.horizontalSpacing, + children: _categoryEmoji.emoji.isNotEmpty + ? _categoryEmoji.emoji.map((e) => _buildEmoji(emojiSize, categoryEmoji, e)).toList() + : categoryEmoji.emoji.map((item) => _buildEmoji(emojiSize, categoryEmoji, item)).toList(), + ), + ), + ); + } + + Widget _buildEmoji( + double emojiSize, + CategoryEmoji categoryEmoji, + Emoji emoji, + ) { + return _buildButtonWidget( + onPressed: () { + widget.state.onEmojiSelected(categoryEmoji.category, emoji); + }, + child: FittedBox( + fit: BoxFit.fill, + child: Text( + emoji.emoji, + textScaleFactor: 1.0, + style: TextStyle( + fontSize: emojiSize, + backgroundColor: Colors.transparent, + ), + ), + )); + } + + Widget _buildNoRecent() { + return Center( + child: Text( + widget.config.noRecentsText, + style: widget.config.noRecentsStyle, + textAlign: TextAlign.center, + )); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart new file mode 100644 index 0000000000..4fdc3bd14f --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_button.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_quill/flutter_quill.dart'; + +import 'package:app_flowy/workspace/presentation/stack_page/doc/widget/toolbar/toolbar_icon_button.dart'; +import 'package:app_flowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart'; + +class FlowyEmojiStyleButton extends StatefulWidget { + // final Attribute attribute; + final String normalIcon; + final double iconSize; + final QuillController controller; + final String tooltipText; + + const FlowyEmojiStyleButton({ + // required this.attribute, + required this.normalIcon, + required this.controller, + required this.tooltipText, + this.iconSize = defaultIconSize, + Key? key, + }) : super(key: key); + + @override + _EmojiStyleButtonState createState() => _EmojiStyleButtonState(); +} + +class _EmojiStyleButtonState extends State { + bool _isToggled = false; + // Style get _selectionStyle => widget.controller.getSelectionStyle(); + final GlobalKey emojiButtonKey = GlobalKey(); + OverlayEntry _entry = OverlayEntry(builder: (context) => Container()); + // final FocusNode _keyFocusNode = FocusNode(); + + @override + void initState() { + super.initState(); + // _isToggled = _getIsToggled(_selectionStyle.attributes); + // widget.controller.addListener(_didChangeEditingValue); + } + + @override + Widget build(BuildContext context) { + // debugPrint(MediaQuery.of(context).size.width.toString()); + // debugPrint(MediaQuery.of(context).size.height.toString()); + + return ToolbarIconButton( + key: emojiButtonKey, + onPressed: _toggleAttribute, + width: widget.iconSize * kIconButtonFactor, + isToggled: _isToggled, + iconName: widget.normalIcon, + tooltipText: widget.tooltipText, + ); + } + + // @override + // void didUpdateWidget(covariant FlowyEmojiStyleButton oldWidget) { + // super.didUpdateWidget(oldWidget); + // if (oldWidget.controller != widget.controller) { + // oldWidget.controller.removeListener(_didChangeEditingValue); + // widget.controller.addListener(_didChangeEditingValue); + // _isToggled = _getIsToggled(_selectionStyle.attributes); + // } + // } + + // @override + // void dispose() { + // widget.controller.removeListener(_didChangeEditingValue); + // super.dispose(); + // } + + // void _didChangeEditingValue() { + // setState(() => _isToggled = _getIsToggled(_selectionStyle.attributes)); + // } + + // bool _getIsToggled(Map attrs) { + // return _entry.mounted; + // } + + void _toggleAttribute() { + if (_entry.mounted) { + _entry.remove(); + setState(() => _isToggled = false); + } else { + RenderBox box = emojiButtonKey.currentContext?.findRenderObject() as RenderBox; + Offset position = box.localToGlobal(Offset.zero); + + // final window = await getWindowInfo(); + + _entry = OverlayEntry( + builder: (BuildContext context) => BuildEmojiPickerView( + controller: widget.controller, + offset: position, + ), + ); + + Overlay.of(context)!.insert(_entry); + setState(() => _isToggled = true); + } + + //TODO @gaganyadav80: INFO: throws error when using TextField with FlowyOverlay. + + // FlowyOverlay.of(context).insertWithRect( + // widget: BuildEmojiPickerView(controller: widget.controller), + // identifier: 'overlay_emoji_picker', + // anchorPosition: Offset(position.dx + 40, position.dy - 10), + // anchorSize: window.frame.size, + // anchorDirection: AnchorDirection.topLeft, + // style: FlowyOverlayStyle(blur: true), + // ); + } +} + +class BuildEmojiPickerView extends StatefulWidget { + const BuildEmojiPickerView({Key? key, required this.controller, this.offset}) : super(key: key); + + final QuillController controller; + final Offset? offset; + + @override + State createState() => _BuildEmojiPickerViewState(); +} + +class _BuildEmojiPickerViewState extends State { + @override + Widget build(BuildContext context) { + return Stack( + children: [ + Positioned( + //TODO @gaganyadav80: Not sure about the calculated position. + top: widget.offset!.dy - MediaQuery.of(context).size.height / 2.83 - 30, + left: widget.offset!.dx - MediaQuery.of(context).size.width / 3.92 + 40, + child: Material( + borderRadius: BorderRadius.circular(8.0), + child: SizedBox( + //TODO @gaganyadav80: FIXIT: Gets too large when fullscreen. + height: MediaQuery.of(context).size.height / 2.83 + 20, + width: MediaQuery.of(context).size.width / 3.92, + child: ClipRRect( + borderRadius: BorderRadius.circular(8.0), + child: EmojiPicker( + onEmojiSelected: (category, emoji) => insertEmoji(emoji), + config: const Config( + columns: 8, + emojiSizeMax: 28, + bgColor: Color(0xffF2F2F2), + iconColor: Colors.grey, + iconColorSelected: Color(0xff333333), + indicatorColor: Color(0xff333333), + progressIndicatorColor: Color(0xff333333), + buttonMode: ButtonMode.CUPERTINO, + initCategory: Category.RECENT, + ), + ), + ), + ), + ), + ), + ], + ); + } + + void insertEmoji(Emoji emoji) { + final baseOffset = widget.controller.selection.baseOffset; + final extentOffset = widget.controller.selection.extentOffset; + final replaceLen = extentOffset - baseOffset; + final selection = widget.controller.selection.copyWith( + baseOffset: baseOffset + emoji.emoji.length, + extentOffset: baseOffset + emoji.emoji.length, + ); + + widget.controller.replaceText(baseOffset, replaceLen, emoji.emoji, selection); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart new file mode 100644 index 0000000000..8bb45b51cd --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_lists.dart @@ -0,0 +1,3223 @@ +// Copyright information +// File originally from https://github.com/JeffG05/emoji_picker + +// import 'emoji.dart'; + +// final List> temp = [smileys, animals, foods, activities, travel, objects, symbols, flags]; + +// final List emojiSearchList = temp +// .map((element) { +// return element.entries.map((entry) => Emoji(entry.key, entry.value)).toList(); +// }) +// .toList() +// .first; + +/// Map of all possible emojis along with their names in [Category.SMILEYS] +final Map smileys = Map.fromIterables([ + 'Grinning Face', + 'Grinning Face With Big Eyes', + 'Grinning Face With Smiling Eyes', + 'Beaming Face With Smiling Eyes', + 'Grinning Squinting Face', + 'Grinning Face With Sweat', + 'Rolling on the Floor Laughing', + 'Face With Tears of Joy', + 'Slightly Smiling Face', + 'Upside-Down Face', + 'Winking Face', + 'Smiling Face With Smiling Eyes', + 'Smiling Face With Halo', + 'Smiling Face With Hearts', + 'Smiling Face With Heart-Eyes', + 'Star-Struck', + 'Face Blowing a Kiss', + 'Kissing Face', + 'Smiling Face', + 'Kissing Face With Closed Eyes', + 'Kissing Face With Smiling Eyes', + 'Face Savoring Food', + 'Face With Tongue', + 'Winking Face With Tongue', + 'Zany Face', + 'Squinting Face With Tongue', + 'Money-Mouth Face', + 'Hugging Face', + 'Face With Hand Over Mouth', + 'Shushing Face', + 'Thinking Face', + 'Zipper-Mouth Face', + 'Face With Raised Eyebrow', + 'Neutral Face', + 'Expressionless Face', + 'Face Without Mouth', + 'Smirking Face', + 'Unamused Face', + 'Face With Rolling Eyes', + 'Grimacing Face', + 'Lying Face', + 'Relieved Face', + 'Pensive Face', + 'Sleepy Face', + 'Drooling Face', + 'Sleeping Face', + 'Face With Medical Mask', + 'Face With Thermometer', + 'Face With Head-Bandage', + 'Nauseated Face', + 'Face Vomiting', + 'Sneezing Face', + 'Hot Face', + 'Cold Face', + 'Woozy Face', + 'Dizzy Face', + 'Exploding Head', + 'Cowboy Hat Face', + 'Partying Face', + 'Smiling Face With Sunglasses', + 'Nerd Face', + 'Face With Monocle', + 'Confused Face', + 'Worried Face', + 'Slightly Frowning Face', + 'Frowning Face', + 'Face With Open Mouth', + 'Hushed Face', + 'Astonished Face', + 'Flushed Face', + 'Pleading Face', + 'Frowning Face With Open Mouth', + 'Anguished Face', + 'Fearful Face', + 'Anxious Face With Sweat', + 'Sad but Relieved Face', + 'Crying Face', + 'Loudly Crying Face', + 'Face Screaming in Fear', + 'Confounded Face', + 'Persevering Face', + 'Disappointed Face', + 'Downcast Face With Sweat', + 'Weary Face', + 'Tired Face', + 'Face With Steam From Nose', + 'Pouting Face', + 'Angry Face', + 'Face With Symbols on Mouth', + 'Smiling Face With Horns', + 'Angry Face With Horns', + 'Skull', + 'Skull and Crossbones', + 'Pile of Poo', + 'Clown Face', + 'Ogre', + 'Goblin', + 'Ghost', + 'Alien', + 'Alien Monster', + 'Robot Face', + 'Grinning Cat Face', + 'Grinning Cat Face With Smiling Eyes', + 'Cat Face With Tears of Joy', + 'Smiling Cat Face With Heart-Eyes', + 'Cat Face With Wry Smile', + 'Kissing Cat Face', + 'Weary Cat Face', + 'Crying Cat Face', + 'Pouting Cat Face', + 'Kiss Mark', + 'Waving Hand', + 'Raised Back of Hand', + 'Hand With Fingers Splayed', + 'Raised Hand', + 'Vulcan Salute', + 'OK Hand', + 'Victory Hand', + 'Crossed Fingers', + 'Love-You Gesture', + 'Sign of the Horns', + 'Call Me Hand', + 'Backhand Index Pointing Left', + 'Backhand Index Pointing Right', + 'Backhand Index Pointing Up', + 'Middle Finger', + 'Backhand Index Pointing Down', + 'Index Pointing Up', + 'Thumbs Up', + 'Thumbs Down', + 'Raised Fist', + 'Oncoming Fist', + 'Left-Facing Fist', + 'Right-Facing Fist', + 'Clapping Hands', + 'Raising Hands', + 'Open Hands', + 'Palms Up Together', + 'Handshake', + 'Folded Hands', + 'Writing Hand', + 'Nail Polish', + 'Selfie', + 'Flexed Biceps', + 'Leg', + 'Foot', + 'Ear', + 'Nose', + 'Brain', + 'Tooth', + 'Bone', + 'Eyes', + 'Eye', + 'Tongue', + 'Mouth', + 'Baby', + 'Child', + 'Boy', + 'Girl', + 'Person', + 'Man', + 'Man: Beard', + 'Man: Blond Hair', + 'Man: Red Hair', + 'Man: Curly Hair', + 'Man: White Hair', + 'Man: Bald', + 'Woman', + 'Woman: Blond Hair', + 'Woman: Red Hair', + 'Woman: Curly Hair', + 'Woman: White Hair', + 'Woman: Bald', + 'Older Person', + 'Old Man', + 'Old Woman', + 'Man Frowning', + 'Woman Frowning', + 'Man Pouting', + 'Woman Pouting', + 'Man Gesturing No', + 'Woman Gesturing No', + 'Man Gesturing OK', + 'Woman Gesturing OK', + 'Man Tipping Hand', + 'Woman Tipping Hand', + 'Man Raising Hand', + 'Woman Raising Hand', + 'Man Bowing', + 'Woman Bowing', + 'Man Facepalming', + 'Woman Facepalming', + 'Man Shrugging', + 'Woman Shrugging', + 'Man Health Worker', + 'Woman Health Worker', + 'Man Student', + 'Woman Student', + 'Man Teacher', + 'Woman Teacher', + 'Man Judge', + 'Woman Judge', + 'Man Farmer', + 'Woman Farmer', + 'Man Cook', + 'Woman Cook', + 'Man Mechanic', + 'Woman Mechanic', + 'Man Factory Worker', + 'Woman Factory Worker', + 'Man Office Worker', + 'Woman Office Worker', + 'Man Scientist', + 'Woman Scientist', + 'Man Technologist', + 'Woman Technologist', + 'Man Singer', + 'Woman Singer', + 'Man Artist', + 'Woman Artist', + 'Man Pilot', + 'Woman Pilot', + 'Man Astronaut', + 'Woman Astronaut', + 'Man Firefighter', + 'Woman Firefighter', + 'Man Police Officer', + 'Woman Police Officer', + 'Man Detective', + 'Woman Detective', + 'Man Guard', + 'Woman Guard', + 'Man Construction Worker', + 'Woman Construction Worker', + 'Prince', + 'Princess', + 'Man Wearing Turban', + 'Woman Wearing Turban', + 'Man With Chinese Cap', + 'Woman With Headscarf', + 'Man in Tuxedo', + 'Bride With Veil', + 'Pregnant Woman', + 'Breast-Feeding', + 'Baby Angel', + 'Santa Claus', + 'Mrs. Claus', + 'Man Superhero', + 'Woman Superhero', + 'Man Supervillain', + 'Woman Supervillain', + 'Man Mage', + 'Woman Mage', + 'Man Fairy', + 'Woman Fairy', + 'Man Vampire', + 'Woman Vampire', + 'Merman', + 'Mermaid', + 'Man Elf', + 'Woman Elf', + 'Man Genie', + 'Woman Genie', + 'Man Zombie', + 'Woman Zombie', + 'Man Getting Massage', + 'Woman Getting Massage', + 'Man Getting Haircut', + 'Woman Getting Haircut', + 'Man Walking', + 'Woman Walking', + 'Man Running', + 'Woman Running', + 'Woman Dancing', + 'Man Dancing', + 'Man in Suit Levitating', + 'Men With Bunny Ears', + 'Women With Bunny Ears', + 'Man in Steamy Room', + 'Woman in Steamy Room', + 'Person in Lotus Position', + 'Women Holding Hands', + 'Woman and Man Holding Hands', + 'Men Holding Hands', + 'Kiss', + 'Kiss: Man, Man', + 'Kiss: Woman, Woman', + 'Couple With Heart', + 'Couple With Heart: Man, Man', + 'Couple With Heart: Woman, Woman', + 'Family', + 'Family: Man, Woman, Boy', + 'Family: Man, Woman, Girl', + 'Family: Man, Woman, Girl, Boy', + 'Family: Man, Woman, Boy, Boy', + 'Family: Man, Woman, Girl, Girl', + 'Family: Man, Man, Boy', + 'Family: Man, Man, Girl', + 'Family: Man, Man, Girl, Boy', + 'Family: Man, Man, Boy, Boy', + 'Family: Man, Man, Girl, Girl', + 'Family: Woman, Woman, Boy', + 'Family: Woman, Woman, Girl', + 'Family: Woman, Woman, Girl, Boy', + 'Family: Woman, Woman, Boy, Boy', + 'Family: Woman, Woman, Girl, Girl', + 'Family: Man, Boy', + 'Family: Man, Boy, Boy', + 'Family: Man, Girl', + 'Family: Man, Girl, Boy', + 'Family: Man, Girl, Girl', + 'Family: Woman, Boy', + 'Family: Woman, Boy, Boy', + 'Family: Woman, Girl', + 'Family: Woman, Girl, Boy', + 'Family: Woman, Girl, Girl', + 'Speaking Head', + 'Bust in Silhouette', + 'Busts in Silhouette', + 'Footprints', + 'Luggage', + 'Closed Umbrella', + 'Umbrella', + 'Thread', + 'Yarn', + 'Glasses', + 'Sunglasses', + 'Goggles', + 'Lab Coat', + 'Necktie', + 'T-Shirt', + 'Jeans', + 'Scarf', + 'Gloves', + 'Coat', + 'Socks', + 'Dress', + 'Kimono', + 'Bikini', + 'Woman’s Clothes', + 'Purse', + 'Handbag', + 'Clutch Bag', + 'Backpack', + 'Man’s Shoe', + 'Running Shoe', + 'Hiking Boot', + 'Flat Shoe', + 'High-Heeled Shoe', + 'Woman’s Sandal', + 'Woman’s Boot', + 'Crown', + 'Woman’s Hat', + 'Top Hat', + 'Graduation Cap', + 'Billed Cap', + 'Rescue Worker’s Helmet', + 'Lipstick', + 'Ring', + 'Briefcase' +], [ + '😀', + '😃', + '😄', + '😁', + '😆', + '😅', + '🤣', + '😂', + '🙂', + '🙃', + '😉', + '😊', + '😇', + '🥰', + '😍', + '🤩', + '😘', + '😗', + '☺', + '😚', + '😙', + '😋', + '😛', + '😜', + '🤪', + '😝', + '🤑', + '🤗', + '🤭', + '🤫', + '🤔', + '🤐', + '🤨', + '😐', + '😑', + '😶', + '😏', + '😒', + '🙄', + '😬', + '🤥', + '😌', + '😔', + '😪', + '🤤', + '😴', + '😷', + '🤒', + '🤕', + '🤢', + '🤮', + '🤧', + '🥵', + '🥶', + '🥴', + '😵', + '🤯', + '🤠', + '🥳', + '😎', + '🤓', + '🧐', + '😕', + '😟', + '🙁', + '☹️', + '😮', + '😯', + '😲', + '😳', + '🥺', + '😦', + '😧', + '😨', + '😰', + '😥', + '😢', + '😭', + '😱', + '😖', + '😣', + '😞', + '😓', + '😩', + '😫', + '😤', + '😡', + '😠', + '🤬', + '😈', + '👿', + '💀', + '☠', + '💩', + '🤡', + '👹', + '👺', + '👻', + '👽', + '👾', + '🤖', + '😺', + '😸', + '😹', + '😻', + '😼', + '😽', + '🙀', + '😿', + '😾', + '💋', + '👋', + '🤚', + '🖐', + '✋', + '🖖', + '👌', + '✌', + '🤞', + '🤟', + '🤘', + '🤙', + '👈', + '👉', + '👆', + '🖕', + '👇', + '☝', + '👍', + '👎', + '✊', + '👊', + '🤛', + '🤜', + '👏', + '🙌', + '👐', + '🤲', + '🤝', + '🙏', + '✍', + '💅', + '🤳', + '💪', + '🦵', + '🦶', + '👂', + '👃', + '🧠', + '🦷', + '🦴', + '👀', + '👁', + '👅', + '👄', + '👶', + '🧒', + '👦', + '👧', + '🧑', + '👨', + '🧔', + '👱', + '👨‍🦰', + '👨‍🦱', + '👨‍🦳', + '👨‍🦲', + '👩', + '👱', + '👩‍🦰', + '👩‍🦱', + '👩‍🦳', + '👩‍🦲', + '🧓', + '👴', + '👵', + '🙍', + '🙍', + '🙎', + '🙎', + '🙅', + '🙅', + '🙆', + '🙆', + '💁', + '💁', + '🙋', + '🙋', + '🙇', + '🙇', + '🤦', + '🤦', + '🤷', + '🤷', + '👨‍⚕️', + '👩‍⚕️', + '👨‍🎓', + '👩‍🎓', + '👨‍🏫', + '👩‍🏫', + '👨‍⚖️', + '👩‍⚖️', + '👨‍🌾', + '👩‍🌾', + '👨‍🍳', + '👩‍🍳', + '👨‍🔧', + '👩‍🔧', + '👨‍🏭', + '👩‍🏭', + '👨‍💼', + '👩‍💼', + '👨‍🔬', + '👩‍🔬', + '👨‍💻', + '👩‍💻', + '👨‍🎤', + '👩‍🎤', + '👨‍🎨', + '👩‍🎨', + '👨‍✈️', + '👩‍✈️', + '👨‍🚀', + '👩‍🚀', + '👨‍🚒', + '👩‍🚒', + '👮', + '👮', + '🕵️', + '🕵️', + '💂', + '💂', + '👷', + '👷', + '🤴', + '👸', + '👳', + '👳', + '👲', + '🧕', + '🤵', + '👰', + '🤰', + '🤱', + '👼', + '🎅', + '🤶', + '🦸', + '🦸', + '🦹', + '🦹', + '🧙', + '🧙', + '🧚', + '🧚', + '🧛', + '🧛', + '🧜', + '🧜', + '🧝', + '🧝', + '🧞', + '🧞', + '🧟', + '🧟', + '💆', + '💆', + '💇', + '💇', + '🚶', + '🚶', + '🏃', + '🏃', + '💃', + '🕺', + '🕴', + '👯', + '👯', + '🧖', + '🧖', + '🧘', + '👭', + '👫', + '👬', + '💏', + '👨‍❤️‍💋‍👨', + '👩‍❤️‍💋‍👩', + '💑', + '👨‍❤️‍👨', + '👩‍❤️‍👩', + '👪', + '👨‍👩‍👦', + '👨‍👩‍👧', + '👨‍👩‍👧‍👦', + '👨‍👩‍👦‍👦', + '👨‍👩‍👧‍👧', + '👨‍👨‍👦', + '👨‍👨‍👧', + '👨‍👨‍👧‍👦', + '👨‍👨‍👦‍👦', + '👨‍👨‍👧‍👧', + '👩‍👩‍👦', + '👩‍👩‍👧', + '👩‍👩‍👧‍👦', + '👩‍👩‍👦‍👦', + '👩‍👩‍👧‍👧', + '👨‍👦', + '👨‍👦‍👦', + '👨‍👧', + '👨‍👧‍👦', + '👨‍👧‍👧', + '👩‍👦', + '👩‍👦‍👦', + '👩‍👧', + '👩‍👧‍👦', + '👩‍👧‍👧', + '🗣', + '👤', + '👥', + '👣', + '🧳', + '🌂', + '☂', + '🧵', + '🧶', + '👓', + '🕶', + '🥽', + '🥼', + '👔', + '👕', + '👖', + '🧣', + '🧤', + '🧥', + '🧦', + '👗', + '👘', + '👙', + '👚', + '👛', + '👜', + '👝', + '🎒', + '👞', + '👟', + '🥾', + '🥿', + '👠', + '👡', + '👢', + '👑', + '👒', + '🎩', + '🎓', + '🧢', + '⛑', + '💄', + '💍', + '💼' +]); + +/// Map of all possible emojis along with their names in [Category.ANIMALS] +final Map animals = Map.fromIterables([ + 'Dog Face', + 'Cat Face', + 'Mouse Face', + 'Hamster Face', + 'Rabbit Face', + 'Fox Face', + 'Bear Face', + 'Panda Face', + 'Koala Face', + 'Tiger Face', + 'Lion Face', + 'Cow Face', + 'Pig Face', + 'Pig Nose', + 'Frog Face', + 'Monkey Face', + 'See-No-Evil Monkey', + 'Hear-No-Evil Monkey', + 'Speak-No-Evil Monkey', + 'Monkey', + 'Collision', + 'Dizzy', + 'Sweat Droplets', + 'Dashing Away', + 'Gorilla', + 'Dog', + 'Poodle', + 'Wolf Face', + 'Raccoon', + 'Cat', + 'Tiger', + 'Leopard', + 'Horse Face', + 'Horse', + 'Unicorn Face', + 'Zebra', + 'Ox', + 'Water Buffalo', + 'Cow', + 'Pig', + 'Boar', + 'Ram', + 'Ewe', + 'Goat', + 'Camel', + 'Two-Hump Camel', + 'Llama', + 'Giraffe', + 'Elephant', + 'Rhinoceros', + 'Hippopotamus', + 'Mouse', + 'Rat', + 'Rabbit', + 'Chipmunk', + 'Hedgehog', + 'Bat', + 'Kangaroo', + 'Badger', + 'Paw Prints', + 'Turkey', + 'Chicken', + 'Rooster', + 'Hatching Chick', + 'Baby Chick', + 'Front-Facing Baby Chick', + 'Bird', + 'Penguin', + 'Dove', + 'Eagle', + 'Duck', + 'Swan', + 'Owl', + 'Peacock', + 'Parrot', + 'Crocodile', + 'Turtle', + 'Lizard', + 'Snake', + 'Dragon Face', + 'Dragon', + 'Sauropod', + 'T-Rex', + 'Spouting Whale', + 'Whale', + 'Dolphin', + 'Fish', + 'Tropical Fish', + 'Blowfish', + 'Shark', + 'Octopus', + 'Spiral Shell', + 'Snail', + 'Butterfly', + 'Bug', + 'Ant', + 'Honeybee', + 'Lady Beetle', + 'Cricket', + 'Spider', + 'Spider Web', + 'Scorpion', + 'Mosquito', + 'Microbe', + 'Bouquet', + 'Cherry Blossom', + 'White Flower', + 'Rosette', + 'Rose', + 'Wilted Flower', + 'Hibiscus', + 'Sunflower', + 'Blossom', + 'Tulip', + 'Seedling', + 'Evergreen Tree', + 'Deciduous Tree', + 'Palm Tree', + 'Cactus', + 'Sheaf of Rice', + 'Herb', + 'Shamrock', + 'Four Leaf Clover', + 'Maple Leaf', + 'Fallen Leaf', + 'Leaf Fluttering in Wind', + 'Mushroom', + 'Chestnut', + 'Crab', + 'Lobster', + 'Shrimp', + 'Squid', + 'Globe Showing Europe-Africa', + 'Globe Showing Americas', + 'Globe Showing Asia-Australia', + 'Globe With Meridians', + 'New Moon', + 'Waxing Crescent Moon', + 'First Quarter Moon', + 'Waxing Gibbous Moon', + 'Full Moon', + 'Waning Gibbous Moon', + 'Last Quarter Moon', + 'Waning Crescent Moon', + 'Crescent Moon', + 'New Moon Face', + 'First Quarter Moon Face', + 'Last Quarter Moon Face', + 'Sun', + 'Full Moon Face', + 'Sun With Face', + 'Star', + 'Glowing Star', + 'Shooting Star', + 'Cloud', + 'Sun Behind Cloud', + 'Cloud With Lightning and Rain', + 'Sun Behind Small Cloud', + 'Sun Behind Large Cloud', + 'Sun Behind Rain Cloud', + 'Cloud With Rain', + 'Cloud With Snow', + 'Cloud With Lightning', + 'Tornado', + 'Fog', + 'Wind Face', + 'Rainbow', + 'Umbrella', + 'Umbrella With Rain Drops', + 'High Voltage', + 'Snowflake', + 'Snowman Without Snow', + 'Snowman', + 'Comet', + 'Fire', + 'Droplet', + 'Water Wave', + 'Christmas Tree', + 'Sparkles', + 'Tanabata Tree', + 'Pine Decoration' +], [ + '🐶', + '🐱', + '🐭', + '🐹', + '🐰', + '🦊', + '🐻', + '🐼', + '🐨', + '🐯', + '🦁', + '🐮', + '🐷', + '🐽', + '🐸', + '🐵', + '🙈', + '🙉', + '🙊', + '🐒', + '💥', + '💫', + '💦', + '💨', + '🦍', + '🐕', + '🐩', + '🐺', + '🦝', + '🐈', + '🐅', + '🐆', + '🐴', + '🐎', + '🦄', + '🦓', + '🐂', + '🐃', + '🐄', + '🐖', + '🐗', + '🐏', + '🐑', + '🐐', + '🐪', + '🐫', + '🦙', + '🦒', + '🐘', + '🦏', + '🦛', + '🐁', + '🐀', + '🐇', + '🐿', + '🦔', + '🦇', + '🦘', + '🦡', + '🐾', + '🦃', + '🐔', + '🐓', + '🐣', + '🐤', + '🐥', + '🐦', + '🐧', + '🕊', + '🦅', + '🦆', + '🦢', + '🦉', + '🦚', + '🦜', + '🐊', + '🐢', + '🦎', + '🐍', + '🐲', + '🐉', + '🦕', + '🦖', + '🐳', + '🐋', + '🐬', + '🐟', + '🐠', + '🐡', + '🦈', + '🐙', + '🐚', + '🐌', + '🦋', + '🐛', + '🐜', + '🐝', + '🐞', + '🦗', + '🕷', + '🕸', + '🦂', + '🦟', + '🦠', + '💐', + '🌸', + '💮', + '🏵', + '🌹', + '🥀', + '🌺', + '🌻', + '🌼', + '🌷', + '🌱', + '🌲', + '🌳', + '🌴', + '🌵', + '🌾', + '🌿', + '☘', + '🍀', + '🍁', + '🍂', + '🍃', + '🍄', + '🌰', + '🦀', + '🦞', + '🦐', + '🦑', + '🌍', + '🌎', + '🌏', + '🌐', + '🌑', + '🌒', + '🌓', + '🌔', + '🌕', + '🌖', + '🌗', + '🌘', + '🌙', + '🌚', + '🌛', + '🌜', + '☀', + '🌝', + '🌞', + '⭐', + '🌟', + '🌠', + '☁', + '⛅', + '⛈', + '🌤', + '🌥', + '🌦', + '🌧', + '🌨', + '🌩', + '🌪', + '🌫', + '🌬', + '🌈', + '☂', + '☔', + '⚡', + '❄', + '☃', + '⛄', + '☄', + '🔥', + '💧', + '🌊', + '🎄', + '✨', + '🎋', + '🎍' +]); + +/// Map of all possible emojis along with their names in [Category.FOODS] +final Map foods = Map.fromIterables([ + 'Grapes', + 'Melon', + 'Watermelon', + 'Tangerine', + 'Lemon', + 'Banana', + 'Pineapple', + 'Mango', + 'Red Apple', + 'Green Apple', + 'Pear', + 'Peach', + 'Cherries', + 'Strawberry', + 'Kiwi Fruit', + 'Tomato', + 'Coconut', + 'Avocado', + 'Eggplant', + 'Potato', + 'Carrot', + 'Ear of Corn', + 'Hot Pepper', + 'Cucumber', + 'Leafy Green', + 'Broccoli', + 'Mushroom', + 'Peanuts', + 'Chestnut', + 'Bread', + 'Croissant', + 'Baguette Bread', + 'Pretzel', + 'Bagel', + 'Pancakes', + 'Cheese Wedge', + 'Meat on Bone', + 'Poultry Leg', + 'Cut of Meat', + 'Bacon', + 'Hamburger', + 'French Fries', + 'Pizza', + 'Hot Dog', + 'Sandwich', + 'Taco', + 'Burrito', + 'Stuffed Flatbread', + 'Cooking', + 'Shallow Pan of Food', + 'Pot of Food', + 'Bowl With Spoon', + 'Green Salad', + 'Popcorn', + 'Salt', + 'Canned Food', + 'Bento Box', + 'Rice Cracker', + 'Rice Ball', + 'Cooked Rice', + 'Curry Rice', + 'Steaming Bowl', + 'Spaghetti', + 'Roasted Sweet Potato', + 'Oden', + 'Sushi', + 'Fried Shrimp', + 'Fish Cake With Swirl', + 'Moon Cake', + 'Dango', + 'Dumpling', + 'Fortune Cookie', + 'Takeout Box', + 'Soft Ice Cream', + 'Shaved Ice', + 'Ice Cream', + 'Doughnut', + 'Cookie', + 'Birthday Cake', + 'Shortcake', + 'Cupcake', + 'Pie', + 'Chocolate Bar', + 'Candy', + 'Lollipop', + 'Custard', + 'Honey Pot', + 'Baby Bottle', + 'Glass of Milk', + 'Hot Beverage', + 'Teacup Without Handle', + 'Sake', + 'Bottle With Popping Cork', + 'Wine Glass', + 'Cocktail Glass', + 'Tropical Drink', + 'Beer Mug', + 'Clinking Beer Mugs', + 'Clinking Glasses', + 'Tumbler Glass', + 'Cup With Straw', + 'Chopsticks', + 'Fork and Knife With Plate', + 'Fork and Knife', + 'Spoon' +], [ + '🍇', + '🍈', + '🍉', + '🍊', + '🍋', + '🍌', + '🍍', + '🥭', + '🍎', + '🍏', + '🍐', + '🍑', + '🍒', + '🍓', + '🥝', + '🍅', + '🥥', + '🥑', + '🍆', + '🥔', + '🥕', + '🌽', + '🌶', + '🥒', + '🥬', + '🥦', + '🍄', + '🥜', + '🌰', + '🍞', + '🥐', + '🥖', + '🥨', + '🥯', + '🥞', + '🧀', + '🍖', + '🍗', + '🥩', + '🥓', + '🍔', + '🍟', + '🍕', + '🌭', + '🥪', + '🌮', + '🌯', + '🥙', + '🍳', + '🥘', + '🍲', + '🥣', + '🥗', + '🍿', + '🧂', + '🥫', + '🍱', + '🍘', + '🍙', + '🍚', + '🍛', + '🍜', + '🍝', + '🍠', + '🍢', + '🍣', + '🍤', + '🍥', + '🥮', + '🍡', + '🥟', + '🥠', + '🥡', + '🍦', + '🍧', + '🍨', + '🍩', + '🍪', + '🎂', + '🍰', + '🧁', + '🥧', + '🍫', + '🍬', + '🍭', + '🍮', + '🍯', + '🍼', + '🥛', + '☕', + '🍵', + '🍶', + '🍾', + '🍷', + '🍸', + '🍹', + '🍺', + '🍻', + '🥂', + '🥃', + '🥤', + '🥢', + '🍽', + '🍴', + '🥄' +]); + +/// Map of all possible emojis along with their names in [Category.TRAVEL] +final Map travel = Map.fromIterables([ + 'Person Rowing Boat', + 'Map of Japan', + 'Snow-Capped Mountain', + 'Mountain', + 'Volcano', + 'Mount Fuji', + 'Camping', + 'Beach With Umbrella', + 'Desert', + 'Desert Island', + 'National Park', + 'Stadium', + 'Classical Building', + 'Building Construction', + 'Houses', + 'Derelict House', + 'House', + 'House With Garden', + 'Office Building', + 'Japanese Post Office', + 'Post Office', + 'Hospital', + 'Bank', + 'Hotel', + 'Love Hotel', + 'Convenience Store', + 'School', + 'Department Store', + 'Factory', + 'Japanese Castle', + 'Castle', + 'Wedding', + 'Tokyo Tower', + 'Statue of Liberty', + 'Church', + 'Mosque', + 'Synagogue', + 'Shinto Shrine', + 'Kaaba', + 'Fountain', + 'Tent', + 'Foggy', + 'Night With Stars', + 'Cityscape', + 'Sunrise Over Mountains', + 'Sunrise', + 'Cityscape at Dusk', + 'Sunset', + 'Bridge at Night', + 'Carousel Horse', + 'Ferris Wheel', + 'Roller Coaster', + 'Locomotive', + 'Railway Car', + 'High-Speed Train', + 'Bullet Train', + 'Train', + 'Metro', + 'Light Rail', + 'Station', + 'Tram', + 'Monorail', + 'Mountain Railway', + 'Tram Car', + 'Bus', + 'Oncoming Bus', + 'Trolleybus', + 'Minibus', + 'Ambulance', + 'Fire Engine', + 'Police Car', + 'Oncoming Police Car', + 'Taxi', + 'Oncoming Taxi', + 'Automobile', + 'Oncoming Automobile', + 'Delivery Truck', + 'Articulated Lorry', + 'Tractor', + 'Racing Car', + 'Motorcycle', + 'Motor Scooter', + 'Bicycle', + 'Kick Scooter', + 'Bus Stop', + 'Railway Track', + 'Fuel Pump', + 'Police Car Light', + 'Horizontal Traffic Light', + 'Vertical Traffic Light', + 'Construction', + 'Anchor', + 'Sailboat', + 'Speedboat', + 'Passenger Ship', + 'Ferry', + 'Motor Boat', + 'Ship', + 'Airplane', + 'Small Airplane', + 'Airplane Departure', + 'Airplane Arrival', + 'Seat', + 'Helicopter', + 'Suspension Railway', + 'Mountain Cableway', + 'Aerial Tramway', + 'Satellite', + 'Rocket', + 'Flying Saucer', + 'Shooting Star', + 'Milky Way', + 'Umbrella on Ground', + 'Fireworks', + 'Sparkler', + 'Moon Viewing Ceremony', + 'Yen Banknote', + 'Dollar Banknote', + 'Euro Banknote', + 'Pound Banknote', + 'Moai', + 'Passport Control', + 'Customs', + 'Baggage Claim', + 'Left Luggage' +], [ + '🚣', + '🗾', + '🏔', + '⛰', + '🌋', + '🗻', + '🏕', + '🏖', + '🏜', + '🏝', + '🏞', + '🏟', + '🏛', + '🏗', + '🏘', + '🏚', + '🏠', + '🏡', + '🏢', + '🏣', + '🏤', + '🏥', + '🏦', + '🏨', + '🏩', + '🏪', + '🏫', + '🏬', + '🏭', + '🏯', + '🏰', + '💒', + '🗼', + '🗽', + '⛪', + '🕌', + '🕍', + '⛩', + '🕋', + '⛲', + '⛺', + '🌁', + '🌃', + '🏙', + '🌄', + '🌅', + '🌆', + '🌇', + '🌉', + '🎠', + '🎡', + '🎢', + '🚂', + '🚃', + '🚄', + '🚅', + '🚆', + '🚇', + '🚈', + '🚉', + '🚊', + '🚝', + '🚞', + '🚋', + '🚌', + '🚍', + '🚎', + '🚐', + '🚑', + '🚒', + '🚓', + '🚔', + '🚕', + '🚖', + '🚗', + '🚘', + '🚚', + '🚛', + '🚜', + '🏎', + '🏍', + '🛵', + '🚲', + '🛴', + '🚏', + '🛤', + '⛽', + '🚨', + '🚥', + '🚦', + '🚧', + '⚓', + '⛵', + '🚤', + '🛳', + '⛴', + '🛥', + '🚢', + '✈', + '🛩', + '🛫', + '🛬', + '💺', + '🚁', + '🚟', + '🚠', + '🚡', + '🛰', + '🚀', + '🛸', + '🌠', + '🌌', + '⛱', + '🎆', + '🎇', + '🎑', + '💴', + '💵', + '💶', + '💷', + '🗿', + '🛂', + '🛃', + '🛄', + '🛅' +]); + +/// Map of all possible emojis along with their names in [Category.ACTIVITIES] +final Map activities = Map.fromIterables([ + 'Man in Suit Levitating', + 'Man Climbing', + 'Woman Climbing', + 'Horse Racing', + 'Skier', + 'Snowboarder', + 'Man Golfing', + 'Woman Golfing', + 'Man Surfing', + 'Woman Surfing', + 'Man Rowing Boat', + 'Woman Rowing Boat', + 'Man Swimming', + 'Woman Swimming', + 'Man Bouncing Ball', + 'Woman Bouncing Ball', + 'Man Lifting Weights', + 'Woman Lifting Weights', + 'Man Biking', + 'Woman Biking', + 'Man Mountain Biking', + 'Woman Mountain Biking', + 'Man Cartwheeling', + 'Woman Cartwheeling', + 'Men Wrestling', + 'Women Wrestling', + 'Man Playing Water Polo', + 'Woman Playing Water Polo', + 'Man Playing Handball', + 'Woman Playing Handball', + 'Man Juggling', + 'Woman Juggling', + 'Man in Lotus Position', + 'Woman in Lotus Position', + 'Circus Tent', + 'Skateboard', + 'Reminder Ribbon', + 'Admission Tickets', + 'Ticket', + 'Military Medal', + 'Trophy', + 'Sports Medal', + '1st Place Medal', + '2nd Place Medal', + '3rd Place Medal', + 'Soccer Ball', + 'Baseball', + 'Softball', + 'Basketball', + 'Volleyball', + 'American Football', + 'Rugby Football', + 'Tennis', + 'Flying Disc', + 'Bowling', + 'Cricket Game', + 'Field Hockey', + 'Ice Hockey', + 'Lacrosse', + 'Ping Pong', + 'Badminton', + 'Boxing Glove', + 'Martial Arts Uniform', + 'Flag in Hole', + 'Ice Skate', + 'Fishing Pole', + 'Running Shirt', + 'Skis', + 'Sled', + 'Curling Stone', + 'Direct Hit', + 'Pool 8 Ball', + 'Video Game', + 'Slot Machine', + 'Game Die', + 'Jigsaw', + 'Chess Pawn', + 'Performing Arts', + 'Artist Palette', + 'Thread', + 'Yarn', + 'Musical Score', + 'Microphone', + 'Headphone', + 'Saxophone', + 'Guitar', + 'Musical Keyboard', + 'Trumpet', + 'Violin', + 'Drum', + 'Clapper Board', + 'Bow and Arrow' +], [ + '🕴', + '🧗', + '🧗', + '🏇', + '⛷', + '🏂', + '🏌️', + '🏌️', + '🏄', + '🏄', + '🚣', + '🚣', + '🏊', + '🏊', + '⛹️', + '⛹️', + '🏋️', + '🏋️', + '🚴', + '🚴', + '🚵', + '🚵', + '🤸', + '🤸', + '🤼', + '🤼', + '🤽', + '🤽', + '🤾', + '🤾', + '🤹', + '🤹', + '🧘🏻‍♂️', + '🧘🏻‍♀️', + '🎪', + '🛹', + '🎗', + '🎟', + '🎫', + '🎖', + '🏆', + '🏅', + '🥇', + '🥈', + '🥉', + '⚽', + '⚾', + '🥎', + '🏀', + '🏐', + '🏈', + '🏉', + '🎾', + '🥏', + '🎳', + '🏏', + '🏑', + '🏒', + '🥍', + '🏓', + '🏸', + '🥊', + '🥋', + '⛳', + '⛸', + '🎣', + '🎽', + '🎿', + '🛷', + '🥌', + '🎯', + '🎱', + '🎮', + '🎰', + '🎲', + '🧩', + '♟', + '🎭', + '🎨', + '🧵', + '🧶', + '🎼', + '🎤', + '🎧', + '🎷', + '🎸', + '🎹', + '🎺', + '🎻', + '🥁', + '🎬', + '🏹' +]); + +/// Map of all possible emojis along with their names in [Category.OBJECTS] +final Map objects = Map.fromIterables([ + 'Love Letter', + 'Hole', + 'Bomb', + 'Person Taking Bath', + 'Person in Bed', + 'Kitchen Knife', + 'Amphora', + 'World Map', + 'Compass', + 'Brick', + 'Barber Pole', + 'Oil Drum', + 'Bellhop Bell', + 'Luggage', + 'Hourglass Done', + 'Hourglass Not Done', + 'Watch', + 'Alarm Clock', + 'Stopwatch', + 'Timer Clock', + 'Mantelpiece Clock', + 'Thermometer', + 'Umbrella on Ground', + 'Firecracker', + 'Balloon', + 'Party Popper', + 'Confetti Ball', + 'Japanese Dolls', + 'Carp Streamer', + 'Wind Chime', + 'Red Envelope', + 'Ribbon', + 'Wrapped Gift', + 'Crystal Ball', + 'Nazar Amulet', + 'Joystick', + 'Teddy Bear', + 'Framed Picture', + 'Thread', + 'Yarn', + 'Shopping Bags', + 'Prayer Beads', + 'Gem Stone', + 'Postal Horn', + 'Studio Microphone', + 'Level Slider', + 'Control Knobs', + 'Radio', + 'Mobile Phone', + 'Mobile Phone With Arrow', + 'Telephone', + 'Telephone Receiver', + 'Pager', + 'Fax Machine', + 'Battery', + 'Electric Plug', + 'Laptop Computer', + 'Desktop Computer', + 'Printer', + 'Keyboard', + 'Computer Mouse', + 'Trackball', + 'Computer Disk', + 'Floppy Disk', + 'Optical Disk', + 'DVD', + 'Abacus', + 'Movie Camera', + 'Film Frames', + 'Film Projector', + 'Television', + 'Camera', + 'Camera With Flash', + 'Video Camera', + 'Videocassette', + 'Magnifying Glass Tilted Left', + 'Magnifying Glass Tilted Right', + 'Candle', + 'Light Bulb', + 'Flashlight', + 'Red Paper Lantern', + 'Notebook With Decorative Cover', + 'Closed Book', + 'Open Book', + 'Green Book', + 'Blue Book', + 'Orange Book', + 'Books', + 'Notebook', + 'Page With Curl', + 'Scroll', + 'Page Facing Up', + 'Newspaper', + 'Rolled-Up Newspaper', + 'Bookmark Tabs', + 'Bookmark', + 'Label', + 'Money Bag', + 'Yen Banknote', + 'Dollar Banknote', + 'Euro Banknote', + 'Pound Banknote', + 'Money With Wings', + 'Credit Card', + 'Receipt', + 'Envelope', + 'E-Mail', + 'Incoming Envelope', + 'Envelope With Arrow', + 'Outbox Tray', + 'Inbox Tray', + 'Package', + 'Closed Mailbox With Raised Flag', + 'Closed Mailbox With Lowered Flag', + 'Open Mailbox With Raised Flag', + 'Open Mailbox With Lowered Flag', + 'Postbox', + 'Ballot Box With Ballot', + 'Pencil', + 'Black Nib', + 'Fountain Pen', + 'Pen', + 'Paintbrush', + 'Crayon', + 'Memo', + 'File Folder', + 'Open File Folder', + 'Card Index Dividers', + 'Calendar', + 'Tear-Off Calendar', + 'Spiral Notepad', + 'Spiral Calendar', + 'Card Index', + 'Chart Increasing', + 'Chart Decreasing', + 'Bar Chart', + 'Clipboard', + 'Pushpin', + 'Round Pushpin', + 'Paperclip', + 'Linked Paperclips', + 'Straight Ruler', + 'Triangular Ruler', + 'Scissors', + 'Card File Box', + 'File Cabinet', + 'Wastebasket', + 'Locked', + 'Unlocked', + 'Locked With Pen', + 'Locked With Key', + 'Key', + 'Old Key', + 'Hammer', + 'Pick', + 'Hammer and Pick', + 'Hammer and Wrench', + 'Dagger', + 'Crossed Swords', + 'Pistol', + 'Shield', + 'Wrench', + 'Nut and Bolt', + 'Gear', + 'Clamp', + 'Balance Scale', + 'Link', + 'Chains', + 'Toolbox', + 'Magnet', + 'Alembic', + 'Test Tube', + 'Petri Dish', + 'DNA', + 'Microscope', + 'Telescope', + 'Satellite Antenna', + 'Syringe', + 'Pill', + 'Door', + 'Bed', + 'Couch and Lamp', + 'Toilet', + 'Shower', + 'Bathtub', + 'Lotion Bottle', + 'Safety Pin', + 'Broom', + 'Basket', + 'Roll of Paper', + 'Soap', + 'Sponge', + 'Fire Extinguisher', + 'Cigarette', + 'Coffin', + 'Funeral Urn', + 'Moai', + 'Potable Water' +], [ + '💌', + '🕳', + '💣', + '🛀', + '🛌', + '🔪', + '🏺', + '🗺', + '🧭', + '🧱', + '💈', + '🛢', + '🛎', + '🧳', + '⌛', + '⏳', + '⌚', + '⏰', + '⏱', + '⏲', + '🕰', + '🌡', + '⛱', + '🧨', + '🎈', + '🎉', + '🎊', + '🎎', + '🎏', + '🎐', + '🧧', + '🎀', + '🎁', + '🔮', + '🧿', + '🕹', + '🧸', + '🖼', + '🧵', + '🧶', + '🛍', + '📿', + '💎', + '📯', + '🎙', + '🎚', + '🎛', + '📻', + '📱', + '📲', + '☎', + '📞', + '📟', + '📠', + '🔋', + '🔌', + '💻', + '🖥', + '🖨', + '⌨', + '🖱', + '🖲', + '💽', + '💾', + '💿', + '📀', + '🧮', + '🎥', + '🎞', + '📽', + '📺', + '📷', + '📸', + '📹', + '📼', + '🔍', + '🔎', + '🕯', + '💡', + '🔦', + '🏮', + '📔', + '📕', + '📖', + '📗', + '📘', + '📙', + '📚', + '📓', + '📃', + '📜', + '📄', + '📰', + '🗞', + '📑', + '🔖', + '🏷', + '💰', + '💴', + '💵', + '💶', + '💷', + '💸', + '💳', + '🧾', + '✉', + '📧', + '📨', + '📩', + '📤', + '📥', + '📦', + '📫', + '📪', + '📬', + '📭', + '📮', + '🗳', + '✏', + '✒', + '🖋', + '🖊', + '🖌', + '🖍', + '📝', + '📁', + '📂', + '🗂', + '📅', + '📆', + '🗒', + '🗓', + '📇', + '📈', + '📉', + '📊', + '📋', + '📌', + '📍', + '📎', + '🖇', + '📏', + '📐', + '✂', + '🗃', + '🗄', + '🗑', + '🔒', + '🔓', + '🔏', + '🔐', + '🔑', + '🗝', + '🔨', + '⛏', + '⚒', + '🛠', + '🗡', + '⚔', + '🔫', + '🛡', + '🔧', + '🔩', + '⚙', + '🗜', + '⚖', + '🔗', + '⛓', + '🧰', + '🧲', + '⚗', + '🧪', + '🧫', + '🧬', + '🔬', + '🔭', + '📡', + '💉', + '💊', + '🚪', + '🛏', + '🛋', + '🚽', + '🚿', + '🛁', + '🧴', + '🧷', + '🧹', + '🧺', + '🧻', + '🧼', + '🧽', + '🧯', + '🚬', + '⚰', + '⚱', + '🗿', + '🚰' +]); + +/// Map of all possible emojis along with their names in [Category.SYMBOLS] +final Map symbols = Map.fromIterables([ + 'Heart With Arrow', + 'Heart With Ribbon', + 'Sparkling Heart', + 'Growing Heart', + 'Beating Heart', + 'Revolving Hearts', + 'Two Hearts', + 'Heart Decoration', + 'Heavy Heart Exclamation', + 'Broken Heart', + 'Red Heart', + 'Orange Heart', + 'Yellow Heart', + 'Green Heart', + 'Blue Heart', + 'Purple Heart', + 'Black Heart', + 'Hundred Points', + 'Anger Symbol', + 'Speech Balloon', + 'Eye in Speech Bubble', + 'Right Anger Bubble', + 'Thought Balloon', + 'Zzz', + 'White Flower', + 'Hot Springs', + 'Barber Pole', + 'Stop Sign', + 'Twelve O’Clock', + 'Twelve-Thirty', + 'One O’Clock', + 'One-Thirty', + 'Two O’Clock', + 'Two-Thirty', + 'Three O’Clock', + 'Three-Thirty', + 'Four O’Clock', + 'Four-Thirty', + 'Five O’Clock', + 'Five-Thirty', + 'Six O’Clock', + 'Six-Thirty', + 'Seven O’Clock', + 'Seven-Thirty', + 'Eight O’Clock', + 'Eight-Thirty', + 'Nine O’Clock', + 'Nine-Thirty', + 'Ten O’Clock', + 'Ten-Thirty', + 'Eleven O’Clock', + 'Eleven-Thirty', + 'Cyclone', + 'Spade Suit', + 'Heart Suit', + 'Diamond Suit', + 'Club Suit', + 'Joker', + 'Mahjong Red Dragon', + 'Flower Playing Cards', + 'Muted Speaker', + 'Speaker Low Volume', + 'Speaker Medium Volume', + 'Speaker High Volume', + 'Loudspeaker', + 'Megaphone', + 'Postal Horn', + 'Bell', + 'Bell With Slash', + 'Musical Note', + 'Musical Notes', + 'ATM Sign', + 'Litter in Bin Sign', + 'Potable Water', + 'Wheelchair Symbol', + 'Men’s Room', + 'Women’s Room', + 'Restroom', + 'Baby Symbol', + 'Water Closet', + 'Warning', + 'Children Crossing', + 'No Entry', + 'Prohibited', + 'No Bicycles', + 'No Smoking', + 'No Littering', + 'Non-Potable Water', + 'No Pedestrians', + 'No One Under Eighteen', + 'Radioactive', + 'Biohazard', + 'Up Arrow', + 'Up-Right Arrow', + 'Right Arrow', + 'Down-Right Arrow', + 'Down Arrow', + 'Down-Left Arrow', + 'Left Arrow', + 'Up-Left Arrow', + 'Up-Down Arrow', + 'Left-Right Arrow', + 'Right Arrow Curving Left', + 'Left Arrow Curving Right', + 'Right Arrow Curving Up', + 'Right Arrow Curving Down', + 'Clockwise Vertical Arrows', + 'Counterclockwise Arrows Button', + 'Back Arrow', + 'End Arrow', + 'On! Arrow', + 'Soon Arrow', + 'Top Arrow', + 'Place of Worship', + 'Atom Symbol', + 'Om', + 'Star of David', + 'Wheel of Dharma', + 'Yin Yang', + 'Latin Cross', + 'Orthodox Cross', + 'Star and Crescent', + 'Peace Symbol', + 'Menorah', + 'Dotted Six-Pointed Star', + 'Aries', + 'Taurus', + 'Gemini', + 'Cancer', + 'Leo', + 'Virgo', + 'Libra', + 'Scorpio', + 'Sagittarius', + 'Capricorn', + 'Aquarius', + 'Pisces', + 'Ophiuchus', + 'Shuffle Tracks Button', + 'Repeat Button', + 'Repeat Single Button', + 'Play Button', + 'Fast-Forward Button', + 'Reverse Button', + 'Fast Reverse Button', + 'Upwards Button', + 'Fast Up Button', + 'Downwards Button', + 'Fast Down Button', + 'Stop Button', + 'Eject Button', + 'Cinema', + 'Dim Button', + 'Bright Button', + 'Antenna Bars', + 'Vibration Mode', + 'Mobile Phone Off', + 'Infinity', + 'Recycling Symbol', + 'Trident Emblem', + 'Name Badge', + 'Japanese Symbol for Beginner', + 'Heavy Large Circle', + 'White Heavy Check Mark', + 'Ballot Box With Check', + 'Heavy Check Mark', + 'Heavy Multiplication X', + 'Cross Mark', + 'Cross Mark Button', + 'Heavy Plus Sign', + 'Heavy Minus Sign', + 'Heavy Division Sign', + 'Curly Loop', + 'Double Curly Loop', + 'Part Alternation Mark', + 'Eight-Spoked Asterisk', + 'Eight-Pointed Star', + 'Sparkle', + 'Double Exclamation Mark', + 'Exclamation Question Mark', + 'Question Mark', + 'White Question Mark', + 'White Exclamation Mark', + 'Exclamation Mark', + 'Copyright', + 'Registered', + 'Trade Mark', + 'Keycap Number Sign', + 'Keycap Digit Zero', + 'Keycap Digit One', + 'Keycap Digit Two', + 'Keycap Digit Three', + 'Keycap Digit Four', + 'Keycap Digit Five', + 'Keycap Digit Six', + 'Keycap Digit Seven', + 'Keycap Digit Eight', + 'Keycap Digit Nine', + 'Keycap: 10', + 'Input Latin Uppercase', + 'Input Latin Lowercase', + 'Input Numbers', + 'Input Symbols', + 'Input Latin Letters', + 'A Button (Blood Type)', + 'AB Button (Blood Type)', + 'B Button (Blood Type)', + 'CL Button', + 'Cool Button', + 'Free Button', + 'Information', + 'ID Button', + 'Circled M', + 'New Button', + 'NG Button', + 'O Button (Blood Type)', + 'OK Button', + 'P Button', + 'SOS Button', + 'Up! Button', + 'Vs Button', + 'Japanese “Here” Button', + 'Japanese “Service Charge” Button', + 'Japanese “Monthly Amount” Button', + 'Japanese “Not Free of Charge” Button', + 'Japanese “Reserved” Button', + 'Japanese “Bargain” Button', + 'Japanese “Discount” Button', + 'Japanese “Free of Charge” Button', + 'Japanese “Prohibited” Button', + 'Japanese “Acceptable” Button', + 'Japanese “Application” Button', + 'Japanese “Passing Grade” Button', + 'Japanese “Vacancy” Button', + 'Japanese “Congratulations” Button', + 'Japanese “Secret” Button', + 'Japanese “Open for Business” Button', + 'Japanese “No Vacancy” Button', + 'Red Circle', + 'Blue Circle', + 'Black Circle', + 'White Circle', + 'Black Large Square', + 'White Large Square', + 'Black Medium Square', + 'White Medium Square', + 'Black Medium-Small Square', + 'White Medium-Small Square', + 'Black Small Square', + 'White Small Square', + 'Large Orange Diamond', + 'Large Blue Diamond', + 'Small Orange Diamond', + 'Small Blue Diamond', + 'Red Triangle Pointed Up', + 'Red Triangle Pointed Down', + 'Diamond With a Dot', + 'White Square Button', + 'Black Square Button' +], [ + '💘', + '💝', + '💖', + '💗', + '💓', + '💞', + '💕', + '💟', + '❣', + '💔', + '❤', + '🧡', + '💛', + '💚', + '💙', + '💜', + '🖤', + '💯', + '💢', + '💬', + '👁️‍🗨️', + '🗯', + '💭', + '💤', + '💮', + '♨', + '💈', + '🛑', + '🕛', + '🕧', + '🕐', + '🕜', + '🕑', + '🕝', + '🕒', + '🕞', + '🕓', + '🕟', + '🕔', + '🕠', + '🕕', + '🕡', + '🕖', + '🕢', + '🕗', + '🕣', + '🕘', + '🕤', + '🕙', + '🕥', + '🕚', + '🕦', + '🌀', + '♠', + '♥', + '♦', + '♣', + '🃏', + '🀄', + '🎴', + '🔇', + '🔈', + '🔉', + '🔊', + '📢', + '📣', + '📯', + '🔔', + '🔕', + '🎵', + '🎶', + '🏧', + '🚮', + '🚰', + '♿', + '🚹', + '🚺', + '🚻', + '🚼', + '🚾', + '⚠', + '🚸', + '⛔', + '🚫', + '🚳', + '🚭', + '🚯', + '🚱', + '🚷', + '🔞', + '☢', + '☣', + '⬆', + '↗', + '➡', + '↘', + '⬇', + '↙', + '⬅', + '↖', + '↕', + '↔', + '↩', + '↪', + '⤴', + '⤵', + '🔃', + '🔄', + '🔙', + '🔚', + '🔛', + '🔜', + '🔝', + '🛐', + '⚛', + '🕉', + '✡', + '☸', + '☯', + '✝', + '☦', + '☪', + '☮', + '🕎', + '🔯', + '♈', + '♉', + '♊', + '♋', + '♌', + '♍', + '♎', + '♏', + '♐', + '♑', + '♒', + '♓', + '⛎', + '🔀', + '🔁', + '🔂', + '▶', + '⏩', + '◀', + '⏪', + '🔼', + '⏫', + '🔽', + '⏬', + '⏹', + '⏏', + '🎦', + '🔅', + '🔆', + '📶', + '📳', + '📴', + '♾', + '♻', + '🔱', + '📛', + '🔰', + '⭕', + '✅', + '☑', + '✔', + '✖', + '❌', + '❎', + '➕', + '➖', + '➗', + '➰', + '➿', + '〽', + '✳', + '✴', + '❇', + '‼', + '⁉', + '❓', + '❔', + '❕', + '❗', + '©', + '®', + '™', + '#️⃣', + '0️⃣', + '1️⃣', + '2️⃣', + '3️⃣', + '4️⃣', + '5️⃣', + '6️⃣', + '7️⃣', + '8️⃣', + '9️⃣', + '🔟', + '🔠', + '🔡', + '🔢', + '🔣', + '🔤', + '🅰', + '🆎', + '🅱', + '🆑', + '🆒', + '🆓', + 'ℹ', + '🆔', + 'Ⓜ', + '🆕', + '🆖', + '🅾', + '🆗', + '🅿', + '🆘', + '🆙', + '🆚', + '🈁', + '🈂', + '🈷', + '🈶', + '🈯', + '🉐', + '🈹', + '🈚', + '🈲', + '🉑', + '🈸', + '🈴', + '🈳', + '㊗', + '㊙', + '🈺', + '🈵', + '🔴', + '🔵', + '⚫', + '⚪', + '⬛', + '⬜', + '◼', + '◻', + '◾', + '◽', + '▪', + '▫', + '🔶', + '🔷', + '🔸', + '🔹', + '🔺', + '🔻', + '💠', + '🔳', + '🔲' +]); + +/// Map of all possible emojis along with their names in [Category.FLAGS] +final Map flags = Map.fromIterables([ + 'Chequered Flag', + 'Triangular Flag', + 'Crossed Flags', + 'Black Flag', + 'White Flag', + 'Rainbow Flag', + 'Pirate Flag', + 'Flag: Ascension Island', + 'Flag: Andorra', + 'Flag: United Arab Emirates', + 'Flag: Afghanistan', + 'Flag: Antigua & Barbuda', + 'Flag: Anguilla', + 'Flag: Albania', + 'Flag: Armenia', + 'Flag: Angola', + 'Flag: Antarctica', + 'Flag: Argentina', + 'Flag: American Samoa', + 'Flag: Austria', + 'Flag: Australia', + 'Flag: Aruba', + 'Flag: Åland Islands', + 'Flag: Azerbaijan', + 'Flag: Bosnia & Herzegovina', + 'Flag: Barbados', + 'Flag: Bangladesh', + 'Flag: Belgium', + 'Flag: Burkina Faso', + 'Flag: Bulgaria', + 'Flag: Bahrain', + 'Flag: Burundi', + 'Flag: Benin', + 'Flag: St. Barthélemy', + 'Flag: Bermuda', + 'Flag: Brunei', + 'Flag: Bolivia', + 'Flag: Caribbean Netherlands', + 'Flag: Brazil', + 'Flag: Bahamas', + 'Flag: Bhutan', + 'Flag: Bouvet Island', + 'Flag: Botswana', + 'Flag: Belarus', + 'Flag: Belize', + 'Flag: Canada', + 'Flag: Cocos (Keeling) Islands', + 'Flag: Congo - Kinshasa', + 'Flag: Central African Republic', + 'Flag: Congo - Brazzaville', + 'Flag: Switzerland', + 'Flag: Côte d’Ivoire', + 'Flag: Cook Islands', + 'Flag: Chile', + 'Flag: Cameroon', + 'Flag: China', + 'Flag: Colombia', + 'Flag: Clipperton Island', + 'Flag: Costa Rica', + 'Flag: Cuba', + 'Flag: Cape Verde', + 'Flag: Curaçao', + 'Flag: Christmas Island', + 'Flag: Cyprus', + 'Flag: Czechia', + 'Flag: Germany', + 'Flag: Diego Garcia', + 'Flag: Djibouti', + 'Flag: Denmark', + 'Flag: Dominica', + 'Flag: Dominican Republic', + 'Flag: Algeria', + 'Flag: Ceuta & Melilla', + 'Flag: Ecuador', + 'Flag: Estonia', + 'Flag: Egypt', + 'Flag: Western Sahara', + 'Flag: Eritrea', + 'Flag: Spain', + 'Flag: Ethiopia', + 'Flag: European Union', + 'Flag: Finland', + 'Flag: Fiji', + 'Flag: Falkland Islands', + 'Flag: Micronesia', + 'Flag: Faroe Islands', + 'Flag: France', + 'Flag: Gabon', + 'Flag: United Kingdom', + 'Flag: Grenada', + 'Flag: Georgia', + 'Flag: French Guiana', + 'Flag: Guernsey', + 'Flag: Ghana', + 'Flag: Gibraltar', + 'Flag: Greenland', + 'Flag: Gambia', + 'Flag: Guinea', + 'Flag: Guadeloupe', + 'Flag: Equatorial Guinea', + 'Flag: Greece', + 'Flag: South Georgia & South Sandwich Islands', + 'Flag: Guatemala', + 'Flag: Guam', + 'Flag: Guinea-Bissau', + 'Flag: Guyana', + 'Flag: Hong Kong SAR China', + 'Flag: Heard & McDonald Islands', + 'Flag: Honduras', + 'Flag: Croatia', + 'Flag: Haiti', + 'Flag: Hungary', + 'Flag: Canary Islands', + 'Flag: Indonesia', + 'Flag: Ireland', + 'Flag: Israel', + 'Flag: Isle of Man', + 'Flag: India', + 'Flag: British Indian Ocean Territory', + 'Flag: Iraq', + 'Flag: Iran', + 'Flag: Iceland', + 'Flag: Italy', + 'Flag: Jersey', + 'Flag: Jamaica', + 'Flag: Jordan', + 'Flag: Japan', + 'Flag: Kenya', + 'Flag: Kyrgyzstan', + 'Flag: Cambodia', + 'Flag: Kiribati', + 'Flag: Comoros', + 'Flag: St. Kitts & Nevis', + 'Flag: North Korea', + 'Flag: South Korea', + 'Flag: Kuwait', + 'Flag: Cayman Islands', + 'Flag: Kazakhstan', + 'Flag: Laos', + 'Flag: Lebanon', + 'Flag: St. Lucia', + 'Flag: Liechtenstein', + 'Flag: Sri Lanka', + 'Flag: Liberia', + 'Flag: Lesotho', + 'Flag: Lithuania', + 'Flag: Luxembourg', + 'Flag: Latvia', + 'Flag: Libya', + 'Flag: Morocco', + 'Flag: Monaco', + 'Flag: Moldova', + 'Flag: Montenegro', + 'Flag: St. Martin', + 'Flag: Madagascar', + 'Flag: Marshall Islands', + 'Flag: North Macedonia', + 'Flag: Mali', + 'Flag: Myanmar (Burma)', + 'Flag: Mongolia', + 'Flag: Macau Sar China', + 'Flag: Northern Mariana Islands', + 'Flag: Martinique', + 'Flag: Mauritania', + 'Flag: Montserrat', + 'Flag: Malta', + 'Flag: Mauritius', + 'Flag: Maldives', + 'Flag: Malawi', + 'Flag: Mexico', + 'Flag: Malaysia', + 'Flag: Mozambique', + 'Flag: Namibia', + 'Flag: New Caledonia', + 'Flag: Niger', + 'Flag: Norfolk Island', + 'Flag: Nigeria', + 'Flag: Nicaragua', + 'Flag: Netherlands', + 'Flag: Norway', + 'Flag: Nepal', + 'Flag: Nauru', + 'Flag: Niue', + 'Flag: New Zealand', + 'Flag: Oman', + 'Flag: Panama', + 'Flag: Peru', + 'Flag: French Polynesia', + 'Flag: Papua New Guinea', + 'Flag: Philippines', + 'Flag: Pakistan', + 'Flag: Poland', + 'Flag: St. Pierre & Miquelon', + 'Flag: Pitcairn Islands', + 'Flag: Puerto Rico', + 'Flag: Palestinian Territories', + 'Flag: Portugal', + 'Flag: Palau', + 'Flag: Paraguay', + 'Flag: Qatar', + 'Flag: Réunion', + 'Flag: Romania', + 'Flag: Serbia', + 'Flag: Russia', + 'Flag: Rwanda', + 'Flag: Saudi Arabia', + 'Flag: Solomon Islands', + 'Flag: Seychelles', + 'Flag: Sudan', + 'Flag: Sweden', + 'Flag: Singapore', + 'Flag: St. Helena', + 'Flag: Slovenia', + 'Flag: Svalbard & Jan Mayen', + 'Flag: Slovakia', + 'Flag: Sierra Leone', + 'Flag: San Marino', + 'Flag: Senegal', + 'Flag: Somalia', + 'Flag: Suriname', + 'Flag: South Sudan', + 'Flag: São Tomé & Príncipe', + 'Flag: El Salvador', + 'Flag: Sint Maarten', + 'Flag: Syria', + 'Flag: Swaziland', + 'Flag: Tristan Da Cunha', + 'Flag: Turks & Caicos Islands', + 'Flag: Chad', + 'Flag: French Southern Territories', + 'Flag: Togo', + 'Flag: Thailand', + 'Flag: Tajikistan', + 'Flag: Tokelau', + 'Flag: Timor-Leste', + 'Flag: Turkmenistan', + 'Flag: Tunisia', + 'Flag: Tonga', + 'Flag: Turkey', + 'Flag: Trinidad & Tobago', + 'Flag: Tuvalu', + 'Flag: Taiwan', + 'Flag: Tanzania', + 'Flag: Ukraine', + 'Flag: Uganda', + 'Flag: U.S. Outlying Islands', + 'Flag: United Nations', + 'Flag: United States', + 'Flag: Uruguay', + 'Flag: Uzbekistan', + 'Flag: Vatican City', + 'Flag: St. Vincent & Grenadines', + 'Flag: Venezuela', + 'Flag: British Virgin Islands', + 'Flag: U.S. Virgin Islands', + 'Flag: Vietnam', + 'Flag: Vanuatu', + 'Flag: Wallis & Futuna', + 'Flag: Samoa', + 'Flag: Kosovo', + 'Flag: Yemen', + 'Flag: Mayotte', + 'Flag: South Africa', + 'Flag: Zambia', + 'Flag: Zimbabwe' +], [ + '🏁', + '🚩', + '🎌', + '🏴', + '🏳', + '🏳️‍🌈', + '🏴‍☠️', + '🇦🇨', + '🇦🇩', + '🇦🇪', + '🇦🇫', + '🇦🇬', + '🇦🇮', + '🇦🇱', + '🇦🇲', + '🇦🇴', + '🇦🇶', + '🇦🇷', + '🇦🇸', + '🇦🇹', + '🇦🇺', + '🇦🇼', + '🇦🇽', + '🇦🇿', + '🇧🇦', + '🇧🇧', + '🇧🇩', + '🇧🇪', + '🇧🇫', + '🇧🇬', + '🇧🇭', + '🇧🇮', + '🇧🇯', + '🇧🇱', + '🇧🇲', + '🇧🇳', + '🇧🇴', + '🇧🇶', + '🇧🇷', + '🇧🇸', + '🇧🇹', + '🇧🇻', + '🇧🇼', + '🇧🇾', + '🇧🇿', + '🇨🇦', + '🇨🇨', + '🇨🇩', + '🇨🇫', + '🇨🇬', + '🇨🇭', + '🇨🇮', + '🇨🇰', + '🇨🇱', + '🇨🇲', + '🇨🇳', + '🇨🇴', + '🇨🇵', + '🇨🇷', + '🇨🇺', + '🇨🇻', + '🇨🇼', + '🇨🇽', + '🇨🇾', + '🇨🇿', + '🇩🇪', + '🇩🇬', + '🇩🇯', + '🇩🇰', + '🇩🇲', + '🇩🇴', + '🇩🇿', + '🇪🇦', + '🇪🇨', + '🇪🇪', + '🇪🇬', + '🇪🇭', + '🇪🇷', + '🇪🇸', + '🇪🇹', + '🇪🇺', + '🇫🇮', + '🇫🇯', + '🇫🇰', + '🇫🇲', + '🇫🇴', + '🇫🇷', + '🇬🇦', + '🇬🇧', + '🇬🇩', + '🇬🇪', + '🇬🇫', + '🇬🇬', + '🇬🇭', + '🇬🇮', + '🇬🇱', + '🇬🇲', + '🇬🇳', + '🇬🇵', + '🇬🇶', + '🇬🇷', + '🇬🇸', + '🇬🇹', + '🇬🇺', + '🇬🇼', + '🇬🇾', + '🇭🇰', + '🇭🇲', + '🇭🇳', + '🇭🇷', + '🇭🇹', + '🇭🇺', + '🇮🇨', + '🇮🇩', + '🇮🇪', + '🇮🇱', + '🇮🇲', + '🇮🇳', + '🇮🇴', + '🇮🇶', + '🇮🇷', + '🇮🇸', + '🇮🇹', + '🇯🇪', + '🇯🇲', + '🇯🇴', + '🇯🇵', + '🇰🇪', + '🇰🇬', + '🇰🇭', + '🇰🇮', + '🇰🇲', + '🇰🇳', + '🇰🇵', + '🇰🇷', + '🇰🇼', + '🇰🇾', + '🇰🇿', + '🇱🇦', + '🇱🇧', + '🇱🇨', + '🇱🇮', + '🇱🇰', + '🇱🇷', + '🇱🇸', + '🇱🇹', + '🇱🇺', + '🇱🇻', + '🇱🇾', + '🇲🇦', + '🇲🇨', + '🇲🇩', + '🇲🇪', + '🇲🇫', + '🇲🇬', + '🇲🇭', + '🇲🇰', + '🇲🇱', + '🇲🇲', + '🇲🇳', + '🇲🇴', + '🇲🇵', + '🇲🇶', + '🇲🇷', + '🇲🇸', + '🇲🇹', + '🇲🇺', + '🇲🇻', + '🇲🇼', + '🇲🇽', + '🇲🇾', + '🇲🇿', + '🇳🇦', + '🇳🇨', + '🇳🇪', + '🇳🇫', + '🇳🇬', + '🇳🇮', + '🇳🇱', + '🇳🇴', + '🇳🇵', + '🇳🇷', + '🇳🇺', + '🇳🇿', + '🇴🇲', + '🇵🇦', + '🇵🇪', + '🇵🇫', + '🇵🇬', + '🇵🇭', + '🇵🇰', + '🇵🇱', + '🇵🇲', + '🇵🇳', + '🇵🇷', + '🇵🇸', + '🇵🇹', + '🇵🇼', + '🇵🇾', + '🇶🇦', + '🇷🇪', + '🇷🇴', + '🇷🇸', + '🇷🇺', + '🇷🇼', + '🇸🇦', + '🇸🇧', + '🇸🇨', + '🇸🇩', + '🇸🇪', + '🇸🇬', + '🇸🇭', + '🇸🇮', + '🇸🇯', + '🇸🇰', + '🇸🇱', + '🇸🇲', + '🇸🇳', + '🇸🇴', + '🇸🇷', + '🇸🇸', + '🇸🇹', + '🇸🇻', + '🇸🇽', + '🇸🇾', + '🇸🇿', + '🇹🇦', + '🇹🇨', + '🇹🇩', + '🇹🇫', + '🇹🇬', + '🇹🇭', + '🇹🇯', + '🇹🇰', + '🇹🇱', + '🇹🇲', + '🇹🇳', + '🇹🇴', + '🇹🇷', + '🇹🇹', + '🇹🇻', + '🇹🇼', + '🇹🇿', + '🇺🇦', + '🇺🇬', + '🇺🇲', + '🇺🇳', + '🇺🇸', + '🇺🇾', + '🇺🇿', + '🇻🇦', + '🇻🇨', + '🇻🇪', + '🇻🇬', + '🇻🇮', + '🇻🇳', + '🇻🇺', + '🇼🇫', + '🇼🇸', + '🇽🇰', + '🇾🇪', + '🇾🇹', + '🇿🇦', + '🇿🇲', + '🇿🇼' +]); diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart new file mode 100644 index 0000000000..cb06fe437a --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker.dart @@ -0,0 +1,292 @@ +// ignore_for_file: constant_identifier_names + +import 'dart:convert'; +import 'dart:io'; +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'models/category_models.dart'; +import 'config.dart'; +import 'default_emoji_picker_view.dart'; +import 'models/emoji_model.dart'; +import 'emoji_lists.dart' as emoji_list; +import 'emoji_view_state.dart'; +import 'models/recent_emoji_model.dart'; + +/// All the possible categories that [Emoji] can be put into +/// +/// All [Category] are shown in the category bar +enum Category { + /// Searched emojis + SEARCH, + + /// Recent emojis + RECENT, + + /// Smiley emojis + SMILEYS, + + /// Animal emojis + ANIMALS, + + /// Food emojis + FOODS, + + /// Activity emojis + ACTIVITIES, + + /// Travel emojis + TRAVEL, + + /// Ojects emojis + OBJECTS, + + /// Sumbol emojis + SYMBOLS, + + /// Flag emojis + FLAGS, +} + +/// Enum to alter the keyboard button style +enum ButtonMode { + /// Android button style - gives the button a splash color with ripple effect + MATERIAL, + + /// iOS button style - gives the button a fade out effect when pressed + CUPERTINO +} + +/// Callback function for when emoji is selected +/// +/// The function returns the selected [Emoji] as well +/// as the [Category] from which it originated +typedef OnEmojiSelected = void Function(Category category, Emoji emoji); + +/// Callback function for backspace button +typedef OnBackspacePressed = void Function(); + +/// Callback function for custom view +typedef EmojiViewBuilder = Widget Function(Config config, EmojiViewState state); + +/// The Emoji Keyboard widget +/// +/// This widget displays a grid of [Emoji] sorted by [Category] +/// which the user can horizontally scroll through. +/// +/// There is also a bottombar which displays all the possible [Category] +/// and allow the user to quickly switch to that [Category] +class EmojiPicker extends StatefulWidget { + /// EmojiPicker for flutter + const EmojiPicker({ + Key? key, + required this.onEmojiSelected, + this.onBackspacePressed, + this.config = const Config(), + this.customWidget, + }) : super(key: key); + + /// Custom widget + final EmojiViewBuilder? customWidget; + + /// The function called when the emoji is selected + final OnEmojiSelected onEmojiSelected; + + /// The function called when backspace button is pressed + final OnBackspacePressed? onBackspacePressed; + + /// Config for customizations + final Config config; + + @override + _EmojiPickerState createState() => _EmojiPickerState(); +} + +class _EmojiPickerState extends State { + static const platform = MethodChannel('emoji_picker_flutter'); + + List categoryEmoji = List.empty(growable: true); + List recentEmoji = List.empty(growable: true); + late Future updateEmojiFuture; + + // Prevent emojis to be reloaded with every build + bool loaded = false; + + @override + void initState() { + super.initState(); + updateEmojiFuture = _updateEmojis(); + } + + @override + void didUpdateWidget(covariant EmojiPicker oldWidget) { + if (oldWidget.config != widget.config) { + // Config changed - rebuild EmojiPickerView completely + loaded = false; + updateEmojiFuture = _updateEmojis(); + } + super.didUpdateWidget(oldWidget); + } + + @override + Widget build(BuildContext context) { + if (!loaded) { + // Load emojis + updateEmojiFuture.then( + (value) => WidgetsBinding.instance!.addPostFrameCallback((_) { + if (!mounted) return; + setState(() { + loaded = true; + }); + }), + ); + + // Show loading indicator + return const Center(child: CircularProgressIndicator()); + } + if (widget.config.showRecentsTab) { + categoryEmoji[0].emoji = recentEmoji.map((e) => e.emoji).toList().cast(); + } + + var state = EmojiViewState( + categoryEmoji, + _getOnEmojiListener(), + widget.onBackspacePressed, + ); + + // Build + return widget.customWidget == null + ? DefaultEmojiPickerView(widget.config, state) + : widget.customWidget!(widget.config, state); + } + + // Add recent emoji handling to tap listener + OnEmojiSelected _getOnEmojiListener() { + return (category, emoji) { + if (widget.config.showRecentsTab) { + _addEmojiToRecentlyUsed(emoji).then((value) { + if (category != Category.RECENT && mounted) { + setState(() { + // rebuild to update recent emoji tab + // when it is not current tab + }); + } + }); + } + widget.onEmojiSelected(category, emoji); + }; + } + + // Initalize emoji data + Future _updateEmojis() async { + categoryEmoji.clear(); + if (widget.config.showRecentsTab) { + recentEmoji = await _getRecentEmojis(); + final List recentEmojiMap = recentEmoji.map((e) => e.emoji).toList().cast(); + categoryEmoji.add(CategoryEmoji(Category.RECENT, recentEmojiMap)); + } + categoryEmoji.addAll([ + CategoryEmoji(Category.SMILEYS, await _getAvailableEmojis(emoji_list.smileys, title: 'smileys')), + CategoryEmoji(Category.ANIMALS, await _getAvailableEmojis(emoji_list.animals, title: 'animals')), + CategoryEmoji(Category.FOODS, await _getAvailableEmojis(emoji_list.foods, title: 'foods')), + CategoryEmoji(Category.ACTIVITIES, await _getAvailableEmojis(emoji_list.activities, title: 'activities')), + CategoryEmoji(Category.TRAVEL, await _getAvailableEmojis(emoji_list.travel, title: 'travel')), + CategoryEmoji(Category.OBJECTS, await _getAvailableEmojis(emoji_list.objects, title: 'objects')), + CategoryEmoji(Category.SYMBOLS, await _getAvailableEmojis(emoji_list.symbols, title: 'symbols')), + CategoryEmoji(Category.FLAGS, await _getAvailableEmojis(emoji_list.flags, title: 'flags')) + ]); + } + + // Get available emoji for given category title + Future> _getAvailableEmojis(Map map, {required String title}) async { + Map? newMap; + + // Get Emojis cached locally if available + newMap = await _restoreFilteredEmojis(title); + + if (newMap == null) { + // Check if emoji is available on this platform + newMap = await _getPlatformAvailableEmoji(map); + // Save available Emojis to local storage for faster loading next time + if (newMap != null) { + await _cacheFilteredEmojis(title, newMap); + } + } + + // Map to Emoji Object + return newMap!.entries.map((entry) => Emoji(entry.key, entry.value)).toList(); + } + + // Check if emoji is available on current platform + Future?> _getPlatformAvailableEmoji(Map emoji) async { + if (Platform.isAndroid) { + Map? filtered = {}; + var delimiter = '|'; + try { + var entries = emoji.values.join(delimiter); + var keys = emoji.keys.join(delimiter); + var result = (await platform + .invokeMethod('checkAvailability', {'emojiKeys': keys, 'emojiEntries': entries})) as String; + var resultKeys = result.split(delimiter); + for (var i = 0; i < resultKeys.length; i++) { + filtered[resultKeys[i]] = emoji[resultKeys[i]]!; + } + } on PlatformException catch (_) { + filtered = null; + } + return filtered; + } else { + return emoji; + } + } + + // Restore locally cached emoji + Future?> _restoreFilteredEmojis(String title) async { + final prefs = await SharedPreferences.getInstance(); + var emojiJson = prefs.getString(title); + if (emojiJson == null) { + return null; + } + var emojis = Map.from(jsonDecode(emojiJson) as Map); + return emojis; + } + + // Stores filtered emoji locally for faster access next time + Future _cacheFilteredEmojis(String title, Map emojis) async { + final prefs = await SharedPreferences.getInstance(); + var emojiJson = jsonEncode(emojis); + prefs.setString(title, emojiJson); + } + + // Returns list of recently used emoji from cache + Future> _getRecentEmojis() async { + final prefs = await SharedPreferences.getInstance(); + var emojiJson = prefs.getString('recent'); + if (emojiJson == null) { + return []; + } + var json = jsonDecode(emojiJson) as List; + return json.map(RecentEmoji.fromJson).toList(); + } + + // Add an emoji to recently used list or increase its counter + Future _addEmojiToRecentlyUsed(Emoji emoji) async { + final prefs = await SharedPreferences.getInstance(); + var recentEmojiIndex = recentEmoji.indexWhere((element) => element.emoji.emoji == emoji.emoji); + if (recentEmojiIndex != -1) { + // Already exist in recent list + // Just update counter + recentEmoji[recentEmojiIndex].counter++; + } else { + recentEmoji.add(RecentEmoji(emoji, 1)); + } + // Sort by counter desc + recentEmoji.sort((a, b) => b.counter - a.counter); + // Limit entries to recentsLimit + recentEmoji = recentEmoji.sublist(0, min(widget.config.recentsLimit, recentEmoji.length)); + // save locally + prefs.setString('recent', jsonEncode(recentEmoji)); + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker_builder.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker_builder.dart new file mode 100644 index 0000000000..07b6ca7efe --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_picker_builder.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; + +import 'config.dart'; +import 'emoji_view_state.dart'; + +/// Template class for custom implementation +/// Inhert this class to create your own EmojiPicker +abstract class EmojiPickerBuilder extends StatefulWidget { + /// Constructor + const EmojiPickerBuilder(this.config, this.state, {Key? key}) : super(key: key); + + /// Config for customizations + final Config config; + + /// State that holds current emoji data + final EmojiViewState state; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_view_state.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_view_state.dart new file mode 100644 index 0000000000..202f913715 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/emoji_view_state.dart @@ -0,0 +1,21 @@ +import 'models/category_models.dart'; +import 'emoji_picker.dart'; + +/// State that holds current emoji data +class EmojiViewState { + /// Constructor + EmojiViewState( + this.categoryEmoji, + this.onEmojiSelected, + this.onBackspacePressed, + ); + + /// List of all category including their emoji + final List categoryEmoji; + + /// Callback when pressed on emoji + final OnEmojiSelected onEmojiSelected; + + /// Callback when pressed on backspace + final OnBackspacePressed? onBackspacePressed; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/category_models.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/category_models.dart new file mode 100644 index 0000000000..8d59fa87e7 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/category_models.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; + +import 'emoji_model.dart'; +import '../emoji_picker.dart'; + +/// Container for Category and their emoji +class CategoryEmoji { + /// Constructor + CategoryEmoji(this.category, this.emoji); + + /// Category instance + final Category category; + + /// List of emoji of this category + List emoji; + + @override + String toString() { + return 'Name: $category, Emoji: $emoji'; + } +} + +/// Class that defines the icon representing a [Category] +class CategoryIcon { + /// Icon of Category + const CategoryIcon({ + required this.icon, + this.color = const Color.fromRGBO(211, 211, 211, 1), + this.selectedColor = const Color.fromRGBO(178, 178, 178, 1), + }); + + /// The icon to represent the category + final IconData icon; + + /// The default color of the icon + final Color color; + + /// The color of the icon once the category is selected + final Color selectedColor; +} + +/// Class used to define all the [CategoryIcon] shown for each [Category] +/// +/// This allows the keyboard to be personalized by changing icons shown. +/// If a [CategoryIcon] is set as null or not defined during initialization, +/// the default icons will be used instead +class CategoryIcons { + /// Constructor + const CategoryIcons({ + this.recentIcon = Icons.access_time, + this.smileyIcon = Icons.tag_faces, + this.animalIcon = Icons.pets, + this.foodIcon = Icons.fastfood, + this.activityIcon = Icons.directions_run, + this.travelIcon = Icons.location_city, + this.objectIcon = Icons.lightbulb_outline, + this.symbolIcon = Icons.emoji_symbols, + this.flagIcon = Icons.flag, + this.searchIcon = Icons.search, + }); + + /// Icon for [Category.RECENT] + final IconData recentIcon; + + /// Icon for [Category.SMILEYS] + final IconData smileyIcon; + + /// Icon for [Category.ANIMALS] + final IconData animalIcon; + + /// Icon for [Category.FOODS] + final IconData foodIcon; + + /// Icon for [Category.ACTIVITIES] + final IconData activityIcon; + + /// Icon for [Category.TRAVEL] + final IconData travelIcon; + + /// Icon for [Category.OBJECTS] + final IconData objectIcon; + + /// Icon for [Category.SYMBOLS] + final IconData symbolIcon; + + /// Icon for [Category.FLAGS] + final IconData flagIcon; + + /// Icon for [Category.SEARCH] + final IconData searchIcon; +} diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/emoji_model.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/emoji_model.dart new file mode 100644 index 0000000000..a1808a9419 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/emoji_model.dart @@ -0,0 +1,32 @@ +/// A class to store data for each individual emoji +class Emoji { + /// Emoji constructor + const Emoji(this.name, this.emoji); + + /// The name or description for this emoji + final String name; + + /// The unicode string for this emoji + /// + /// This is the string that should be displayed to view the emoji + final String emoji; + + @override + String toString() { + // return 'Name: $name, Emoji: $emoji'; + return name; + } + + /// Parse Emoji from json + static Emoji fromJson(Map json) { + return Emoji(json['name'] as String, json['emoji'] as String); + } + + /// Encode Emoji to json + Map toJson() { + return { + 'name': name, + 'emoji': emoji, + }; + } +} diff --git a/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/recent_emoji_model.dart b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/recent_emoji_model.dart new file mode 100644 index 0000000000..1571bc9a92 --- /dev/null +++ b/frontend/app_flowy/lib/workspace/presentation/widgets/emoji_picker/src/models/recent_emoji_model.dart @@ -0,0 +1,30 @@ +import 'emoji_model.dart'; + +/// Class that holds an recent emoji +/// Recent Emoji has an instance of the emoji +/// And a counter, which counts how often this emoji +/// has been used before +class RecentEmoji { + /// Constructor + RecentEmoji(this.emoji, this.counter); + + /// Emoji instance + final Emoji emoji; + + /// Counter how often emoji has been used before + int counter = 0; + + /// Parse RecentEmoji from json + static RecentEmoji fromJson(dynamic json) { + return RecentEmoji( + Emoji.fromJson(json['emoji'] as Map), + json['counter'] as int, + ); + } + + /// Encode RecentEmoji to json + Map toJson() => { + 'emoji': emoji, + 'counter': counter, + }; +}