Prechádzať zdrojové kódy

Merge pull request #376 from gaganyadav80/emoji_picker

feat: add emoji picker
Nathan.fooo 3 rokov pred
rodič
commit
959b2db9a3

+ 6 - 0
frontend/app_flowy/assets/images/editor/insert_emoticon.svg

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16px" height="16px" viewBox="0 0 16 16" version="1.1">
+<g id="surface1">
+<path style=" stroke:none;fill-rule:nonzero;fill:rgb(12.941176%,12.941176%,12.941176%);fill-opacity:1;" d="M 8 1.332031 C 11.683594 1.332031 14.667969 4.316406 14.667969 8 C 14.667969 11.683594 11.683594 14.667969 8 14.667969 C 4.316406 14.667969 1.332031 11.683594 1.332031 8 C 1.332031 4.316406 4.316406 1.332031 8 1.332031 Z M 8 2.332031 C 4.871094 2.332031 2.332031 4.871094 2.332031 8 C 2.332031 11.128906 4.871094 13.667969 8 13.667969 C 11.128906 13.667969 13.667969 11.128906 13.667969 8 C 13.667969 4.871094 11.128906 2.332031 8 2.332031 Z M 5.640625 9.855469 C 6.207031 10.574219 7.066406 11 8 11 C 8.929688 11 9.789062 10.574219 10.355469 9.859375 C 10.527344 9.640625 10.84375 9.605469 11.058594 9.777344 C 11.277344 9.949219 11.3125 10.261719 11.140625 10.476562 C 10.386719 11.433594 9.238281 12 8 12 C 6.757812 12 5.609375 11.429688 4.855469 10.476562 C 4.683594 10.257812 4.722656 9.945312 4.9375 9.773438 C 5.15625 9.601562 5.46875 9.640625 5.640625 9.855469 Z M 6 5.835938 C 6.460938 5.835938 6.832031 6.207031 6.832031 6.667969 C 6.832031 7.125 6.460938 7.5 6 7.5 C 5.539062 7.5 5.167969 7.125 5.167969 6.667969 C 5.167969 6.207031 5.539062 5.835938 6 5.835938 Z M 10 5.835938 C 10.460938 5.835938 10.832031 6.207031 10.832031 6.667969 C 10.832031 7.125 10.460938 7.5 10 7.5 C 9.539062 7.5 9.167969 7.125 9.167969 6.667969 C 9.167969 6.207031 9.539062 5.835938 10 5.835938 Z M 10 5.835938 "/>
+</g>
+</svg>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 3 - 0
frontend/app_flowy/assets/images/editor/insert_emoticon_2.svg


+ 6 - 0
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",
+        ),
       ],
     );
   }

+ 5 - 0
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';

+ 164 - 0
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;
+}

+ 276 - 0
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<DefaultEmojiPickerView> 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, <Emoji>[]);
+
+  @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<Widget>((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<Widget>((e) => _buildEmoji(emojiSize, categoryEmoji, e)).toList()
+              : categoryEmoji.emoji.map<Widget>((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,
+    ));
+  }
+}

+ 174 - 0
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<FlowyEmojiStyleButton> {
+  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<String, Attribute> 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<BuildEmojiPickerView> createState() => _BuildEmojiPickerViewState();
+}
+
+class _BuildEmojiPickerViewState extends State<BuildEmojiPickerView> {
+  @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);
+  }
+}

+ 3223 - 0
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<Map<String, String>> temp = [smileys, animals, foods, activities, travel, objects, symbols, flags];
+
+// final List<Emoji> emojiSearchList = temp
+//     .map((element) {
+//       return element.entries.map<Emoji>((entry) => Emoji(entry.key, entry.value)).toList();
+//     })
+//     .toList()
+//     .first;
+
+/// Map of all possible emojis along with their names in [Category.SMILEYS]
+final Map<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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 &amp; 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 &amp; 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 &amp; 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 &amp; South Sandwich Islands',
+  'Flag: Guatemala',
+  'Flag: Guam',
+  'Flag: Guinea-Bissau',
+  'Flag: Guyana',
+  'Flag: Hong Kong SAR China',
+  'Flag: Heard &amp; 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 &amp; 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 &amp; 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 &amp; Jan Mayen',
+  'Flag: Slovakia',
+  'Flag: Sierra Leone',
+  'Flag: San Marino',
+  'Flag: Senegal',
+  'Flag: Somalia',
+  'Flag: Suriname',
+  'Flag: South Sudan',
+  'Flag: São Tomé &amp; Príncipe',
+  'Flag: El Salvador',
+  'Flag: Sint Maarten',
+  'Flag: Syria',
+  'Flag: Swaziland',
+  'Flag: Tristan Da Cunha',
+  'Flag: Turks &amp; 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 &amp; 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 &amp; Grenadines',
+  'Flag: Venezuela',
+  'Flag: British Virgin Islands',
+  'Flag: U.S. Virgin Islands',
+  'Flag: Vietnam',
+  'Flag: Vanuatu',
+  'Flag: Wallis &amp; Futuna',
+  'Flag: Samoa',
+  'Flag: Kosovo',
+  'Flag: Yemen',
+  'Flag: Mayotte',
+  'Flag: South Africa',
+  'Flag: Zambia',
+  'Flag: Zimbabwe'
+], [
+  '🏁',
+  '🚩',
+  '🎌',
+  '🏴',
+  '🏳',
+  '🏳️‍🌈',
+  '🏴‍☠️',
+  '🇦🇨',
+  '🇦🇩',
+  '🇦🇪',
+  '🇦🇫',
+  '🇦🇬',
+  '🇦🇮',
+  '🇦🇱',
+  '🇦🇲',
+  '🇦🇴',
+  '🇦🇶',
+  '🇦🇷',
+  '🇦🇸',
+  '🇦🇹',
+  '🇦🇺',
+  '🇦🇼',
+  '🇦🇽',
+  '🇦🇿',
+  '🇧🇦',
+  '🇧🇧',
+  '🇧🇩',
+  '🇧🇪',
+  '🇧🇫',
+  '🇧🇬',
+  '🇧🇭',
+  '🇧🇮',
+  '🇧🇯',
+  '🇧🇱',
+  '🇧🇲',
+  '🇧🇳',
+  '🇧🇴',
+  '🇧🇶',
+  '🇧🇷',
+  '🇧🇸',
+  '🇧🇹',
+  '🇧🇻',
+  '🇧🇼',
+  '🇧🇾',
+  '🇧🇿',
+  '🇨🇦',
+  '🇨🇨',
+  '🇨🇩',
+  '🇨🇫',
+  '🇨🇬',
+  '🇨🇭',
+  '🇨🇮',
+  '🇨🇰',
+  '🇨🇱',
+  '🇨🇲',
+  '🇨🇳',
+  '🇨🇴',
+  '🇨🇵',
+  '🇨🇷',
+  '🇨🇺',
+  '🇨🇻',
+  '🇨🇼',
+  '🇨🇽',
+  '🇨🇾',
+  '🇨🇿',
+  '🇩🇪',
+  '🇩🇬',
+  '🇩🇯',
+  '🇩🇰',
+  '🇩🇲',
+  '🇩🇴',
+  '🇩🇿',
+  '🇪🇦',
+  '🇪🇨',
+  '🇪🇪',
+  '🇪🇬',
+  '🇪🇭',
+  '🇪🇷',
+  '🇪🇸',
+  '🇪🇹',
+  '🇪🇺',
+  '🇫🇮',
+  '🇫🇯',
+  '🇫🇰',
+  '🇫🇲',
+  '🇫🇴',
+  '🇫🇷',
+  '🇬🇦',
+  '🇬🇧',
+  '🇬🇩',
+  '🇬🇪',
+  '🇬🇫',
+  '🇬🇬',
+  '🇬🇭',
+  '🇬🇮',
+  '🇬🇱',
+  '🇬🇲',
+  '🇬🇳',
+  '🇬🇵',
+  '🇬🇶',
+  '🇬🇷',
+  '🇬🇸',
+  '🇬🇹',
+  '🇬🇺',
+  '🇬🇼',
+  '🇬🇾',
+  '🇭🇰',
+  '🇭🇲',
+  '🇭🇳',
+  '🇭🇷',
+  '🇭🇹',
+  '🇭🇺',
+  '🇮🇨',
+  '🇮🇩',
+  '🇮🇪',
+  '🇮🇱',
+  '🇮🇲',
+  '🇮🇳',
+  '🇮🇴',
+  '🇮🇶',
+  '🇮🇷',
+  '🇮🇸',
+  '🇮🇹',
+  '🇯🇪',
+  '🇯🇲',
+  '🇯🇴',
+  '🇯🇵',
+  '🇰🇪',
+  '🇰🇬',
+  '🇰🇭',
+  '🇰🇮',
+  '🇰🇲',
+  '🇰🇳',
+  '🇰🇵',
+  '🇰🇷',
+  '🇰🇼',
+  '🇰🇾',
+  '🇰🇿',
+  '🇱🇦',
+  '🇱🇧',
+  '🇱🇨',
+  '🇱🇮',
+  '🇱🇰',
+  '🇱🇷',
+  '🇱🇸',
+  '🇱🇹',
+  '🇱🇺',
+  '🇱🇻',
+  '🇱🇾',
+  '🇲🇦',
+  '🇲🇨',
+  '🇲🇩',
+  '🇲🇪',
+  '🇲🇫',
+  '🇲🇬',
+  '🇲🇭',
+  '🇲🇰',
+  '🇲🇱',
+  '🇲🇲',
+  '🇲🇳',
+  '🇲🇴',
+  '🇲🇵',
+  '🇲🇶',
+  '🇲🇷',
+  '🇲🇸',
+  '🇲🇹',
+  '🇲🇺',
+  '🇲🇻',
+  '🇲🇼',
+  '🇲🇽',
+  '🇲🇾',
+  '🇲🇿',
+  '🇳🇦',
+  '🇳🇨',
+  '🇳🇪',
+  '🇳🇫',
+  '🇳🇬',
+  '🇳🇮',
+  '🇳🇱',
+  '🇳🇴',
+  '🇳🇵',
+  '🇳🇷',
+  '🇳🇺',
+  '🇳🇿',
+  '🇴🇲',
+  '🇵🇦',
+  '🇵🇪',
+  '🇵🇫',
+  '🇵🇬',
+  '🇵🇭',
+  '🇵🇰',
+  '🇵🇱',
+  '🇵🇲',
+  '🇵🇳',
+  '🇵🇷',
+  '🇵🇸',
+  '🇵🇹',
+  '🇵🇼',
+  '🇵🇾',
+  '🇶🇦',
+  '🇷🇪',
+  '🇷🇴',
+  '🇷🇸',
+  '🇷🇺',
+  '🇷🇼',
+  '🇸🇦',
+  '🇸🇧',
+  '🇸🇨',
+  '🇸🇩',
+  '🇸🇪',
+  '🇸🇬',
+  '🇸🇭',
+  '🇸🇮',
+  '🇸🇯',
+  '🇸🇰',
+  '🇸🇱',
+  '🇸🇲',
+  '🇸🇳',
+  '🇸🇴',
+  '🇸🇷',
+  '🇸🇸',
+  '🇸🇹',
+  '🇸🇻',
+  '🇸🇽',
+  '🇸🇾',
+  '🇸🇿',
+  '🇹🇦',
+  '🇹🇨',
+  '🇹🇩',
+  '🇹🇫',
+  '🇹🇬',
+  '🇹🇭',
+  '🇹🇯',
+  '🇹🇰',
+  '🇹🇱',
+  '🇹🇲',
+  '🇹🇳',
+  '🇹🇴',
+  '🇹🇷',
+  '🇹🇹',
+  '🇹🇻',
+  '🇹🇼',
+  '🇹🇿',
+  '🇺🇦',
+  '🇺🇬',
+  '🇺🇲',
+  '🇺🇳',
+  '🇺🇸',
+  '🇺🇾',
+  '🇺🇿',
+  '🇻🇦',
+  '🇻🇨',
+  '🇻🇪',
+  '🇻🇬',
+  '🇻🇮',
+  '🇻🇳',
+  '🇻🇺',
+  '🇼🇫',
+  '🇼🇸',
+  '🇽🇰',
+  '🇾🇪',
+  '🇾🇹',
+  '🇿🇦',
+  '🇿🇲',
+  '🇿🇼'
+]);

+ 292 - 0
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<EmojiPicker> {
+  static const platform = MethodChannel('emoji_picker_flutter');
+
+  List<CategoryEmoji> categoryEmoji = List.empty(growable: true);
+  List<RecentEmoji> recentEmoji = List.empty(growable: true);
+  late Future<void> 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<Emoji>();
+    }
+
+    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<void> _updateEmojis() async {
+    categoryEmoji.clear();
+    if (widget.config.showRecentsTab) {
+      recentEmoji = await _getRecentEmojis();
+      final List<Emoji> recentEmojiMap = recentEmoji.map((e) => e.emoji).toList().cast<Emoji>();
+      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<List<Emoji>> _getAvailableEmojis(Map<String, String> map, {required String title}) async {
+    Map<String, String>? 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<Emoji>((entry) => Emoji(entry.key, entry.value)).toList();
+  }
+
+  // Check if emoji is available on current platform
+  Future<Map<String, String>?> _getPlatformAvailableEmoji(Map<String, String> emoji) async {
+    if (Platform.isAndroid) {
+      Map<String, String>? filtered = {};
+      var delimiter = '|';
+      try {
+        var entries = emoji.values.join(delimiter);
+        var keys = emoji.keys.join(delimiter);
+        var result = (await platform
+            .invokeMethod<String>('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<Map<String, String>?> _restoreFilteredEmojis(String title) async {
+    final prefs = await SharedPreferences.getInstance();
+    var emojiJson = prefs.getString(title);
+    if (emojiJson == null) {
+      return null;
+    }
+    var emojis = Map<String, String>.from(jsonDecode(emojiJson) as Map<String, dynamic>);
+    return emojis;
+  }
+
+  // Stores filtered emoji locally for faster access next time
+  Future<void> _cacheFilteredEmojis(String title, Map<String, String> emojis) async {
+    final prefs = await SharedPreferences.getInstance();
+    var emojiJson = jsonEncode(emojis);
+    prefs.setString(title, emojiJson);
+  }
+
+  // Returns list of recently used emoji from cache
+  Future<List<RecentEmoji>> _getRecentEmojis() async {
+    final prefs = await SharedPreferences.getInstance();
+    var emojiJson = prefs.getString('recent');
+    if (emojiJson == null) {
+      return [];
+    }
+    var json = jsonDecode(emojiJson) as List<dynamic>;
+    return json.map<RecentEmoji>(RecentEmoji.fromJson).toList();
+  }
+
+  // Add an emoji to recently used list or increase its counter
+  Future<void> _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));
+  }
+}

+ 17 - 0
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;
+}

+ 21 - 0
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> categoryEmoji;
+
+  /// Callback when pressed on emoji
+  final OnEmojiSelected onEmojiSelected;
+
+  /// Callback when pressed on backspace
+  final OnBackspacePressed? onBackspacePressed;
+}

+ 91 - 0
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> 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;
+}

+ 32 - 0
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<String, dynamic> json) {
+    return Emoji(json['name'] as String, json['emoji'] as String);
+  }
+
+  ///  Encode Emoji to json
+  Map<String, dynamic> toJson() {
+    return {
+      'name': name,
+      'emoji': emoji,
+    };
+  }
+}

+ 30 - 0
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<String, dynamic>),
+      json['counter'] as int,
+    );
+  }
+
+  /// Encode RecentEmoji to json
+  Map<String, dynamic> toJson() => {
+        'emoji': emoji,
+        'counter': counter,
+      };
+}

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov