Add dialog to set user status
authorFelix Weilbach <felix.weilbach@nextcloud.com>
Thu, 9 Sep 2021 09:18:22 +0000 (11:18 +0200)
committerFelix Weilbach <felix.weilbach@nextcloud.com>
Thu, 9 Sep 2021 09:18:22 +0000 (11:18 +0200)
Signed-off-by: Felix Weilbach <felix.weilbach@nextcloud.com>
42 files changed:
resources.qrc
src/gui/CMakeLists.txt
src/gui/EmojiPicker.qml [new file with mode: 0644]
src/gui/ErrorBox.qml [new file with mode: 0644]
src/gui/UserStatusSelector.qml [new file with mode: 0644]
src/gui/UserStatusSelectorDialog.qml [new file with mode: 0644]
src/gui/accountstate.cpp
src/gui/accountstate.h
src/gui/emojimodel.cpp [new file with mode: 0644]
src/gui/emojimodel.h [new file with mode: 0644]
src/gui/main.cpp
src/gui/socketapi/socketapi.cpp
src/gui/tray/UserLine.qml
src/gui/tray/UserModel.cpp
src/gui/tray/UserModel.h
src/gui/tray/Window.qml
src/gui/userstatus.cpp [deleted file]
src/gui/userstatus.h [deleted file]
src/gui/userstatusselectormodel.cpp [new file with mode: 0644]
src/gui/userstatusselectormodel.h [new file with mode: 0644]
src/libsync/CMakeLists.txt
src/libsync/abstractnetworkjob.cpp
src/libsync/abstractnetworkjob.h
src/libsync/account.cpp
src/libsync/account.h
src/libsync/capabilities.cpp
src/libsync/capabilities.h
src/libsync/clientsideencryption.cpp
src/libsync/clientsideencryption.h
src/libsync/clientsideencryptionjobs.cpp
src/libsync/datetimeprovider.cpp [new file with mode: 0644]
src/libsync/datetimeprovider.h [new file with mode: 0644]
src/libsync/networkjobs.cpp
src/libsync/networkjobs.h
src/libsync/ocsuserstatusconnector.cpp [new file with mode: 0644]
src/libsync/ocsuserstatusconnector.h [new file with mode: 0644]
src/libsync/userstatusconnector.cpp [new file with mode: 0644]
src/libsync/userstatusconnector.h [new file with mode: 0644]
test/CMakeLists.txt
test/testcapabilities.cpp
test/testpushnotifications.cpp
test/testsetuserstatusdialog.cpp [new file with mode: 0644]

index 9fa393c4efe6d15f79714060e2e8736068f77ac6..f811db0f79e60940b24b10327dd4c8c3f22a2f71 100644 (file)
@@ -1,5 +1,9 @@
 <RCC>
     <qresource prefix="/qml">
+        <file>src/gui/UserStatusSelector.qml</file>
+        <file>src/gui/UserStatusSelectorDialog.qml</file>
+        <file>src/gui/EmojiPicker.qml</file>
+        <file>src/gui/ErrorBox.qml</file>
         <file>src/gui/tray/Window.qml</file>
         <file>src/gui/tray/UserLine.qml</file>
         <file>src/gui/tray/HeaderButton.qml</file>
index 67a2c73234cd42729056cb26e5ed560554521257..74c460d066336205e3f14a9d84bfd7f5371485c0 100644 (file)
@@ -35,6 +35,8 @@ set(client_UI_SRCS
     addcertificatedialog.ui
     proxyauthdialog.ui
     mnemonicdialog.ui
+    UserStatusSelector.qml
+    UserStatusSelectorDialog.qml
     tray/ActivityActionButton.qml
     tray/ActivityItem.qml
     tray/Window.qml
@@ -92,7 +94,6 @@ set(client_SRCS
     systray.cpp
     thumbnailjob.cpp
     userinfo.cpp
-    userstatus.cpp
     accountstate.cpp
     addcertificatedialog.cpp
     authenticationdialog.cpp
@@ -106,6 +107,8 @@ set(client_SRCS
     iconjob.cpp
     iconutils.cpp
     remotewipe.cpp
+    userstatusselectormodel.cpp
+    emojimodel.cpp
     tray/ActivityData.cpp
     tray/ActivityListModel.cpp
     tray/UserModel.cpp
diff --git a/src/gui/EmojiPicker.qml b/src/gui/EmojiPicker.qml
new file mode 100644 (file)
index 0000000..6a66dbd
--- /dev/null
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+import QtQuick 2.15
+import QtQuick.Controls 2.15
+import QtQuick.Layouts 1.15
+
+import com.nextcloud.desktopclient 1.0 as NC
+
+ColumnLayout {
+    NC.EmojiModel {
+        id: emojiModel
+    }
+
+    signal chosen(string emoji)
+
+    spacing: 0
+
+    FontMetrics {
+        id: metrics
+    }
+
+    ListView {
+        id: headerLayout
+        Layout.fillWidth: true
+        implicitWidth: contentItem.childrenRect.width
+        implicitHeight: metrics.height * 2
+
+        orientation: ListView.Horizontal
+
+        model: emojiModel.emojiCategoriesModel
+
+        delegate: ItemDelegate {
+            width: metrics.height * 2
+            height: headerLayout.height
+
+            contentItem: Text {
+                horizontalAlignment: Text.AlignHCenter
+                verticalAlignment: Text.AlignVCenter
+                text: emoji
+            }
+
+            Rectangle {
+                anchors.bottom: parent.bottom
+
+                width: parent.width
+                height: 2
+
+                visible: ListView.isCurrentItem
+
+                color: "grey"
+            }
+
+
+            onClicked: {
+                emojiModel.setCategory(label)
+            }
+        }
+
+    }
+
+    Rectangle {
+        height: 1
+        Layout.fillWidth: true
+        color: "grey"
+    }
+
+    GridView {
+        Layout.fillWidth: true
+        Layout.fillHeight: true
+        Layout.preferredHeight: metrics.height * 8
+
+        cellWidth: metrics.height * 2
+        cellHeight: metrics.height * 2
+
+        boundsBehavior: Flickable.DragOverBounds
+        clip: true
+
+        model: emojiModel.model
+
+        delegate: ItemDelegate {
+
+            width: metrics.height * 2
+            height: metrics.height * 2
+
+            contentItem: Text {
+                anchors.centerIn: parent
+                text: modelData === undefined ? "" : modelData.unicode
+            }
+
+            onClicked: {
+                chosen(modelData.unicode);
+                emojiModel.emojiUsed(modelData);
+            }
+        }
+
+        ScrollBar.vertical: ScrollBar {}
+        
+    }
+
+}
diff --git a/src/gui/ErrorBox.qml b/src/gui/ErrorBox.qml
new file mode 100644 (file)
index 0000000..5b0d102
--- /dev/null
@@ -0,0 +1,26 @@
+import QtQuick 2.15
+
+Item {
+    id: errorBox
+    
+    property var text: ""
+    
+    implicitHeight: errorMessage.implicitHeight + 2 * 8
+
+    Rectangle {
+        anchors.fill: parent
+        color: "red"
+        border.color: "black"
+    }
+
+    Text {
+        id: errorMessage
+        
+        anchors.fill: parent
+        anchors.margins: 8
+        width: parent.width
+        color: "white"
+        wrapMode: Text.WordWrap
+        text: errorBox.text
+    }
+}
diff --git a/src/gui/UserStatusSelector.qml b/src/gui/UserStatusSelector.qml
new file mode 100644 (file)
index 0000000..c879df4
--- /dev/null
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+import QtQuick 2.6
+import QtQuick.Dialogs 1.3
+import QtQuick.Layouts 1.15
+import QtQuick.Controls 2.15
+import QtQuick.Window 2.15
+import com.nextcloud.desktopclient 1.0 as NC
+
+ColumnLayout {
+    id: rootLayout
+    spacing: 0
+    property NC.UserStatusSelectorModel userStatusSelectorModel
+
+    FontMetrics {
+        id: metrics
+    }
+
+    Text {
+        Layout.topMargin: 16
+        Layout.leftMargin: 8
+        Layout.rightMargin: 8
+        Layout.bottomMargin: 8
+        Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
+        font.bold: true
+        text: qsTr("Online status")
+    }
+        
+    GridLayout {
+        Layout.margins: 8
+        Layout.alignment: Qt.AlignTop
+        columns: 2
+        rows: 2
+        columnSpacing: 8
+        rowSpacing: 8
+
+        Button {
+            Layout.fillWidth: true
+            checked: NC.UserStatus.Online == userStatusSelectorModel.onlineStatus
+            checkable: true
+            icon.source: userStatusSelectorModel.onlineIcon
+            icon.color: "transparent"
+            text: qsTr("Online")
+            onClicked: userStatusSelectorModel.setOnlineStatus(NC.UserStatus.Online)
+            implicitWidth: 100
+        }
+        Button {
+            Layout.fillWidth: true
+            checked: NC.UserStatus.Away == userStatusSelectorModel.onlineStatus
+            checkable: true
+            icon.source: userStatusSelectorModel.awayIcon
+            icon.color: "transparent"
+            text: qsTr("Away")
+            onClicked: userStatusSelectorModel.setOnlineStatus(NC.UserStatus.Away)
+            implicitWidth: 100
+            
+        }
+        Button {
+            Layout.fillWidth: true
+            checked: NC.UserStatus.DoNotDisturb == userStatusSelectorModel.onlineStatus
+            checkable: true
+            icon.source: userStatusSelectorModel.dndIcon
+            icon.color: "transparent"
+            text: qsTr("Do not disturb")
+            onClicked: userStatusSelectorModel.setOnlineStatus(NC.UserStatus.DoNotDisturb)
+            implicitWidth: 100
+        }
+        Button {
+            Layout.fillWidth: true
+            checked: NC.UserStatus.Invisible == userStatusSelectorModel.onlineStatus
+            checkable: true
+            icon.source: userStatusSelectorModel.invisibleIcon
+            icon.color: "transparent"
+            text: qsTr("Invisible")
+            onClicked: userStatusSelectorModel.setOnlineStatus(NC.UserStatus.Invisible)
+            implicitWidth: 100
+        }
+    }
+
+    Text {
+        Layout.topMargin: 16
+        Layout.leftMargin: 8
+        Layout.rightMargin: 8
+        Layout.bottomMargin: 8
+        Layout.alignment: Qt.AlignTop | Qt.AlignHCenter
+        font.bold: true
+        text: qsTr("Status message")
+    }
+
+    RowLayout {
+        Layout.topMargin: 8
+        Layout.leftMargin: 8
+        Layout.rightMargin: 8
+        Layout.bottomMargin: 16
+        Layout.alignment: Qt.AlignTop
+        Layout.fillWidth: true
+
+        Button {
+            Layout.preferredWidth: userStatusMessageTextField.height // metrics.height * 2
+            Layout.preferredHeight: userStatusMessageTextField.height // metrics.height * 2
+            text: userStatusSelectorModel.userStatusEmoji
+            onClicked: emojiDialog.open()
+        }
+
+        Popup {
+            id: emojiDialog
+            padding: 0
+            margins: 0
+
+            anchors.centerIn: Overlay.overlay
+            
+            EmojiPicker {
+                id: emojiPicker
+
+                onChosen: {
+                    userStatusSelectorModel.userStatusEmoji = emoji
+                    emojiDialog.close()
+                }
+            }
+        }
+
+        TextField {
+            id: userStatusMessageTextField
+            Layout.fillWidth: true
+            placeholderText: qsTr("What is your Status?")
+            text: userStatusSelectorModel.userStatusMessage
+            onEditingFinished: userStatusSelectorModel.setUserStatusMessage(text)
+        }
+    }
+
+    Repeater {
+        model: userStatusSelectorModel.predefinedStatusesCount
+
+        Button {
+            id: control
+            Layout.fillWidth: true
+            flat: !hovered
+            hoverEnabled: true
+            text: userStatusSelectorModel.predefinedStatus(index).icon + " <b>" + userStatusSelectorModel.predefinedStatus(index).message + "</b> - " + userStatusSelectorModel.predefinedStatusClearAt(index)
+            onClicked: userStatusSelectorModel.setPredefinedStatus(index)
+        }
+    }
+
+   RowLayout {
+       Layout.topMargin: 16
+       Layout.leftMargin: 8
+       Layout.rightMargin: 8
+       Layout.bottomMargin: 8
+       Layout.alignment: Qt.AlignTop
+
+       Text {
+           text: qsTr("Clear status message after")
+       }
+
+       ComboBox {
+           Layout.fillWidth: true
+           model: userStatusSelectorModel.clearAtValues
+           displayText: userStatusSelectorModel.clearAt
+           onActivated: userStatusSelectorModel.setClearAt(index)
+       }
+   }
+
+    RowLayout {
+        Layout.margins: 8
+        Layout.alignment: Qt.AlignTop
+        
+        Button {
+            Layout.fillWidth: true
+            text: qsTr("Clear status message")
+            onClicked: userStatusSelectorModel.clearUserStatus()
+        }
+        Button {
+            highlighted: true
+            Layout.fillWidth: true
+            text: qsTr("Set status message")
+            onClicked: userStatusSelectorModel.setUserStatus()
+        }
+    }
+
+    ErrorBox {
+        Layout.margins: 8
+        Layout.fillWidth: true
+        
+        visible: userStatusSelectorModel.errorMessage != ""
+        text: "<b>Error:</b> " + userStatusSelectorModel.errorMessage
+    }
+}
diff --git a/src/gui/UserStatusSelectorDialog.qml b/src/gui/UserStatusSelectorDialog.qml
new file mode 100644 (file)
index 0000000..624b24f
--- /dev/null
@@ -0,0 +1,29 @@
+import QtQuick.Window 2.15
+
+import com.nextcloud.desktopclient 1.0 as NC
+
+Window {
+    id: dialog
+    
+    property NC.UserStatusSelectorModel model: NC.UserStatusSelectorModel {
+        onFinished: {
+            dialog.close()
+        }
+    }
+
+    width: view.implicitWidth
+    height: view.implicitHeight
+    minimumWidth: view.implicitWidth
+    minimumHeight: view.implicitHeight
+    maximumWidth: view.implicitWidth
+    maximumHeight: view.implicitHeight
+
+    visible: true
+
+    flags: Qt.Dialog
+    
+    UserStatusSelector {
+        id: view
+        userStatusSelectorModel: model
+    }
+}
index bb2ab52738b2fc8096e03ddb043649c9a4e4027e..7521dac799a7619c3687d90ed114ab4b66b530fd 100644 (file)
@@ -44,7 +44,6 @@ AccountState::AccountState(AccountPtr account)
     , _waitingForNewCredentials(false)
     , _maintenanceToConnectedDelay(60000 + (qrand() % (4 * 60000))) // 1-5min delay
     , _remoteWipe(new RemoteWipe(_account))
-    , _userStatus(new UserStatus(this))
     , _isDesktopNotificationsAllowed(true)
 {
     qRegisterMetaType<AccountState *>("AccountState*");
@@ -127,26 +126,6 @@ void AccountState::setState(State state)
     emit stateChanged(_state);
 }
 
-UserStatus::Status AccountState::status() const
-{
-    return _userStatus->status();
-}
-
-QString AccountState::statusMessage() const
-{
-    return _userStatus->message();
-}
-
-QUrl AccountState::statusIcon() const
-{
-    return _userStatus->icon();
-}
-
-QString AccountState::statusEmoji() const
-{
-    return _userStatus->emoji();
-}
-
 QString AccountState::stateString(State state)
 {
     switch (state) {
@@ -462,12 +441,6 @@ void AccountState::fetchNavigationApps(){
     job->getNavigationApps();
 }
 
-void AccountState::fetchUserStatus() 
-{
-    connect(_userStatus, &UserStatus::fetchUserStatusFinished, this, &AccountState::statusChanged);
-    _userStatus->fetchUserStatus(_account);
-}
-
 void AccountState::slotEtagResponseHeaderReceived(const QByteArray &value, int statusCode){
     if(statusCode == 200){
         qCDebug(lcAccountState) << "New navigation apps ETag Response Header received " << value;
index 74e41ad6a4fac94b6cd9dfa6148a34584670be23..7946e2f969f3145c2e80089fe73d51d58b2c31f9 100644 (file)
@@ -21,7 +21,7 @@
 #include <QPointer>
 #include "connectionvalidator.h"
 #include "creds/abstractcredentials.h"
-#include "userstatus.h"
+
 #include <memory>
 
 class QSettings;
@@ -162,23 +162,6 @@ public:
     ///Asks for user credentials
     void handleInvalidCredentials();
 
-    /** Returns the user status (Online, Dnd, Away, Offline, Invisible)
-     *  https://gist.github.com/georgehrke/55a0412007f13be1551d1f9436a39675
-    */
-    UserStatus::Status status() const;
-
-    /** Returns the user status Message (text)
-    */
-    QString statusMessage() const;
-
-    /** Returns the user status icon url
-    */
-    QUrl statusIcon() const;
-    
-    /** Returns the user status emoji
-    */
-    QString statusEmoji() const;
-
     /** Returns the notifications status retrieved by the notificatons endpoint
      *  https://github.com/nextcloud/desktop/issues/2318#issuecomment-680698429
     */
@@ -188,10 +171,6 @@ public:
     */
     void setDesktopNotificationsAllowed(bool isAllowed);
 
-    /** Fetch the user status (status, icon, message)
-    */
-    void fetchUserStatus();
-
 public slots:
     /// Triggers a ping to the server to update state and
     /// connection status and errors.
@@ -256,7 +235,6 @@ private:
      */
     AccountAppList _apps;
 
-    UserStatus *_userStatus;
     bool _isDesktopNotificationsAllowed;
 };
 
diff --git a/src/gui/emojimodel.cpp b/src/gui/emojimodel.cpp
new file mode 100644 (file)
index 0000000..e6b281d
--- /dev/null
@@ -0,0 +1,1571 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include <QtGlobal>
+#include <memory>
+
+#include "emojimodel.h"
+
+namespace OCC {
+
+QVariant EmojiCategoriesModel::data(const QModelIndex &index, int role) const
+{
+    if (!index.isValid()) {
+        return {};
+    }
+
+    switch (role) {
+    case Roles::EmojiRole:
+        return categories[index.row()].emoji;
+
+    case Roles::LabelRole:
+        return categories[index.row()].label;
+    }
+
+    return {};
+}
+
+int EmojiCategoriesModel::rowCount(const QModelIndex &parent) const
+{
+    Q_UNUSED(parent);
+    return static_cast<int>(categories.size());
+}
+
+QHash<int, QByteArray> EmojiCategoriesModel::roleNames() const
+{
+    QHash<int, QByteArray> roles;
+    roles[Roles::EmojiRole] = "emoji";
+    roles[Roles::LabelRole] = "label";
+    return roles;
+}
+
+const std::vector<EmojiCategoriesModel::Category> EmojiCategoriesModel::categories = {
+    { "⌛️", "history" },
+    { "😏", "people" },
+    { "🌲", "nature" },
+    { "🍛", "food" },
+    { "🚁", "activity" },
+    { "🚅", "travel" },
+    { "💡", "objects" },
+    { "🔣", "symbols" },
+    { "🏁", "flags" },
+};
+
+QVariantList EmojiModel::history() const
+{
+    return _settings.value("Editor/emojis", QVariantList()).toList();
+}
+
+void EmojiModel::setCategory(const QString &category)
+{
+    if (_category == category) {
+        return;
+    }
+    _category = category;
+    emit modelChanged();
+}
+
+QAbstractListModel *EmojiModel::emojiCategoriesModel()
+{
+    return &_emojiCategoriesModel;
+}
+
+QVariantList EmojiModel::model() const
+{
+    if (_category == "history") {
+        return history();
+    } else if (_category == "people") {
+        return people;
+    } else if (_category == "nature") {
+        return nature;
+    } else if (_category == "food") {
+        return food;
+    } else if (_category == "activity") {
+        return activity;
+    } else if (_category == "travel") {
+        return travel;
+    } else if (_category == "objects") {
+        return objects;
+    } else if (_category == "symbols") {
+        return symbols;
+    } else if (_category == "flags") {
+        return flags;
+    }
+    return history();
+}
+
+void EmojiModel::emojiUsed(const QVariant &modelData)
+{
+    auto historyEmojis = history();
+
+    auto historyEmojisIter = historyEmojis.begin();
+    while (historyEmojisIter != historyEmojis.end()) {
+        if ((*historyEmojisIter).value<Emoji>().unicode == modelData.value<Emoji>().unicode) {
+            historyEmojisIter = historyEmojis.erase(historyEmojisIter);
+        } else {
+            historyEmojisIter++;
+        }
+    }
+
+    historyEmojis.push_front(modelData);
+    _settings.setValue("Editor/emojis", historyEmojis);
+
+    emit historyChanged();
+}
+
+const QVariantList EmojiModel::people = {
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x80"), ":grinning:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x81"), ":grin:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x82"), ":joy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xa3"), ":rofl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x83"), ":smiley:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x84"), ":smile:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x85"), ":sweat_smile:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x86"), ":laughing:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x89"), ":wink:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x8a"), ":blush:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x8b"), ":yum:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x8e"), ":sunglasses:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x8d"), ":heart_eyes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x98"), ":kissing_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x97"), ":kissing:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x99"), ":kissing_smiling_eyes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x9a"), ":kissing_closed_eyes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xba"), ":relaxed:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x82"), ":slight_smile:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x97"), ":hugging:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x94"), ":thinking:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x90"), ":neutral_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x91"), ":expressionless:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb6"), ":no_mouth:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x84"), ":rolling_eyes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x8f"), ":smirk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa3"), ":persevere:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa5"), ":disappointed_relieved:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xae"), ":open_mouth:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x90"), ":zipper_mouth:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xaf"), ":hushed:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xaa"), ":sleepy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xab"), ":tired_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb4"), ":sleeping:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x8c"), ":relieved:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x93"), ":nerd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x9b"), ":stuck_out_tongue:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x9c"), ":stuck_out_tongue_winking_eye:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x9d"), ":stuck_out_tongue_closed_eyes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xa4"), ":drooling_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x92"), ":unamused:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x93"), ":sweat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x94"), ":pensive:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x95"), ":confused:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x83"), ":upside_down:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x91"), ":money_mouth:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb2"), ":astonished:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xb9"), ":frowning2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x81"), ":slight_frown:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x96"), ":confounded:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x9e"), ":disappointed:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x9f"), ":worried:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa4"), ":triumph:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa2"), ":cry:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xad"), ":sob:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa6"), ":frowning:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa7"), ":anguished:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa8"), ":fearful:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa9"), ":weary:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xac"), ":grimacing:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb0"), ":cold_sweat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb1"), ":scream:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb3"), ":flushed:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb5"), ":dizzy_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa1"), ":rage:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xa0"), ":angry:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x87"), ":innocent:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xa0"), ":cowboy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xa1"), ":clown:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xa5"), ":lying_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb7"), ":mask:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x92"), ":thermometer_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x95"), ":head_bandage:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xa2"), ":nauseated_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xa7"), ":sneezing_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\x88"), ":smiling_imp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xbf"), ":imp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb9"), ":japanese_ogre:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xba"), ":japanese_goblin:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x80"), ":skull:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xbb"), ":ghost:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xbd"), ":alien:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x96"), ":robot:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa9"), ":poop:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xba"), ":smiley_cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb8"), ":smile_cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xb9"), ":joy_cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xbb"), ":heart_eyes_cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xbc"), ":smirk_cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xbd"), ":kissing_cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x80"), ":scream_cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xbf"), ":crying_cat_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x98\xbe"), ":pouting_cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa6"), ":boy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa7"), ":girl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa8"), ":man:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa9"), ":woman:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb4"), ":older_man:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb5"), ":older_woman:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb6"), ":baby:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xbc"), ":angel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xae"), ":cop:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xb5"), ":spy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x82"), ":guardsman:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb7"), ":construction_worker:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb3"), ":man_with_turban:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb1"), ":person_with_blond_hair:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x85"), ":santa:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb6"), ":mrs_claus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb8"), ":princess:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb4"), ":prince:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb0"), ":bride_with_veil:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb5"), ":man_in_tuxedo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb0"), ":pregnant_woman:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xb2"), ":man_with_gua_pi_mao:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x8d"), ":person_frowning:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x8e"), ":person_with_pouting_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x85"), ":no_good:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x86"), ":ok_woman:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x81"), ":information_desk_person:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x8b"), ":raising_hand:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x87"), ":bow:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xa6"), ":face_palm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb7"), ":shrug:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x86"), ":massage:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x87"), ":haircut:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb6"), ":walking:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x83"), ":runner:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x83"), ":dancer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xba"), ":man_dancing:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xaf"), ":dancers:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xa3"), ":speaking_head:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa4"), ":bust_in_silhouette:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa5"), ":busts_in_silhouette:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xab"), ":couple:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xac"), ":two_men_holding_hands:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xad"), ":two_women_holding_hands:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x8f"), ":couplekiss:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x91"), ":couple_with_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xaa"), ":family:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xaa"), ":muscle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb3"), ":selfie:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x88"), ":point_left:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x89"), ":point_right:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x9d"), ":point_up:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x86"), ":point_up_2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\x95"), ":middle_finger:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x87"), ":point_down:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x8c"), ":v:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x9e"), ":fingers_crossed:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\x96"), ":vulcan:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x98"), ":metal:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x99"), ":call_me:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\x90"), ":hand_splayed:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x8b"), ":raised_hand:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x8c"), ":ok_hand:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x8d"), ":thumbsup:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x8e"), ":thumbsdown:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x8a"), ":fist:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x8a"), ":punch:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x9b"), ":left_facing_fist:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x9c"), ":right_facing_fist:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x9a"), ":raised_back_of_hand:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x8b"), ":wave:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x8f"), ":clap:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x8d"), ":writing_hand:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x90"), ":open_hands:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x8c"), ":raised_hands:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x8f"), ":pray:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\x9d"), ":handshake:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x85"), ":nail_care:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x82"), ":ear:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x83"), ":nose:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa3"), ":footprints:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x80"), ":eyes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x81"), ":eye:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x85"), ":tongue:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x84"), ":lips:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x8b"), ":kiss:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa4"), ":zzz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x93"), ":eyeglasses:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xb6"), ":dark_sunglasses:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x94"), ":necktie:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x95"), ":shirt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x96"), ":jeans:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x97"), ":dress:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x98"), ":kimono:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x99"), ":bikini:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x9a"), ":womans_clothes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x9b"), ":purse:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x9c"), ":handbag:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x9d"), ":pouch:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x92"), ":school_satchel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x9e"), ":mans_shoe:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x9f"), ":athletic_shoe:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa0"), ":high_heel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa1"), ":sandal:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xa2"), ":boot:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x91"), ":crown:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x92"), ":womans_hat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa9"), ":tophat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x93"), ":mortar_board:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\x91"), ":helmet_with_cross:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x84"), ":lipstick:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x8d"), ":ring:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x82"), ":closed_umbrella:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xbc"), ":briefcase:" }),
+};
+
+const QVariantList EmojiModel::nature = {
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x88"), ":see_no_evil:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x89"), ":hear_no_evil:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x99\x8a"), ":speak_no_evil:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa6"), ":sweat_drops:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa8"), ":dash:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb5"), ":monkey_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x92"), ":monkey:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x8d"), ":gorilla:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb6"), ":dog:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x95"), ":dog2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa9"), ":poodle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xba"), ":wolf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x8a"), ":fox:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb1"), ":cat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x88"), ":cat2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x81"), ":lion_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xaf"), ":tiger:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x85"), ":tiger2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x86"), ":leopard:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb4"), ":horse:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x8e"), ":racehorse:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x8c"), ":deer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x84"), ":unicorn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xae"), ":cow:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x82"), ":ox:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x83"), ":water_buffalo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x84"), ":cow2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb7"), ":pig:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x96"), ":pig2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x97"), ":boar:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xbd"), ":pig_nose:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x8f"), ":ram:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x91"), ":sheep:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x90"), ":goat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xaa"), ":dromedary_camel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xab"), ":camel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x98"), ":elephant:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x8f"), ":rhino:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xad"), ":mouse:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x81"), ":mouse2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x80"), ":rat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb9"), ":hamster:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb0"), ":rabbit:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x87"), ":rabbit2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xbf"), ":chipmunk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x87"), ":bat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xbb"), ":bear:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa8"), ":koala:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xbc"), ":panda_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xbe"), ":feet:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x83"), ":turkey:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x94"), ":chicken:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x93"), ":rooster:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa3"), ":hatching_chick:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa4"), ":baby_chick:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa5"), ":hatched_chick:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa6"), ":bird:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa7"), ":penguin:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x8a"), ":dove:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x85"), ":eagle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x86"), ":duck:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x89"), ":owl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb8"), ":frog:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x8a"), ":crocodile:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa2"), ":turtle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x8e"), ":lizard:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x8d"), ":snake:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb2"), ":dragon_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x89"), ":dragon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xb3"), ":whale:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x8b"), ":whale2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xac"), ":dolphin:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x9f"), ":fish:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa0"), ":tropical_fish:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\xa1"), ":blowfish:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x88"), ":shark:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x99"), ":octopus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x9a"), ":shell:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x80"), ":crab:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x90"), ":shrimp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x91"), ":squid:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x8b"), ":butterfly:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x8c"), ":snail:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x9b"), ":bug:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x9c"), ":ant:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x9d"), ":bee:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x90\x9e"), ":beetle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xb7"), ":spider:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xb8"), ":spider_web:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa6\x82"), ":scorpion:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x90"), ":bouquet:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb8"), ":cherry_blossom:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xb5"), ":rosette:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb9"), ":rose:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x80"), ":wilted_rose:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xba"), ":hibiscus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xbb"), ":sunflower:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xbc"), ":blossom:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb7"), ":tulip:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb1"), ":seedling:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb2"), ":evergreen_tree:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb3"), ":deciduous_tree:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb4"), ":palm_tree:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb5"), ":cactus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xbe"), ":ear_of_rice:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xbf"), ":herb:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x98"), ":shamrock:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x80"), ":four_leaf_clover:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x81"), ":maple_leaf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x82"), ":fallen_leaf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x83"), ":leaves:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x84"), ":mushroom:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb0"), ":chestnut:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x8d"), ":earth_africa:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x8e"), ":earth_americas:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x8f"), ":earth_asia:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x91"), ":new_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x92"), ":waxing_crescent_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x93"), ":first_quarter_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x94"), ":waxing_gibbous_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x95"), ":full_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x96"), ":waning_gibbous_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x97"), ":last_quarter_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x98"), ":waning_crescent_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x99"), ":crescent_moon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x9a"), ":new_moon_with_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x9b"), ":first_quarter_moon_with_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x9c"), ":last_quarter_moon_with_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x80"), ":sunny:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x9d"), ":full_moon_with_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x9e"), ":sun_with_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xad\x90"), ":star:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x9f"), ":star2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x81"), ":cloud:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\x85"), ":partly_sunny:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\x88"), ":thunder_cloud_rain:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xa4"), ":white_sun_small_cloud:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xa5"), ":white_sun_cloud:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xa6"), ":white_sun_rain_cloud:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xa7"), ":cloud_rain:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xa8"), ":cloud_snow:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xa9"), ":cloud_lightning:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xaa"), ":cloud_tornado:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xab"), ":fog:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xac"), ":wind_blowing_face:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x82"), ":umbrella2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x94"), ":umbrella:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\xa1"), ":zap:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\x84"), ":snowflake:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x83"), ":snowman2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\x84"), ":snowman:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x84"), ":comet:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa5"), ":fire:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa7"), ":droplet:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x8a"), ":ocean:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x83"), ":jack_o_lantern:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x84"), ":christmas_tree:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\xa8"), ":sparkles:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x8b"), ":tanabata_tree:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x8d"), ":bamboo:" }),
+};
+
+const QVariantList EmojiModel::food = {
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x87"), ":grapes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x88"), ":melon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x89"), ":watermelon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x8a"), ":tangerine:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x8b"), ":lemon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x8c"), ":banana:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x8d"), ":pineapple:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x8e"), ":apple:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x8f"), ":green_apple:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x90"), ":pear:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x91"), ":peach:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x92"), ":cherries:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x93"), ":strawberry:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x9d"), ":kiwi:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x85"), ":tomato:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x91"), ":avocado:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x86"), ":eggplant:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x94"), ":potato:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x95"), ":carrot:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xbd"), ":corn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xb6"), ":hot_pepper:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x92"), ":cucumber:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x9c"), ":peanuts:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x9e"), ":bread:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x90"), ":croissant:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x96"), ":french_bread:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x9e"), ":pancakes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa7\x80"), ":cheese:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x96"), ":meat_on_bone:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x97"), ":poultry_leg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x93"), ":bacon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x94"), ":hamburger:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x9f"), ":fries:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x95"), ":pizza:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xad"), ":hotdog:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xae"), ":taco:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xaf"), ":burrito:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x99"), ":stuffed_flatbread:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x9a"), ":egg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb3"), ":cooking:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x98"), ":shallow_pan_of_food:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb2"), ":stew:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x97"), ":salad:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xbf"), ":popcorn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb1"), ":bento:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x98"), ":rice_cracker:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x99"), ":rice_ball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x9a"), ":rice:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x9b"), ":curry:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x9c"), ":ramen:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\x9d"), ":spaghetti:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa0"), ":sweet_potato:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa2"), ":oden:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa3"), ":sushi:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa4"), ":fried_shrimp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa5"), ":fish_cake:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa1"), ":dango:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa6"), ":icecream:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa7"), ":shaved_ice:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa8"), ":ice_cream:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xa9"), ":doughnut:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xaa"), ":cookie:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x82"), ":birthday:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb0"), ":cake:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xab"), ":chocolate_bar:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xac"), ":candy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xad"), ":lollipop:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xae"), ":custard:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xaf"), ":honey_pot:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xbc"), ":baby_bottle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x9b"), ":milk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x95"), ":coffee:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb5"), ":tea:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb6"), ":sake:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xbe"), ":champagne:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb7"), ":wine_glass:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb8"), ":cocktail:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb9"), ":tropical_drink:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xba"), ":beer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xbb"), ":beers:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x82"), ":champagne_glass:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x83"), ":tumbler_glass:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xbd"), ":fork_knife_plate:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8d\xb4"), ":fork_and_knife:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x84"), ":spoon:" }),
+};
+
+const QVariantList EmojiModel::activity = {
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\xbe"), ":space_invader:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xb4"), ":levitate:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xba"), ":fencer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x87"), ":horse_racing:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x87\xf0\x9f\x8f\xbb"), ":horse_racing_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x87\xf0\x9f\x8f\xbc"), ":horse_racing_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x87\xf0\x9f\x8f\xbd"), ":horse_racing_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x87\xf0\x9f\x8f\xbe"), ":horse_racing_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x87\xf0\x9f\x8f\xbf"), ":horse_racing_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb7"), ":skier:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x82"), ":snowboarder:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8c"), ":golfer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x84"), ":surfer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x84\xf0\x9f\x8f\xbb"), ":surfer_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x84\xf0\x9f\x8f\xbc"), ":surfer_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x84\xf0\x9f\x8f\xbd"), ":surfer_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x84\xf0\x9f\x8f\xbe"), ":surfer_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x84\xf0\x9f\x8f\xbf"), ":surfer_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa3"), ":rowboat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa3\xf0\x9f\x8f\xbb"), ":rowboat_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa3\xf0\x9f\x8f\xbc"), ":rowboat_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa3\xf0\x9f\x8f\xbd"), ":rowboat_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa3\xf0\x9f\x8f\xbe"), ":rowboat_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa3\xf0\x9f\x8f\xbf"), ":rowboat_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8a"), ":swimmer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8a\xf0\x9f\x8f\xbb"), ":swimmer_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8a\xf0\x9f\x8f\xbc"), ":swimmer_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8a\xf0\x9f\x8f\xbd"), ":swimmer_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8a\xf0\x9f\x8f\xbe"), ":swimmer_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8a\xf0\x9f\x8f\xbf"), ":swimmer_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb9"), ":basketball_player:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb9\xf0\x9f\x8f\xbb"), ":basketball_player_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb9\xf0\x9f\x8f\xbc"), ":basketball_player_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb9\xf0\x9f\x8f\xbd"), ":basketball_player_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb9\xf0\x9f\x8f\xbe"), ":basketball_player_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb9\xf0\x9f\x8f\xbf"), ":basketball_player_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8b"), ":lifter:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8b\xf0\x9f\x8f\xbb"), ":lifter_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8b\xf0\x9f\x8f\xbc"), ":lifter_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8b\xf0\x9f\x8f\xbd"), ":lifter_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8b\xf0\x9f\x8f\xbe"), ":lifter_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8b\xf0\x9f\x8f\xbf"), ":lifter_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb4"), ":bicyclist:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb4\xf0\x9f\x8f\xbb"), ":bicyclist_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb4\xf0\x9f\x8f\xbc"), ":bicyclist_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb4\xf0\x9f\x8f\xbd"), ":bicyclist_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb4\xf0\x9f\x8f\xbe"), ":bicyclist_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb4\xf0\x9f\x8f\xbf"), ":bicyclist_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb5"), ":mountain_bicyclist:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb5\xf0\x9f\x8f\xbb"), ":mountain_bicyclist_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb5\xf0\x9f\x8f\xbc"), ":mountain_bicyclist_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb5\xf0\x9f\x8f\xbd"), ":mountain_bicyclist_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb5\xf0\x9f\x8f\xbe"), ":mountain_bicyclist_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb5\xf0\x9f\x8f\xbf"), ":mountain_bicyclist_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb8"), ":cartwheel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb8\xf0\x9f\x8f\xbb"), ":cartwheel_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb8\xf0\x9f\x8f\xbc"), ":cartwheel_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb8\xf0\x9f\x8f\xbd"), ":cartwheel_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb8\xf0\x9f\x8f\xbe"), ":cartwheel_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb8\xf0\x9f\x8f\xbf"), ":cartwheel_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbc"), ":wrestlers:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbc\xf0\x9f\x8f\xbb"), ":wrestlers_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbc\xf0\x9f\x8f\xbc"), ":wrestlers_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbc\xf0\x9f\x8f\xbd"), ":wrestlers_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbc\xf0\x9f\x8f\xbe"), ":wrestlers_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbc\xf0\x9f\x8f\xbf"), ":wrestlers_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbd"), ":water_polo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbd\xf0\x9f\x8f\xbb"), ":water_polo_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbd\xf0\x9f\x8f\xbc"), ":water_polo_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbd\xf0\x9f\x8f\xbd"), ":water_polo_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbd\xf0\x9f\x8f\xbe"), ":water_polo_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbd\xf0\x9f\x8f\xbf"), ":water_polo_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbe"), ":handball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbe\xf0\x9f\x8f\xbb"), ":handball_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbe\xf0\x9f\x8f\xbc"), ":handball_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbe\xf0\x9f\x8f\xbd"), ":handball_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbe\xf0\x9f\x8f\xbe"), ":handball_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xbe\xf0\x9f\x8f\xbf"), ":handball_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb9"), ":juggling:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb9\xf0\x9f\x8f\xbb"), ":juggling_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb9\xf0\x9f\x8f\xbc"), ":juggling_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb9\xf0\x9f\x8f\xbd"), ":juggling_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb9\xf0\x9f\x8f\xbe"), ":juggling_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa4\xb9\xf0\x9f\x8f\xbf"), ":juggling_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xaa"), ":circus_tent:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xad"), ":performing_arts:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa8"), ":art:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb0"), ":slot_machine:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x80"), ":bath:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x80\xf0\x9f\x8f\xbb"), ":bath_tone1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x80\xf0\x9f\x8f\xbc"), ":bath_tone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x80\xf0\x9f\x8f\xbd"), ":bath_tone3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x80\xf0\x9f\x8f\xbe"), ":bath_tone4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x80\xf0\x9f\x8f\xbf"), ":bath_tone5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x97"), ":reminder_ribbon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x9f"), ":tickets:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xab"), ":ticket:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x96"), ":military_medal:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x86"), ":trophy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x85"), ":medal:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x87"), ":first_place:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x88"), ":second_place:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x89"), ":third_place:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\xbd"), ":soccer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\xbe"), ":baseball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x80"), ":basketball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x90"), ":volleyball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x88"), ":football:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x89"), ":rugby_football:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xbe"), ":tennis:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb1"), ":8ball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb3"), ":bowling:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8f"), ":cricket:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x91"), ":field_hockey:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x92"), ":hockey:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x93"), ":ping_pong:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xb8"), ":badminton:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x8a"), ":boxing_glove:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x8b"), ":martial_arts_uniform:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x85"), ":goal:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xaf"), ":dart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb3"), ":golf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb8"), ":ice_skate:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa3"), ":fishing_pole_and_fish:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xbd"), ":running_shirt_with_sash:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xbf"), ":ski:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xae"), ":video_game:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb2"), ":game_die:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xbc"), ":musical_score:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa4"), ":microphone:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa7"), ":headphones:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb7"), ":saxophone:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb8"), ":guitar:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb9"), ":musical_keyboard:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xba"), ":trumpet:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xbb"), ":violin:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\xa5\x81"), ":drum:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xac"), ":clapper:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xb9"), ":bow_and_arrow:" }),
+};
+
+const QVariantList EmojiModel::travel = {
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8e"), ":race_car:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x8d"), ":motorcycle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xbe"), ":japan:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x94"), ":mountain_snow:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb0"), ":mountain:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x8b"), ":volcano:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xbb"), ":mount_fuji:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x95"), ":camping:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x96"), ":beach:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x9c"), ":desert:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x9d"), ":island:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x9e"), ":park:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x9f"), ":stadium:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x9b"), ":classical_building:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x97"), ":construction_site:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x98"), ":homes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x99"), ":cityscape:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x9a"), ":house_abandoned:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa0"), ":house:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa1"), ":house_with_garden:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa2"), ":office:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa3"), ":post_office:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa4"), ":european_post_office:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa5"), ":hospital:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa6"), ":bank:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa8"), ":hotel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa9"), ":love_hotel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xaa"), ":convenience_store:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xab"), ":school:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xac"), ":department_store:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xad"), ":factory:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xaf"), ":japanese_castle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xb0"), ":european_castle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x92"), ":wedding:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xbc"), ":tokyo_tower:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xbd"), ":statue_of_liberty:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xaa"), ":church:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x8c"), ":mosque:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x8d"), ":synagogue:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xa9"), ":shinto_shrine:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x8b"), ":kaaba:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb2"), ":fountain:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xba"), ":tent:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x81"), ":foggy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x83"), ":night_with_stars:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x84"), ":sunrise_over_mountains:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x85"), ":sunrise:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x86"), ":city_dusk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x87"), ":city_sunset:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x89"), ":bridge_at_night:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x8c"), ":milky_way:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa0"), ":carousel_horse:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa1"), ":ferris_wheel:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa2"), ":roller_coaster:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x82"), ":steam_locomotive:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x83"), ":railway_car:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x84"), ":bullettrain_side:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x85"), ":bullettrain_front:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x86"), ":train2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x87"), ":metro:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x88"), ":light_rail:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x89"), ":station:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x8a"), ":tram:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x9d"), ":monorail:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x9e"), ":mountain_railway:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x8b"), ":train:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x8c"), ":bus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x8d"), ":oncoming_bus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x8e"), ":trolleybus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x90"), ":minibus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x91"), ":ambulance:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x92"), ":fire_engine:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x93"), ":police_car:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x94"), ":oncoming_police_car:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x95"), ":taxi:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x96"), ":oncoming_taxi:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x97"), ":red_car:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x98"), ":oncoming_automobile:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x99"), ":blue_car:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x9a"), ":truck:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x9b"), ":articulated_lorry:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x9c"), ":tractor:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb2"), ":bike:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xb4"), ":scooter:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xb5"), ":motor_scooter:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x8f"), ":busstop:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xa3"), ":motorway:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xa4"), ":railway_track:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xbd"), ":fuelpump:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa8"), ":rotating_light:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa5"), ":traffic_light:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa6"), ":vertical_traffic_light:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa7"), ":construction:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\x93"), ":anchor:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb5"), ":sailboat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xb6"), ":canoe:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa4"), ":speedboat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xb3"), ":cruise_ship:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb4"), ":ferry:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xa5"), ":motorboat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa2"), ":ship:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x88"), ":airplane:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xa9"), ":airplane_small:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xab"), ":airplane_departure:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xac"), ":airplane_arriving:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xba"), ":seat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x81"), ":helicopter:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x9f"), ":suspension_railway:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa0"), ":mountain_cableway:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa1"), ":aerial_tramway:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\x80"), ":rocket:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xb0"), ":satellite_orbital:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xa0"), ":stars:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x88"), ":rainbow:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x86"), ":fireworks:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x87"), ":sparkler:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x91"), ":rice_scene:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\x81"), ":checkered_flag:" }),
+};
+
+const QVariantList EmojiModel::objects = {
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xa0"), ":skull_crossbones:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x8c"), ":love_letter:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa3"), ":bomb:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xb3"), ":hole:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x8d"), ":shopping_bags:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xbf"), ":prayer_beads:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x8e"), ":gem:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xaa"), ":knife:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xba"), ":amphora:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xba"), ":map:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x88"), ":barber:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\xbc"), ":frame_photo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x8e"), ":bellhop:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xaa"), ":door:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x8c"), ":sleeping_accommodation:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x8f"), ":bed:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x8b"), ":couch:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xbd"), ":toilet:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xbf"), ":shower:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x81"), ":bathtub:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8c\x9b"), ":hourglass:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xb3"), ":hourglass_flowing_sand:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8c\x9a"), ":watch:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xb0"), ":alarm_clock:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xb1"), ":stopwatch:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xb2"), ":timer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xb0"), ":clock:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\xa1"), ":thermometer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\xb1"), ":beach_umbrella:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x88"), ":balloon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x89"), ":tada:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x8a"), ":confetti_ball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x8e"), ":dolls:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x8f"), ":flags:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x90"), ":wind_chime:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x80"), ":ribbon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x81"), ":gift:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xb9"), ":joystick:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xaf"), ":postal_horn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x99"), ":microphone2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x9a"), ":level_slider:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x9b"), ":control_knobs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xbb"), ":radio:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb1"), ":iphone:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb2"), ":calling:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x8e"), ":telephone:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x9e"), ":telephone_receiver:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x9f"), ":pager:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa0"), ":fax:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x8b"), ":battery:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x8c"), ":electric_plug:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xbb"), ":computer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\xa5"), ":desktop:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\xa8"), ":printer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8c\xa8"), ":keyboard:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\xb1"), ":mouse_three_button:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\xb2"), ":trackball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xbd"), ":minidisc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xbe"), ":floppy_disk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xbf"), ":cd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x80"), ":dvd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa5"), ":movie_camera:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x9e"), ":film_frames:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xbd"), ":projector:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xba"), ":tv:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb7"), ":camera:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb8"), ":camera_with_flash:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb9"), ":video_camera:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xbc"), ":vhs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x8d"), ":mag:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x8e"), ":mag_right:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xac"), ":microscope:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xad"), ":telescope:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa1"), ":satellite:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xaf"), ":candle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa1"), ":bulb:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa6"), ":flashlight:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xae"), ":izakaya_lantern:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x94"), ":notebook_with_decorative_cover:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x95"), ":closed_book:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x96"), ":book:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x97"), ":green_book:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x98"), ":blue_book:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x99"), ":orange_book:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x9a"), ":books:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x93"), ":notebook:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x92"), ":ledger:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x83"), ":page_with_curl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x9c"), ":scroll:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x84"), ":page_facing_up:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb0"), ":newspaper:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x9e"), ":newspaper2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x91"), ":bookmark_tabs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x96"), ":bookmark:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xb7"), ":label:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb0"), ":moneybag:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb4"), ":yen:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb5"), ":dollar:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb6"), ":euro:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb7"), ":pound:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb8"), ":money_with_wings:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb3"), ":credit_card:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x89"), ":envelope:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa7"), ":e-mail:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa8"), ":incoming_envelope:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa9"), ":envelope_with_arrow:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa4"), ":outbox_tray:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa5"), ":inbox_tray:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa6"), ":package:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xab"), ":mailbox:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xaa"), ":mailbox_closed:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xac"), ":mailbox_with_mail:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xad"), ":mailbox_with_no_mail:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xae"), ":postbox:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xb3"), ":ballot_box:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x8f"), ":pencil2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x92"), ":black_nib:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\x8b"), ":pen_fountain:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\x8a"), ":pen_ballpoint:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\x8c"), ":paintbrush:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\x8d"), ":crayon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x9d"), ":pencil:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x81"), ":file_folder:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x82"), ":open_file_folder:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x82"), ":dividers:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x85"), ":date:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x86"), ":calendar:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x92"), ":notepad_spiral:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x93"), ":calendar_spiral:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x87"), ":card_index:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x88"), ":chart_with_upwards_trend:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x89"), ":chart_with_downwards_trend:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x8a"), ":bar_chart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x8b"), ":clipboard:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x8c"), ":pushpin:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x8d"), ":round_pushpin:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x8e"), ":paperclip:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\x87"), ":paperclips:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x8f"), ":straight_ruler:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x90"), ":triangular_ruler:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x82"), ":scissors:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x83"), ":card_box:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x84"), ":file_cabinet:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x91"), ":wastebasket:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x92"), ":lock:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x93"), ":unlock:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x8f"), ":lock_with_ink_pen:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x90"), ":closed_lock_with_key:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x91"), ":key:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x9d"), ":key2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa8"), ":hammer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\x8f"), ":pick:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\x92"), ":hammer_pick:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xa0"), ":tools:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xa1"), ":dagger:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\x94"), ":crossed_swords:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xab"), ":gun:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xa1"), ":shield:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa7"), ":wrench:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa9"), ":nut_and_bolt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\x99"), ":gear:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\x9c"), ":compression:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\x97"), ":alembic:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\x96"), ":scales:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x97"), ":link:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\x93"), ":chains:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x89"), ":syringe:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x8a"), ":pill:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xac"), ":smoking:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\xb0"), ":coffin:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\xb1"), ":urn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xbf"), ":moyai:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\xa2"), ":oil:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xae"), ":crystal_ball:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x92"), ":shopping_cart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xa9"), ":triangular_flag_on_post:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\x8c"), ":crossed_flags:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xb4"), ":flag_black:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xb3"), ":flag_white:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xb3\xf0\x9f\x8c\x88"), ":rainbow_flag:" }),
+};
+
+const QVariantList EmojiModel::symbols = {
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x91\x81\xf0\x9f\x97\xa8"), ":eye_in_speech_bubble:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x98"), ":cupid:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\xa4"), ":heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x93"), ":heartbeat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x94"), ":broken_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x95"), ":two_hearts:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x96"), ":sparkling_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x97"), ":heartpulse:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x99"), ":blue_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x9a"), ":green_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x9b"), ":yellow_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x9c"), ":purple_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x96\xa4"), ":black_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x9d"), ":gift_heart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x9e"), ":revolving_hearts:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\x9f"), ":heart_decoration:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\xa3"), ":heart_exclamation:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa2"), ":anger:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa5"), ":boom:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xab"), ":dizzy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xac"), ":speech_balloon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xa8"), ":speech_left:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x97\xaf"), ":anger_right:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xad"), ":thought_balloon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xae"), ":white_flower:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x90"), ":globe_with_meridians:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\xa8"), ":hotsprings:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x91"), ":octagonal_sign:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x9b"), ":clock12:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xa7"), ":clock1230:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x90"), ":clock1:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x9c"), ":clock130:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x91"), ":clock2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x9d"), ":clock230:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x92"), ":clock3:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x9e"), ":clock330:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x93"), ":clock4:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x9f"), ":clock430:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x94"), ":clock5:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xa0"), ":clock530:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x95"), ":clock6:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xa1"), ":clock630:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x96"), ":clock7:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xa2"), ":clock730:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x97"), ":clock8:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xa3"), ":clock830:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x98"), ":clock9:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xa4"), ":clock930:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x99"), ":clock10:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xa5"), ":clock1030:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x9a"), ":clock11:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\xa6"), ":clock1130:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8c\x80"), ":cyclone:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\xa0"), ":spades:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\xa5"), ":hearts:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\xa6"), ":diamonds:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\xa3"), ":clubs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x83\x8f"), ":black_joker:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x80\x84"), ":mahjong:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb4"), ":flower_playing_cards:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x87"), ":mute:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x88"), ":speaker:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x89"), ":sound:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x8a"), ":loud_sound:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa2"), ":loudspeaker:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xa3"), ":mega:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x94"), ":bell:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x95"), ":no_bell:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb5"), ":musical_note:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xb6"), ":notes:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb9"), ":chart:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb1"), ":currency_exchange:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xb2"), ":heavy_dollar_sign:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8f\xa7"), ":atm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xae"), ":put_litter_in_its_place:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb0"), ":potable_water:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\xbf"), ":wheelchair:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb9"), ":mens:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xba"), ":womens:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xbb"), ":restroom:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xbc"), ":baby_symbol:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xbe"), ":wc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x82"), ":passport_control:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x83"), ":customs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x84"), ":baggage_claim:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x85"), ":left_luggage:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\xa0"), ":warning:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb8"), ":children_crossing:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\x94"), ":no_entry:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xab"), ":no_entry_sign:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb3"), ":no_bicycles:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xad"), ":no_smoking:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xaf"), ":do_not_litter:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb1"), ":non-potable_water:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9a\xb7"), ":no_pedestrians:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb5"), ":no_mobile_phones:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x9e"), ":underage:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xa2"), ":radioactive:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xa3"), ":biohazard:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xac\x86"), ":arrow_up:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x86\x97"), ":arrow_upper_right:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9e\xa1"), ":arrow_right:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x86\x98"), ":arrow_lower_right:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xac\x87"), ":arrow_down:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x86\x99"), ":arrow_lower_left:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xac\x85"), ":arrow_left:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x86\x96"), ":arrow_upper_left:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x86\x95"), ":arrow_up_down:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x86\x94"), ":left_right_arrow:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x86\xa9"), ":leftwards_arrow_with_hook:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x86\xaa"), ":arrow_right_hook:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xa4\xb4"), ":arrow_heading_up:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xa4\xb5"), ":arrow_heading_down:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x83"), ":arrows_clockwise:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x84"), ":arrows_counterclockwise:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x99"), ":back:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x9a"), ":end:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x9b"), ":on:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x9c"), ":soon:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x9d"), ":top:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x9b\x90"), ":place_of_worship:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\x9b"), ":atom:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x89"), ":om_symbol:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\xa1"), ":star_of_david:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xb8"), ":wheel_of_dharma:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xaf"), ":yin_yang:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x9d"), ":cross:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xa6"), ":orthodox_cross:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xaa"), ":star_and_crescent:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\xae"), ":peace:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x95\x8e"), ":menorah:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xaf"), ":six_pointed_star:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x88"), ":aries:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x89"), ":taurus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x8a"), ":gemini:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x8b"), ":cancer:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x8c"), ":leo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x8d"), ":virgo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x8e"), ":libra:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x8f"), ":scorpius:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x90"), ":sagittarius:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x91"), ":capricorn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x92"), ":aquarius:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\x93"), ":pisces:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9b\x8e"), ":ophiuchus:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x80"), ":twisted_rightwards_arrows:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x81"), ":repeat:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x82"), ":repeat_one:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x96\xb6"), ":arrow_forward:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xa9"), ":fast_forward:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xad"), ":track_next:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xaf"), ":play_pause:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x97\x80"), ":arrow_backward:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xaa"), ":rewind:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xae"), ":track_previous:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xbc"), ":arrow_up_small:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xab"), ":arrow_double_up:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xbd"), ":arrow_down_small:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xac"), ":arrow_double_down:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xb8"), ":pause_button:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xb9"), ":stop_button:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\xba"), ":record_button:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x8f\x8f"), ":eject:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x8e\xa6"), ":cinema:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x85"), ":low_brightness:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x86"), ":high_brightness:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb6"), ":signal_strength:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb3"), ":vibration_mode:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\xb4"), ":mobile_phone_off:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x99\xbb"), ":recycle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x93\x9b"), ":name_badge:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\x9c"), ":fleur-de-lis:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb0"), ":beginner:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb1"), ":trident:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xad\x95"), ":o:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x85"), ":white_check_mark:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x98\x91"), ":ballot_box_with_check:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x94"), ":heavy_check_mark:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\x96"), ":heavy_multiplication_x:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\x8c"), ":x:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\x8e"), ":negative_squared_cross_mark:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9e\x95"), ":heavy_plus_sign:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9e\x96"), ":heavy_minus_sign:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9e\x97"), ":heavy_division_sign:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9e\xb0"), ":curly_loop:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9e\xbf"), ":loop:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe3\x80\xbd"), ":part_alternation_mark:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\xb3"), ":eight_spoked_asterisk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9c\xb4"), ":eight_pointed_black_star:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\x87"), ":sparkle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x80\xbc"), ":bangbang:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x81\x89"), ":interrobang:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\x93"), ":question:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\x94"), ":grey_question:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\x95"), ":grey_exclamation:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9d\x97"), ":exclamation:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe3\x80\xb0"), ":wavy_dash:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xc2\xa9"), ":copyright:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xc2\xae"), ":registered:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x84\xa2"), ":tm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("#\xe2\x83\xa3"), ":hash:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("*\xe2\x83\xa3"), ":asterisk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("0\xe2\x83\xa3"), ":zero:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("1\xe2\x83\xa3"), ":one:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("2\xe2\x83\xa3"), ":two:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("3\xe2\x83\xa3"), ":three:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("4\xe2\x83\xa3"), ":four:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("5\xe2\x83\xa3"), ":five:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("6\xe2\x83\xa3"), ":six:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("7\xe2\x83\xa3"), ":seven:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("8\xe2\x83\xa3"), ":eight:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("9\xe2\x83\xa3"), ":nine:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x9f"), ":keycap_ten:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xaf"), ":100:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa0"), ":capital_abcd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa1"), ":abcd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa2"), ":1234:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa3"), ":symbols:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xa4"), ":abc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x85\xb0"), ":a:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x8e"), ":ab:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x85\xb1"), ":b:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x91"), ":cl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x92"), ":cool:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x93"), ":free:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x84\xb9"), ":information_source:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x94"), ":id:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x93\x82"), ":m:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x95"), ":new:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x96"), ":ng:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x85\xbe"), ":o2:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x97"), ":ok:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x85\xbf"), ":parking:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x98"), ":sos:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x99"), ":up:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x86\x9a"), ":vs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\x81"), ":koko:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\x82"), ":sa:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xb7"), ":u6708:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xb6"), ":u6709:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xaf"), ":u6307:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x89\x90"), ":ideograph_advantage:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xb9"), ":u5272:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\x9a"), ":u7121:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xb2"), ":u7981:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x89\x91"), ":accept:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xb8"), ":u7533:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xb4"), ":u5408:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xb3"), ":u7a7a:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe3\x8a\x97"), ":congratulations:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe3\x8a\x99"), ":secret:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xba"), ":u55b6:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x88\xb5"), ":u6e80:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x96\xaa"), ":black_small_square:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x96\xab"), ":white_small_square:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x97\xbb"), ":white_medium_square:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x97\xbc"), ":black_medium_square:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x97\xbd"), ":white_medium_small_square:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x97\xbe"), ":black_medium_small_square:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xac\x9b"), ":black_large_square:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\xac\x9c"), ":white_large_square:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb6"), ":large_orange_diamond:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb7"), ":large_blue_diamond:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb8"), ":small_orange_diamond:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb9"), ":small_blue_diamond:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xba"), ":small_red_triangle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xbb"), ":small_red_triangle_down:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x92\xa0"), ":diamond_shape_with_a_dot_inside:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\x98"), ":radio_button:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb2"), ":black_square_button:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb3"), ":white_square_button:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\xaa"), ":white_circle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xe2\x9a\xab"), ":black_circle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb4"), ":red_circle:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x94\xb5"), ":blue_circle:" }),
+};
+
+const QVariantList EmojiModel::flags = {
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xa8"), ":flag_ac:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xa9"), ":flag_ad:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xaa"), ":flag_ae:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xab"), ":flag_af:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xac"), ":flag_ag:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xae"), ":flag_ai:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb1"), ":flag_al:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb2"), ":flag_am:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb4"), ":flag_ao:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb6"), ":flag_aq:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb7"), ":flag_ar:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb8"), ":flag_as:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xb9"), ":flag_at:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xba"), ":flag_au:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbc"), ":flag_aw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbd"), ":flag_ax:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa6\xf0\x9f\x87\xbf"), ":flag_az:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa6"), ":flag_ba:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa7"), ":flag_bb:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xa9"), ":flag_bd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xaa"), ":flag_be:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xab"), ":flag_bf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xac"), ":flag_bg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xad"), ":flag_bh:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xae"), ":flag_bi:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xaf"), ":flag_bj:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb1"), ":flag_bl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb2"), ":flag_bm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb3"), ":flag_bn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb4"), ":flag_bo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb6"), ":flag_bq:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb7"), ":flag_br:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb8"), ":flag_bs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xb9"), ":flag_bt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbb"), ":flag_bv:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbc"), ":flag_bw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbe"), ":flag_by:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa7\xf0\x9f\x87\xbf"), ":flag_bz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa6"), ":flag_ca:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa8"), ":flag_cc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xa9"), ":flag_cd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xab"), ":flag_cf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xac"), ":flag_cg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xad"), ":flag_ch:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xae"), ":flag_ci:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb0"), ":flag_ck:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb1"), ":flag_cl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb2"), ":flag_cm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb3"), ":flag_cn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb4"), ":flag_co:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb5"), ":flag_cp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xb7"), ":flag_cr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xba"), ":flag_cu:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbb"), ":flag_cv:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbc"), ":flag_cw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbd"), ":flag_cx:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbe"), ":flag_cy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa8\xf0\x9f\x87\xbf"), ":flag_cz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xaa"), ":flag_de:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xac"), ":flag_dg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xaf"), ":flag_dj:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb0"), ":flag_dk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb2"), ":flag_dm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xb4"), ":flag_do:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xa9\xf0\x9f\x87\xbf"), ":flag_dz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xa6"), ":flag_ea:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xa8"), ":flag_ec:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xaa"), ":flag_ee:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xac"), ":flag_eg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xad"), ":flag_eh:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb7"), ":flag_er:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb8"), ":flag_es:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xb9"), ":flag_et:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaa\xf0\x9f\x87\xba"), ":flag_eu:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xae"), ":flag_fi:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xaf"), ":flag_fj:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb0"), ":flag_fk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb2"), ":flag_fm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb4"), ":flag_fo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xab\xf0\x9f\x87\xb7"), ":flag_fr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa6"), ":flag_ga:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa7"), ":flag_gb:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xa9"), ":flag_gd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xaa"), ":flag_ge:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xab"), ":flag_gf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xac"), ":flag_gg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xad"), ":flag_gh:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xae"), ":flag_gi:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb1"), ":flag_gl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb2"), ":flag_gm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb3"), ":flag_gn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb5"), ":flag_gp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb6"), ":flag_gq:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb7"), ":flag_gr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb8"), ":flag_gs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xb9"), ":flag_gt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xba"), ":flag_gu:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xbc"), ":flag_gw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xac\xf0\x9f\x87\xbe"), ":flag_gy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb0"), ":flag_hk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb2"), ":flag_hm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb3"), ":flag_hn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb7"), ":flag_hr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xb9"), ":flag_ht:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xad\xf0\x9f\x87\xba"), ":flag_hu:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xa8"), ":flag_ic:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xa9"), ":flag_id:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xaa"), ":flag_ie:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb1"), ":flag_il:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb2"), ":flag_im:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb3"), ":flag_in:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb4"), ":flag_io:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb6"), ":flag_iq:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb7"), ":flag_ir:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb8"), ":flag_is:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xae\xf0\x9f\x87\xb9"), ":flag_it:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xaa"), ":flag_je:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb2"), ":flag_jm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb4"), ":flag_jo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xaf\xf0\x9f\x87\xb5"), ":flag_jp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xaa"), ":flag_ke:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xac"), ":flag_kg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xad"), ":flag_kh:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xae"), ":flag_ki:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb2"), ":flag_km:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb3"), ":flag_kn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb5"), ":flag_kp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xb7"), ":flag_kr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbc"), ":flag_kw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbe"), ":flag_ky:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb0\xf0\x9f\x87\xbf"), ":flag_kz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa6"), ":flag_la:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa7"), ":flag_lb:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xa8"), ":flag_lc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xae"), ":flag_li:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb0"), ":flag_lk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb7"), ":flag_lr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb8"), ":flag_ls:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xb9"), ":flag_lt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xba"), ":flag_lu:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xbb"), ":flag_lv:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb1\xf0\x9f\x87\xbe"), ":flag_ly:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa6"), ":flag_ma:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa8"), ":flag_mc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xa9"), ":flag_md:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xaa"), ":flag_me:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xab"), ":flag_mf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xac"), ":flag_mg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xad"), ":flag_mh:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb0"), ":flag_mk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb1"), ":flag_ml:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb2"), ":flag_mm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb3"), ":flag_mn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb4"), ":flag_mo:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb5"), ":flag_mp:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb6"), ":flag_mq:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb7"), ":flag_mr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb8"), ":flag_ms:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xb9"), ":flag_mt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xba"), ":flag_mu:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbb"), ":flag_mv:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbc"), ":flag_mw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbd"), ":flag_mx:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbe"), ":flag_my:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb2\xf0\x9f\x87\xbf"), ":flag_mz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xa6"), ":flag_na:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xa8"), ":flag_nc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xaa"), ":flag_ne:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xab"), ":flag_nf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xac"), ":flag_ng:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xae"), ":flag_ni:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb1"), ":flag_nl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb4"), ":flag_no:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb5"), ":flag_np:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xb7"), ":flag_nr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xba"), ":flag_nu:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb3\xf0\x9f\x87\xbf"), ":flag_nz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb4\xf0\x9f\x87\xb2"), ":flag_om:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xa6"), ":flag_pa:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xaa"), ":flag_pe:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xab"), ":flag_pf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xac"), ":flag_pg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xad"), ":flag_ph:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb0"), ":flag_pk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb1"), ":flag_pl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb2"), ":flag_pm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb3"), ":flag_pn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb7"), ":flag_pr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb8"), ":flag_ps:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xb9"), ":flag_pt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xbc"), ":flag_pw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb5\xf0\x9f\x87\xbe"), ":flag_py:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb6\xf0\x9f\x87\xa6"), ":flag_qa:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xaa"), ":flag_re:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xb4"), ":flag_ro:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xb8"), ":flag_rs:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xba"), ":flag_ru:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb7\xf0\x9f\x87\xbc"), ":flag_rw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa6"), ":flag_sa:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa7"), ":flag_sb:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa8"), ":flag_sc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xa9"), ":flag_sd:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xaa"), ":flag_se:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xac"), ":flag_sg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xad"), ":flag_sh:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xae"), ":flag_si:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xaf"), ":flag_sj:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb0"), ":flag_sk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb1"), ":flag_sl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb2"), ":flag_sm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb3"), ":flag_sn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb4"), ":flag_so:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb7"), ":flag_sr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb8"), ":flag_ss:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xb9"), ":flag_st:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbb"), ":flag_sv:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbd"), ":flag_sx:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbe"), ":flag_sy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb8\xf0\x9f\x87\xbf"), ":flag_sz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa6"), ":flag_ta:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa8"), ":flag_tc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xa9"), ":flag_td:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xab"), ":flag_tf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xac"), ":flag_tg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xad"), ":flag_th:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xaf"), ":flag_tj:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb0"), ":flag_tk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb1"), ":flag_tl:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb2"), ":flag_tm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb3"), ":flag_tn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb4"), ":flag_to:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb7"), ":flag_tr:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xb9"), ":flag_tt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbb"), ":flag_tv:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbc"), ":flag_tw:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xb9\xf0\x9f\x87\xbf"), ":flag_tz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xa6"), ":flag_ua:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xac"), ":flag_ug:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xb2"), ":flag_um:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xb8"), ":flag_us:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xbe"), ":flag_uy:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xba\xf0\x9f\x87\xbf"), ":flag_uz:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xa6"), ":flag_va:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xa8"), ":flag_vc:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xaa"), ":flag_ve:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xac"), ":flag_vg:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xae"), ":flag_vi:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xb3"), ":flag_vn:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbb\xf0\x9f\x87\xba"), ":flag_vu:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbc\xf0\x9f\x87\xab"), ":flag_wf:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbc\xf0\x9f\x87\xb8"), ":flag_ws:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbd\xf0\x9f\x87\xb0"), ":flag_xk:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbe\xf0\x9f\x87\xaa"), ":flag_ye:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbe\xf0\x9f\x87\xb9"), ":flag_yt:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbf\xf0\x9f\x87\xa6"), ":flag_za:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbf\xf0\x9f\x87\xb2"), ":flag_zm:" }),
+    QVariant::fromValue(Emoji { QString::fromUtf8("\xf0\x9f\x87\xbf\xf0\x9f\x87\xbc"), ":flag_zw:" }),
+};
+}
diff --git a/src/gui/emojimodel.h b/src/gui/emojimodel.h
new file mode 100644 (file)
index 0000000..2d35305
--- /dev/null
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#pragma once
+
+#include <QObject>
+#include <QSettings>
+#include <QObject>
+#include <QQmlEngine>
+#include <QVariant>
+#include <QVector>
+#include <QAbstractItemModel>
+
+namespace OCC {
+
+struct Emoji
+{
+    Emoji(QString u, QString s, bool isCustom = false)
+        : unicode(std::move(std::move(u)))
+        , shortname(std::move(std::move(s)))
+        , isCustom(isCustom)
+    {
+    }
+    Emoji() = default;
+
+    friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
+    {
+        arch << object.unicode;
+        arch << object.shortname;
+        return arch;
+    }
+
+    friend QDataStream &operator>>(QDataStream &arch, Emoji &object)
+    {
+        arch >> object.unicode;
+        arch >> object.shortname;
+        object.isCustom = object.unicode.startsWith("image://");
+        return arch;
+    }
+
+    QString unicode;
+    QString shortname;
+    bool isCustom = false;
+
+    Q_GADGET
+    Q_PROPERTY(QString unicode MEMBER unicode)
+    Q_PROPERTY(QString shortname MEMBER shortname)
+    Q_PROPERTY(bool isCustom MEMBER isCustom)
+};
+
+class EmojiCategoriesModel : public QAbstractListModel
+{
+public:
+    QVariant data(const QModelIndex &index, int role) const override;
+    int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+    QHash<int, QByteArray> roleNames() const override;
+
+private:
+    enum Roles {
+        EmojiRole = 0,
+        LabelRole
+    };
+
+    struct Category
+    {
+        QString emoji;
+        QString label;
+    };
+
+    static const std::vector<Category> categories;
+};
+
+class EmojiModel : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QVariantList model READ model NOTIFY modelChanged)
+    Q_PROPERTY(QAbstractListModel *emojiCategoriesModel READ emojiCategoriesModel CONSTANT)
+
+    Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
+
+    Q_PROPERTY(QVariantList people MEMBER people CONSTANT)
+    Q_PROPERTY(QVariantList nature MEMBER nature CONSTANT)
+    Q_PROPERTY(QVariantList food MEMBER food CONSTANT)
+    Q_PROPERTY(QVariantList activity MEMBER activity CONSTANT)
+    Q_PROPERTY(QVariantList travel MEMBER travel CONSTANT)
+    Q_PROPERTY(QVariantList objects MEMBER objects CONSTANT)
+    Q_PROPERTY(QVariantList symbols MEMBER symbols CONSTANT)
+    Q_PROPERTY(QVariantList flags MEMBER flags CONSTANT)
+
+public:
+    explicit EmojiModel(QObject *parent = nullptr)
+        : QObject(parent)
+    {
+    }
+
+    Q_INVOKABLE QVariantList history() const;
+    Q_INVOKABLE void setCategory(const QString &category);
+    Q_INVOKABLE void emojiUsed(const QVariant &modelData);
+
+    QVariantList model() const;
+    QAbstractListModel *emojiCategoriesModel();
+
+signals:
+    void historyChanged();
+    void modelChanged();
+
+private:
+    static const QVariantList people;
+    static const QVariantList nature;
+    static const QVariantList food;
+    static const QVariantList activity;
+    static const QVariantList travel;
+    static const QVariantList objects;
+    static const QVariantList symbols;
+    static const QVariantList flags;
+
+    QSettings _settings;
+    QString _category = "history";
+
+    EmojiCategoriesModel _emojiCategoriesModel;
+};
+
+}
+
+Q_DECLARE_METATYPE(OCC::Emoji)
index a7a47e16e71e895afc89d42384bfcc6693c5b7fd..ec4a347eb839da7c659cb4965581c567d0346289 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <cmath>
 #include <csignal>
+#include <qqml.h>
 
 #ifdef Q_OS_UNIX
 #include <sys/time.h>
@@ -26,6 +27,8 @@
 #include "theme.h"
 #include "common/utility.h"
 #include "cocoainitializer.h"
+#include "userstatusselectormodel.h"
+#include "emojimodel.h"
 
 #if defined(BUILD_UPDATER)
 #include "updater/updater.h"
@@ -54,6 +57,15 @@ int main(int argc, char **argv)
     Q_INIT_RESOURCE(resources);
     Q_INIT_RESOURCE(theme);
 
+    qmlRegisterType<EmojiModel>("com.nextcloud.desktopclient", 1, 0, "EmojiModel");
+    qRegisterMetaTypeStreamOperators<Emoji>();
+    qmlRegisterType<UserStatusSelectorModel>("com.nextcloud.desktopclient", 1, 0,
+        "UserStatusSelectorModel");
+    qmlRegisterUncreatableType<OCC::UserStatus>("com.nextcloud.desktopclient", 1, 0, "UserStatus",
+        "Access to Status enum");
+    qRegisterMetaType<OCC::UserStatus>("UserStatus");
+
+
     // Work around a bug in KDE's qqc2-desktop-style which breaks
     // buttons with icons not based on a name, by forcing a style name
     // the platformtheme plugin won't try to force qqc2-desktops-style
index 0669ebc64d7d18c4f334f130bbffa2b127448691..0b3d37582c46bc481d6d56221df86e5691403d4c 100644 (file)
@@ -617,7 +617,7 @@ void SocketApi::command_EDIT(const QString &localFile, SocketListener *listener)
     params.addQueryItem("path", fileData.serverRelativePath);
     params.addQueryItem("editorId", editor->id());
     job->addQueryParams(params);
-    job->usePOST();
+    job->setVerb(JsonApiJob::Verb::Post);
 
     QObject::connect(job, &JsonApiJob::jsonReceived, [](const QJsonDocument &json){
         auto data = json.object().value("ocs").toObject().value("data").toObject();
index 4143acaeecf3f498a99f5c67ea6d58b0e5ba0b1c..c418e3c1f1ac238305d2590aec0f8f81dd9ee40d 100644 (file)
@@ -14,6 +14,11 @@ MenuItem {
     Accessible.role: Accessible.MenuItem\r
     Accessible.name: qsTr("Account entry")\r
 \r
+    property variant dialog;\r
+    property variant comp;\r
+\r
+    signal showUserStatusSelectorDialog(int id)\r
+\r
     RowLayout {\r
         id: userLineLayout\r
         spacing: 0\r
@@ -188,6 +193,29 @@ MenuItem {
                     radius: 2\r
                 }\r
 \r
+                MenuItem {\r
+                    visible: model.isConnected && model.serverHasUserStatus\r
+                    height: visible ? implicitHeight : 0\r
+                    \r
+                    text: qsTr("Set status")\r
+                    font.pixelSize: Style.topLinePixelSize\r
+                    hoverEnabled: true\r
+                    onClicked: {\r
+                        showUserStatusSelectorDialog(index)\r
+                        accountMenu.close()\r
+                    }\r
+\r
+                    background: Item {\r
+                        height: parent.height\r
+                        width: parent.menu.width\r
+                        Rectangle {\r
+                            anchors.fill: parent\r
+                            anchors.margins: 1\r
+                            color: parent.parent.hovered ? Style.lightHover : "transparent"\r
+                        }\r
+                    }\r
+                }\r
+\r
                 MenuItem {\r
                     text: model.isConnected ? qsTr("Log out") : qsTr("Log in")\r
                     font.pixelSize: Style.topLinePixelSize\r
index 907392571900e29ab25de3362777427d0eb90ac8..56a387aa59d44b41904322349b3f9a1ceffe01f9 100644 (file)
@@ -4,6 +4,7 @@
 #include "accountmanager.h"
 #include "owncloudgui.h"
 #include <pushnotifications.h>
+#include "userstatusselectormodel.h"
 #include "syncengine.h"
 #include "ocsjob.h"
 #include "configfile.h"
@@ -11,7 +12,9 @@
 #include "logger.h"
 #include "guiutility.h"
 #include "syncfileitem.h"
+#include "tray/ActivityListModel.h"
 #include "tray/NotificationCache.h"
+#include "userstatusconnector.h"
 
 #include <QDesktopServices>
 #include <QIcon>
@@ -25,8 +28,8 @@
 #define NOTIFICATION_REQUEST_FREE_PERIOD 15000
 
 namespace {
-    constexpr qint64 expiredActivitiesCheckIntervalMsecs = 1000 * 60;
-    constexpr qint64 activityDefaultExpirationTimeMsecs = 1000 * 60 * 10;
+constexpr qint64 expiredActivitiesCheckIntervalMsecs = 1000 * 60;
+constexpr qint64 activityDefaultExpirationTimeMsecs = 1000 * 60 * 10;
 }
 
 namespace OCC {
@@ -65,7 +68,7 @@ User::User(AccountStatePtr &account, const bool &isCurrent, QObject *parent)
     connect(this, &User::guiLog, Logger::instance(), &Logger::guiLog);
 
     connect(_account->account().data(), &Account::accountChangedAvatar, this, &User::avatarChanged);
-    connect(_account.data(), &AccountState::statusChanged, this, &User::statusChanged);
+    connect(_account->account().data(), &Account::userStatusChanged, this, &User::statusChanged);
     connect(_account.data(), &AccountState::desktopNotificationsAllowedChanged, this, &User::desktopNotificationsAllowedChanged);
 
     connect(_activityModel, &ActivityListModel::sendNotificationRequest, this, &User::slotSendNotificationRequest);
@@ -236,10 +239,10 @@ void User::slotRefreshActivities()
     _activityModel->slotRefreshActivity();
 }
 
-void User::slotRefreshUserStatus() 
+void User::slotRefreshUserStatus()
 {
     if (_account.data() && _account.data()->isConnected()) {
-        _account.data()->fetchUserStatus();
+        _account->account()->userStatusConnector()->fetchUserStatus();
     }
 }
 
@@ -621,29 +624,29 @@ QString User::server(bool shortened) const
     return serverUrl;
 }
 
-UserStatus::Status User::status() const
+UserStatus::OnlineStatus User::status() const
 {
-    return _account->status();
+    return _account->account()->userStatusConnector()->userStatus().state();
 }
 
 QString User::statusMessage() const
 {
-    return _account->statusMessage();
+    return _account->account()->userStatusConnector()->userStatus().message();
 }
 
 QUrl User::statusIcon() const
 {
-    return _account->statusIcon();
+    return _account->account()->userStatusConnector()->userStatus().stateIcon();
 }
 
 QString User::statusEmoji() const
 {
-    return _account->statusEmoji();
+    return _account->account()->userStatusConnector()->userStatus().icon();
 }
 
 bool User::serverHasUserStatus() const
 {
-    return _account->account()->capabilities().userStatus();
+    return _account->account()->capabilities().userStatusNotification();
 }
 
 QImage User::avatar() const
@@ -921,6 +924,15 @@ Q_INVOKABLE void UserModel::removeAccount(const int &id)
     endRemoveRows();
 }
 
+std::shared_ptr<OCC::UserStatusConnector> UserModel::userStatusConnector(int id)
+{
+    if (id < 0 || id >= _users.size()) {
+        return nullptr;
+    }
+
+    return _users[id]->account()->userStatusConnector();
+}
+
 int UserModel::rowCount(const QModelIndex &parent) const
 {
     Q_UNUSED(parent);
index b45fde7c3a1ea172573735d02ff86b2f43d29edb..c11b2c4769111faa1302de45bc256ada902099cc 100644 (file)
@@ -12,6 +12,8 @@
 #include "accountmanager.h"
 #include "folderman.h"
 #include "NotificationCache.h"
+#include "userstatusselectormodel.h"
+#include "userstatusconnector.h"
 #include <chrono>
 
 namespace OCC {
@@ -55,7 +57,7 @@ public:
     void removeAccount() const;
     QString avatarUrl() const;
     bool isDesktopNotificationsAllowed() const;
-    UserStatus::Status status() const;
+    UserStatus::OnlineStatus status() const;
     QString statusMessage() const;
     QUrl statusIcon() const;
     QString statusEmoji() const;
@@ -158,6 +160,8 @@ public:
     Q_INVOKABLE void logout(const int &id);
     Q_INVOKABLE void removeAccount(const int &id);
 
+    Q_INVOKABLE std::shared_ptr<OCC::UserStatusConnector> userStatusConnector(int id);
+
     ActivityListModel *currentActivityModel();
 
     enum UserRoles {
index ccb4af74e8d04846000e6d58d6fb6d8aafb9ce93..31d5143decac69dbef921407b4a0ba5b819d5bc5 100644 (file)
@@ -140,6 +140,10 @@ Window {
                             }\r
                         }\r
 \r
+                        Loader {\r
+                            id: userStatusSelectorDialogLoader\r
+                        }\r
+\r
                         Menu {\r
                             id: accountMenu\r
 \r
@@ -167,7 +171,14 @@ Window {
                             Instantiator {\r
                                 id: userLineInstantiator\r
                                 model: UserModel\r
-                                delegate: UserLine {}\r
+                                delegate: UserLine {\r
+                                    onShowUserStatusSelectorDialog: {\r
+                                        userStatusSelectorDialogLoader.source = "qrc:/qml/src/gui/UserStatusSelectorDialog.qml"\r
+                                        userStatusSelectorDialogLoader.item.title = qsTr("Set user status")\r
+                                        userStatusSelectorDialogLoader.item.model.load(index)\r
+                                        userStatusSelectorDialogLoader.item.show()\r
+                                    }\r
+                                }\r
                                 onObjectAdded: accountMenu.insertItem(index, object)\r
                                 onObjectRemoved: accountMenu.removeItem(object)\r
                             }\r
diff --git a/src/gui/userstatus.cpp b/src/gui/userstatus.cpp
deleted file mode 100644 (file)
index 55d929e..0000000
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * Copyright (C) by Camila <hello@camila.codes>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * for more details.
- */
-
-#include "userstatus.h"
-#include "account.h"
-#include "accountstate.h"
-#include "networkjobs.h"
-#include "folderman.h"
-#include "creds/abstractcredentials.h"
-#include "theme.h"
-#include "capabilities.h"
-
-#include <QTimer>
-#include <QJsonDocument>
-#include <QJsonObject>
-
-namespace OCC {
-
-Q_LOGGING_CATEGORY(lcUserStatus, "nextcloud.gui.userstatus", QtInfoMsg)
-
-namespace {
-    UserStatus::Status stringToEnum(const QString &status) 
-    {
-        // it needs to match the Status enum
-        const QHash<QString, UserStatus::Status> preDefinedStatus{
-            {"online", UserStatus::Status::Online},
-            {"dnd", UserStatus::Status::DoNotDisturb},
-            {"away", UserStatus::Status::Away},
-            {"offline", UserStatus::Status::Offline},
-            {"invisible", UserStatus::Status::Invisible}
-        };
-        
-        // api should return invisible, dnd,... toLower() it is to make sure 
-        // it matches _preDefinedStatus, otherwise the default is online (0)
-        return preDefinedStatus.value(status.toLower(), UserStatus::Status::Online);
-    }
-}
-
-UserStatus::UserStatus(QObject *parent)
-    : QObject(parent)
-{
-}
-
-void UserStatus::fetchUserStatus(AccountPtr account)
-{
-    if (!account->capabilities().userStatus()) {
-        return;
-    }
-    
-    if (_job) {
-        _job->deleteLater();
-    }
-
-    _job = new JsonApiJob(account, QStringLiteral("/ocs/v2.php/apps/user_status/api/v1/user_status"), this);
-    connect(_job.data(), &JsonApiJob::jsonReceived, this, &UserStatus::slotFetchUserStatusFinished);
-    _job->start();
-}
-
-void UserStatus::slotFetchUserStatusFinished(const QJsonDocument &json, int statusCode)
-{
-    const QJsonObject defaultValues {
-        {"icon", ""},
-        {"message", ""},
-        {"status", "online"},
-        {"messageIsPredefined", "false"},
-        {"statusIsUserDefined", "false"}
-    };
-    
-    if (statusCode != 200) {
-        qCInfo(lcUserStatus) << "Slot fetch UserStatus finished with status code" << statusCode;
-        qCInfo(lcUserStatus) << "Using then default values as if user has not set any status" << defaultValues;
-    }
-    
-    const auto retrievedData = json.object().value("ocs").toObject().value("data").toObject(defaultValues);
-
-    _emoji = retrievedData.value("icon").toString().trimmed();
-    _status = stringToEnum(retrievedData.value("status").toString());
-    _message = retrievedData.value("message").toString().trimmed();
-
-    emit fetchUserStatusFinished();
-}
-
-UserStatus::Status UserStatus::status() const
-{
-    return _status;
-}
-
-QString UserStatus::message() const
-{
-    return _message;
-}
-
-QString UserStatus::emoji() const
-{
-    return _emoji;
-}
-
-QUrl UserStatus::icon() const
-{
-    switch (_status) {
-    case Status::Away:
-        return Theme::instance()->statusAwayImageSource();
-    case Status::DoNotDisturb:
-        return Theme::instance()->statusDoNotDisturbImageSource();
-    case Status::Invisible:
-    case Status::Offline:
-        return Theme::instance()->statusInvisibleImageSource();
-    case Status::Online:
-        return Theme::instance()->statusOnlineImageSource();
-    }
-    
-    Q_UNREACHABLE();
-}
-
-} // namespace OCC
diff --git a/src/gui/userstatus.h b/src/gui/userstatus.h
deleted file mode 100644 (file)
index 3e952a5..0000000
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) by Camila <hello@camila.codes>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful, but
- * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
- * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
- * for more details.
- */
-
-#ifndef USERSTATUS_H
-#define USERSTATUS_H
-
-#include <QPointer>
-#include "accountfwd.h"
-
-namespace OCC {
-
-class JsonApiJob;
-
-class UserStatus : public QObject
-{
-    Q_OBJECT
-    
-public:
-    explicit UserStatus(QObject *parent = nullptr);
-    enum class Status {
-        Online,
-        DoNotDisturb,
-        Away,
-        Offline,
-        Invisible
-    };
-    Q_ENUM(Status);
-    void fetchUserStatus(AccountPtr account);
-    Status status() const;
-    QString message() const;
-    QString emoji() const;
-    QUrl icon() const;
-
-private slots:
-    void slotFetchUserStatusFinished(const QJsonDocument &json, int statusCode);
-
-signals:
-    void fetchUserStatusFinished();
-
-private:
-    QPointer<JsonApiJob> _job; // the currently running job
-    Status _status = Status::Online;
-    QString _message;
-    QString _emoji;
-};
-
-
-} // namespace OCC
-
-#endif //USERSTATUS_H
diff --git a/src/gui/userstatusselectormodel.cpp b/src/gui/userstatusselectormodel.cpp
new file mode 100644 (file)
index 0000000..c241474
--- /dev/null
@@ -0,0 +1,474 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "userstatusselectormodel.h"
+#include "tray/UserModel.h"
+
+#include <ocsuserstatusconnector.h>
+#include <qnamespace.h>
+#include <userstatusconnector.h>
+#include <theme.h>
+
+#include <QDateTime>
+#include <QLoggingCategory>
+
+#include <algorithm>
+#include <cmath>
+#include <cstddef>
+
+namespace OCC {
+
+Q_LOGGING_CATEGORY(lcUserStatusDialogModel, "nextcloud.gui.userstatusdialogmodel", QtInfoMsg)
+
+UserStatusSelectorModel::UserStatusSelectorModel(QObject *parent)
+    : QObject(parent)
+    , _dateTimeProvider(new DateTimeProvider)
+{
+    _userStatus.setIcon("😀");
+}
+
+UserStatusSelectorModel::UserStatusSelectorModel(std::shared_ptr<UserStatusConnector> userStatusConnector, QObject *parent)
+    : QObject(parent)
+    , _userStatusConnector(userStatusConnector)
+    , _userStatus("no-id", "", "😀", UserStatus::OnlineStatus::Online, false, {})
+    , _dateTimeProvider(new DateTimeProvider)
+{
+    _userStatus.setIcon("😀");
+    init();
+}
+
+UserStatusSelectorModel::UserStatusSelectorModel(std::shared_ptr<UserStatusConnector> userStatusConnector,
+    std::unique_ptr<DateTimeProvider> dateTimeProvider,
+    QObject *parent)
+    : QObject(parent)
+    , _userStatusConnector(userStatusConnector)
+    , _dateTimeProvider(std::move(dateTimeProvider))
+{
+    _userStatus.setIcon("😀");
+    init();
+}
+
+UserStatusSelectorModel::UserStatusSelectorModel(const UserStatus &userStatus,
+    std::unique_ptr<DateTimeProvider> dateTimeProvider, QObject *parent)
+    : QObject(parent)
+    , _userStatus(userStatus)
+    , _dateTimeProvider(std::move(dateTimeProvider))
+{
+    _userStatus.setIcon("😀");
+}
+
+UserStatusSelectorModel::UserStatusSelectorModel(const UserStatus &userStatus,
+    QObject *parent)
+    : QObject(parent)
+    , _userStatus(userStatus)
+{
+    _userStatus.setIcon("😀");
+}
+
+void UserStatusSelectorModel::load(int id)
+{
+    reset();
+    _userStatusConnector = UserModel::instance()->userStatusConnector(id);
+    init();
+}
+
+void UserStatusSelectorModel::reset()
+{
+    if (_userStatusConnector) {
+        disconnect(_userStatusConnector.get(), &UserStatusConnector::userStatusFetched, this,
+            &UserStatusSelectorModel::onUserStatusFetched);
+        disconnect(_userStatusConnector.get(), &UserStatusConnector::predefinedStatusesFetched, this,
+            &UserStatusSelectorModel::onPredefinedStatusesFetched);
+        disconnect(_userStatusConnector.get(), &UserStatusConnector::error, this,
+            &UserStatusSelectorModel::onError);
+        disconnect(_userStatusConnector.get(), &UserStatusConnector::userStatusSet, this,
+            &UserStatusSelectorModel::onUserStatusSet);
+        disconnect(_userStatusConnector.get(), &UserStatusConnector::messageCleared, this,
+            &UserStatusSelectorModel::onMessageCleared);
+    }
+    _userStatusConnector = nullptr;
+}
+
+void UserStatusSelectorModel::init()
+{
+    if (!_userStatusConnector) {
+        return;
+    }
+
+    connect(_userStatusConnector.get(), &UserStatusConnector::userStatusFetched, this,
+        &UserStatusSelectorModel::onUserStatusFetched);
+    connect(_userStatusConnector.get(), &UserStatusConnector::predefinedStatusesFetched, this,
+        &UserStatusSelectorModel::onPredefinedStatusesFetched);
+    connect(_userStatusConnector.get(), &UserStatusConnector::error, this,
+        &UserStatusSelectorModel::onError);
+    connect(_userStatusConnector.get(), &UserStatusConnector::userStatusSet, this,
+        &UserStatusSelectorModel::onUserStatusSet);
+    connect(_userStatusConnector.get(), &UserStatusConnector::messageCleared, this,
+        &UserStatusSelectorModel::onMessageCleared);
+
+    _userStatusConnector->fetchUserStatus();
+    _userStatusConnector->fetchPredefinedStatuses();
+}
+
+UserStatusSelectorModel::~UserStatusSelectorModel()
+{
+    qCDebug(lcUserStatusDialogModel) << "Destroyed";
+}
+
+void UserStatusSelectorModel::onUserStatusSet()
+{
+    qCDebug(lcUserStatusDialogModel) << "Emit finished";
+    emit finished();
+}
+
+void UserStatusSelectorModel::onMessageCleared()
+{
+    emit finished();
+}
+
+void UserStatusSelectorModel::onError(UserStatusConnector::Error error)
+{
+    qCWarning(lcUserStatusDialogModel) << "Error:" << error;
+
+    switch (error) {
+    case UserStatusConnector::Error::CouldNotFetchPredefinedUserStatuses:
+        setError(tr("Could not fetch predefined statuses. Make sure you are connected to the server."));
+        return;
+
+    case UserStatusConnector::Error::CouldNotFetchUserStatus:
+        setError(tr("Could not fetch user status. Make sure you are connected to the server."));
+        return;
+
+    case UserStatusConnector::Error::UserStatusNotSupported:
+        setError(tr("User status feature is not supported. You will not be able to set your user status."));
+        return;
+
+    case UserStatusConnector::Error::EmojisNotSupported:
+        setError(tr("Emojis feature is not supported. Some user status functionality may not work."));
+        return;
+
+    case UserStatusConnector::Error::CouldNotSetUserStatus:
+        setError(tr("Could not set user status. Make sure you are connected to the server."));
+        return;
+
+    case UserStatusConnector::Error::CouldNotClearMessage:
+        setError(tr("Could not clear user status message. Make sure you are connected to the server."));
+        return;
+    }
+
+    Q_UNREACHABLE();
+}
+
+void UserStatusSelectorModel::setError(const QString &reason)
+{
+    _errorMessage = reason;
+    emit errorMessageChanged();
+}
+
+void UserStatusSelectorModel::clearError()
+{
+    setError("");
+}
+
+void UserStatusSelectorModel::setOnlineStatus(UserStatus::OnlineStatus status)
+{
+    if (status == _userStatus.state()) {
+        return;
+    }
+
+    _userStatus.setState(status);
+    emit onlineStatusChanged();
+}
+
+QUrl UserStatusSelectorModel::onlineIcon() const
+{
+    return Theme::instance()->statusOnlineImageSource();
+}
+
+QUrl UserStatusSelectorModel::awayIcon() const
+{
+    return Theme::instance()->statusAwayImageSource();
+}
+QUrl UserStatusSelectorModel::dndIcon() const
+{
+    return Theme::instance()->statusDoNotDisturbImageSource();
+}
+QUrl UserStatusSelectorModel::invisibleIcon() const
+{
+    return Theme::instance()->statusInvisibleImageSource();
+}
+
+UserStatus::OnlineStatus UserStatusSelectorModel::onlineStatus() const
+{
+    return _userStatus.state();
+}
+
+QString UserStatusSelectorModel::userStatusMessage() const
+{
+    return _userStatus.message();
+}
+
+void UserStatusSelectorModel::setUserStatusMessage(const QString &message)
+{
+    _userStatus.setMessage(message);
+    _userStatus.setMessagePredefined(false);
+    emit userStatusChanged();
+}
+
+void UserStatusSelectorModel::setUserStatusEmoji(const QString &emoji)
+{
+    _userStatus.setIcon(emoji);
+    _userStatus.setMessagePredefined(false);
+    emit userStatusChanged();
+}
+
+QString UserStatusSelectorModel::userStatusEmoji() const
+{
+    return _userStatus.icon();
+}
+
+void UserStatusSelectorModel::onUserStatusFetched(const UserStatus &userStatus)
+{
+    if (userStatus.state() != UserStatus::OnlineStatus::Offline) {
+        _userStatus.setState(userStatus.state());
+    }
+    _userStatus.setMessage(userStatus.message());
+    _userStatus.setMessagePredefined(userStatus.messagePredefined());
+    _userStatus.setId(userStatus.id());
+    _userStatus.setClearAt(userStatus.clearAt());
+
+    if (!userStatus.icon().isEmpty()) {
+        _userStatus.setIcon(userStatus.icon());
+    }
+
+    emit userStatusChanged();
+    emit onlineStatusChanged();
+    emit clearAtChanged();
+}
+
+Optional<ClearAt> UserStatusSelectorModel::clearStageTypeToDateTime(ClearStageType type) const
+{
+    switch (type) {
+    case ClearStageType::DontClear:
+        return {};
+
+    case ClearStageType::HalfHour: {
+        ClearAt clearAt;
+        clearAt._type = ClearAtType::Period;
+        clearAt._period = 60 * 30;
+        return clearAt;
+    }
+
+    case ClearStageType::OneHour: {
+        ClearAt clearAt;
+        clearAt._type = ClearAtType::Period;
+        clearAt._period = 60 * 60;
+        return clearAt;
+    }
+
+    case ClearStageType::FourHour: {
+        ClearAt clearAt;
+        clearAt._type = ClearAtType::Period;
+        clearAt._period = 60 * 60 * 4;
+        return clearAt;
+    }
+
+    case ClearStageType::Today: {
+        ClearAt clearAt;
+        clearAt._type = ClearAtType::EndOf;
+        clearAt._endof = "day";
+        return clearAt;
+    }
+
+    case ClearStageType::Week: {
+        ClearAt clearAt;
+        clearAt._type = ClearAtType::EndOf;
+        clearAt._endof = "week";
+        return clearAt;
+    }
+
+    default:
+        Q_UNREACHABLE();
+    }
+}
+
+void UserStatusSelectorModel::setUserStatus()
+{
+    Q_ASSERT(_userStatusConnector);
+    if (!_userStatusConnector) {
+        return;
+    }
+
+    clearError();
+    _userStatusConnector->setUserStatus(_userStatus);
+}
+
+void UserStatusSelectorModel::clearUserStatus()
+{
+    Q_ASSERT(_userStatusConnector);
+    if (!_userStatusConnector) {
+        return;
+    }
+
+    clearError();
+    _userStatusConnector->clearMessage();
+}
+
+void UserStatusSelectorModel::onPredefinedStatusesFetched(const std::vector<UserStatus> &statuses)
+{
+    _predefinedStatuses = statuses;
+    emit predefinedStatusesChanged();
+}
+
+UserStatus UserStatusSelectorModel::predefinedStatus(int index) const
+{
+    Q_ASSERT(0 <= index && index < static_cast<int>(_predefinedStatuses.size()));
+    return _predefinedStatuses[index];
+}
+
+int UserStatusSelectorModel::predefinedStatusesCount() const
+{
+    return static_cast<int>(_predefinedStatuses.size());
+}
+
+void UserStatusSelectorModel::setPredefinedStatus(int index)
+{
+    Q_ASSERT(0 <= index && index < static_cast<int>(_predefinedStatuses.size()));
+
+    _userStatus.setMessagePredefined(true);
+    const auto predefinedStatus = _predefinedStatuses[index];
+    _userStatus.setId(predefinedStatus.id());
+    _userStatus.setMessage(predefinedStatus.message());
+    _userStatus.setIcon(predefinedStatus.icon());
+    _userStatus.setClearAt(predefinedStatus.clearAt());
+
+    emit userStatusChanged();
+    emit clearAtChanged();
+}
+
+QString UserStatusSelectorModel::clearAtStageToString(ClearStageType stage) const
+{
+    switch (stage) {
+    case ClearStageType::DontClear:
+        return tr("Don't clear");
+
+    case ClearStageType::HalfHour:
+        return tr("30 minutes");
+
+    case ClearStageType::OneHour:
+        return tr("1 hour");
+
+    case ClearStageType::FourHour:
+        return tr("4 hours");
+
+    case ClearStageType::Today:
+        return tr("Today");
+
+    case ClearStageType::Week:
+        return tr("This week");
+
+    default:
+        Q_UNREACHABLE();
+    }
+}
+
+QStringList UserStatusSelectorModel::clearAtValues() const
+{
+    QStringList clearAtStages;
+    std::transform(_clearStages.begin(), _clearStages.end(),
+        std::back_inserter(clearAtStages),
+        [this](const ClearStageType &stage) { return clearAtStageToString(stage); });
+
+    return clearAtStages;
+}
+
+void UserStatusSelectorModel::setClearAt(int index)
+{
+    Q_ASSERT(0 <= index && index < static_cast<int>(_clearStages.size()));
+    _userStatus.setClearAt(clearStageTypeToDateTime(_clearStages[index]));
+    emit clearAtChanged();
+}
+
+QString UserStatusSelectorModel::errorMessage() const
+{
+    return _errorMessage;
+}
+
+QString UserStatusSelectorModel::timeDifferenceToString(int differenceSecs) const
+{
+    if (differenceSecs < 60) {
+        return tr("Less than a minute");
+    } else if (differenceSecs < 60 * 60) {
+        const auto minutesLeft = std::ceil(differenceSecs / 60.0);
+        if (minutesLeft == 1) {
+            return tr("1 minute");
+        } else {
+            return tr("%1 minutes").arg(minutesLeft);
+        }
+    } else if (differenceSecs < 60 * 60 * 24) {
+        const auto hoursLeft = std::ceil(differenceSecs / 60.0 / 60.0);
+        if (hoursLeft == 1) {
+            return tr("1 hour");
+        } else {
+            return tr("%1 hours").arg(hoursLeft);
+        }
+    } else {
+        const auto daysLeft = std::ceil(differenceSecs / 60.0 / 60.0 / 24.0);
+        if (daysLeft == 1) {
+            return tr("1 day");
+        } else {
+            return tr("%1 days").arg(daysLeft);
+        }
+    }
+}
+
+QString UserStatusSelectorModel::clearAtReadable(const Optional<ClearAt> &clearAt) const
+{
+    if (clearAt) {
+        switch (clearAt->_type) {
+        case ClearAtType::Period: {
+            return timeDifferenceToString(clearAt->_period);
+        }
+
+        case ClearAtType::Timestamp: {
+            const int difference = static_cast<int>(clearAt->_timestamp - _dateTimeProvider->currentDateTime().toTime_t());
+            return timeDifferenceToString(difference);
+        }
+
+        case ClearAtType::EndOf: {
+            if (clearAt->_endof == "day") {
+                return tr("Today");
+            } else if (clearAt->_endof == "week") {
+                return tr("This week");
+            }
+            Q_UNREACHABLE();
+        }
+
+        default:
+            Q_UNREACHABLE();
+        }
+    }
+    return tr("Don't clear");
+}
+
+QString UserStatusSelectorModel::predefinedStatusClearAt(int index) const
+{
+    return clearAtReadable(predefinedStatus(index).clearAt());
+}
+
+QString UserStatusSelectorModel::clearAt() const
+{
+    return clearAtReadable(_userStatus.clearAt());
+}
+}
diff --git a/src/gui/userstatusselectormodel.h b/src/gui/userstatusselectormodel.h
new file mode 100644 (file)
index 0000000..cc90b77
--- /dev/null
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#pragma once
+
+#include "common/result.h"
+
+#include <userstatusconnector.h>
+#include <datetimeprovider.h>
+
+#include <QObject>
+#include <QMetaType>
+#include <QtNumeric>
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+namespace OCC {
+
+class UserStatusSelectorModel : public QObject
+{
+    Q_OBJECT
+
+    Q_PROPERTY(QString userStatusMessage READ userStatusMessage NOTIFY userStatusChanged)
+    Q_PROPERTY(QString userStatusEmoji READ userStatusEmoji WRITE setUserStatusEmoji NOTIFY userStatusChanged)
+    Q_PROPERTY(OCC::UserStatus::OnlineStatus onlineStatus READ onlineStatus WRITE setOnlineStatus NOTIFY onlineStatusChanged)
+    Q_PROPERTY(int predefinedStatusesCount READ predefinedStatusesCount NOTIFY predefinedStatusesChanged)
+    Q_PROPERTY(QStringList clearAtValues READ clearAtValues CONSTANT)
+    Q_PROPERTY(QString clearAt READ clearAt NOTIFY clearAtChanged)
+    Q_PROPERTY(QString errorMessage READ errorMessage NOTIFY errorMessageChanged)
+    Q_PROPERTY(QUrl onlineIcon READ onlineIcon CONSTANT)
+    Q_PROPERTY(QUrl awayIcon READ awayIcon CONSTANT)
+    Q_PROPERTY(QUrl dndIcon READ dndIcon CONSTANT)
+    Q_PROPERTY(QUrl invisibleIcon READ invisibleIcon CONSTANT)
+
+public:
+    explicit UserStatusSelectorModel(QObject *parent = nullptr);
+
+    explicit UserStatusSelectorModel(std::shared_ptr<UserStatusConnector> userStatusConnector,
+        QObject *parent = nullptr);
+
+    explicit UserStatusSelectorModel(std::shared_ptr<UserStatusConnector> userStatusConnector,
+        std::unique_ptr<DateTimeProvider> dateTimeProvider,
+        QObject *parent = nullptr);
+
+    explicit UserStatusSelectorModel(const UserStatus &userStatus,
+        std::unique_ptr<DateTimeProvider> dateTimeProvider,
+        QObject *parent = nullptr);
+
+    explicit UserStatusSelectorModel(const UserStatus &userStatus,
+        QObject *parent = nullptr);
+
+    ~UserStatusSelectorModel() override;
+
+    Q_INVOKABLE void load(int id);
+
+    Q_REQUIRED_RESULT UserStatus::OnlineStatus onlineStatus() const;
+    Q_INVOKABLE void setOnlineStatus(OCC::UserStatus::OnlineStatus status);
+
+    Q_REQUIRED_RESULT QUrl onlineIcon() const;
+    Q_REQUIRED_RESULT QUrl awayIcon() const;
+    Q_REQUIRED_RESULT QUrl dndIcon() const;
+    Q_REQUIRED_RESULT QUrl invisibleIcon() const;
+
+    Q_REQUIRED_RESULT QString userStatusMessage() const;
+    Q_INVOKABLE void setUserStatusMessage(const QString &message);
+    void setUserStatusEmoji(const QString &emoji);
+    Q_REQUIRED_RESULT QString userStatusEmoji() const;
+
+    Q_INVOKABLE void setUserStatus();
+    Q_INVOKABLE void clearUserStatus();
+
+    Q_REQUIRED_RESULT int predefinedStatusesCount() const;
+    Q_INVOKABLE UserStatus predefinedStatus(int index) const;
+    Q_INVOKABLE QString predefinedStatusClearAt(int index) const;
+    Q_INVOKABLE void setPredefinedStatus(int index);
+
+    Q_REQUIRED_RESULT QStringList clearAtValues() const;
+    Q_REQUIRED_RESULT QString clearAt() const;
+    Q_INVOKABLE void setClearAt(int index);
+
+    Q_REQUIRED_RESULT QString errorMessage() const;
+
+signals:
+    void errorMessageChanged();
+    void userStatusChanged();
+    void onlineStatusChanged();
+    void clearAtChanged();
+    void predefinedStatusesChanged();
+    void finished();
+
+private:
+    enum class ClearStageType {
+        DontClear,
+        HalfHour,
+        OneHour,
+        FourHour,
+        Today,
+        Week
+    };
+
+    void init();
+    void reset();
+    void onUserStatusFetched(const UserStatus &userStatus);
+    void onPredefinedStatusesFetched(const std::vector<UserStatus> &statuses);
+    void onUserStatusSet();
+    void onMessageCleared();
+    void onError(UserStatusConnector::Error error);
+
+    Q_REQUIRED_RESULT QString clearAtStageToString(ClearStageType stage) const;
+    Q_REQUIRED_RESULT QString clearAtReadable(const Optional<ClearAt> &clearAt) const;
+    Q_REQUIRED_RESULT QString timeDifferenceToString(int differenceSecs) const;
+    Q_REQUIRED_RESULT Optional<ClearAt> clearStageTypeToDateTime(ClearStageType type) const;
+    void setError(const QString &reason);
+    void clearError();
+
+    std::shared_ptr<UserStatusConnector> _userStatusConnector {};
+    std::vector<UserStatus> _predefinedStatuses;
+    UserStatus _userStatus;
+    std::unique_ptr<DateTimeProvider> _dateTimeProvider;
+
+    QString _errorMessage;
+
+    std::vector<ClearStageType> _clearStages = {
+        ClearStageType::DontClear,
+        ClearStageType::HalfHour,
+        ClearStageType::OneHour,
+        ClearStageType::FourHour,
+        ClearStageType::Today,
+        ClearStageType::Week
+    };
+};
+}
index 3c38c63744c4c0d73a736863834c598a11c822d5..750d45ec8361c6098400ddb24aa83d7d7e779fe9 100644 (file)
@@ -55,6 +55,9 @@ set(libsync_SRCS
     theme.cpp
     clientsideencryption.cpp
     clientsideencryptionjobs.cpp
+    datetimeprovider.cpp
+    ocsuserstatusconnector.cpp
+    userstatusconnector.cpp
     creds/dummycredentials.cpp
     creds/abstractcredentials.cpp
     creds/credentialscommon.cpp
index be9c01c9312e951435143d4c9bf55c8dc9049abb..6bb00a6b28acc3ebafade410f207b1d72dd4949e 100644 (file)
@@ -139,6 +139,15 @@ QNetworkReply *AbstractNetworkJob::sendRequest(const QByteArray &verb, const QUr
     return reply;
 }
 
+QNetworkReply *AbstractNetworkJob::sendRequest(const QByteArray &verb, const QUrl &url,
+    QNetworkRequest req, const QByteArray &requestBody)
+{
+    auto reply = _account->sendRawRequest(verb, url, req, requestBody);
+    _requestBody = nullptr;
+    adoptRequest(reply);
+    return reply;
+}
+
 void AbstractNetworkJob::adoptRequest(QNetworkReply *reply)
 {
     addTimer(reply);
index d1f3f6bc5e0dfc4ee18ee22ddbe9773b0acf36bf..63391e672e6dc3feb546d88627bc1efc8219f779 100644 (file)
@@ -128,6 +128,9 @@ protected:
         QNetworkRequest req = QNetworkRequest(),
         QIODevice *requestBody = nullptr);
 
+    QNetworkReply *sendRequest(const QByteArray &verb, const QUrl &url,
+        QNetworkRequest req, const QByteArray &requestBody);
+
     // sendRequest does not take a relative path instead of an url,
     // but the old API allowed that. We have this undefined overload
     // to help catch usage errors
index 8beb34412f4d10549d3cda557194ab039380da76..d0cfe36e40bf0668678afe9c11da7ad31307dc01 100644 (file)
@@ -13,6 +13,8 @@
  */
 
 #include "account.h"
+#include "accountfwd.h"
+#include "clientsideencryptionjobs.h"
 #include "cookiejar.h"
 #include "networkjobs.h"
 #include "configfile.h"
@@ -27,6 +29,7 @@
 
 #include "common/asserts.h"
 #include "clientsideencryption.h"
+#include "ocsuserstatusconnector.h"
 
 #include <QLoggingCategory>
 #include <QNetworkReply>
@@ -43,6 +46,7 @@
 #include <QJsonDocument>
 #include <QJsonObject>
 #include <QJsonArray>
+#include <QLoggingCategory>
 
 #include <qsslconfiguration.h>
 #include <qt5keychain/keychain.h>
@@ -93,6 +97,7 @@ QString Account::davPath() const
 void Account::setSharedThis(AccountPtr sharedThis)
 {
     _sharedThis = sharedThis.toWeakRef();
+    setupUserStatusConnector();
 }
 
 QString Account::davPathBase()
@@ -337,6 +342,24 @@ QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url,
     return _am->sendCustomRequest(req, verb, data);
 }
 
+QNetworkReply *Account::sendRawRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, const QByteArray &data)
+{
+    req.setUrl(url);
+    req.setSslConfiguration(this->getOrCreateSslConfig());
+    if (verb == "HEAD" && data.isEmpty()) {
+        return _am->head(req);
+    } else if (verb == "GET" && data.isEmpty()) {
+        return _am->get(req);
+    } else if (verb == "POST") {
+        return _am->post(req, data);
+    } else if (verb == "PUT") {
+        return _am->put(req, data);
+    } else if (verb == "DELETE" && data.isEmpty()) {
+        return _am->deleteResource(req);
+    }
+    return _am->sendCustomRequest(req, verb, data);
+}
+
 SimpleNetworkJob *Account::sendRequest(const QByteArray &verb, const QUrl &url, QNetworkRequest req, QIODevice *data)
 {
     auto job = new SimpleNetworkJob(sharedFromThis());
@@ -544,9 +567,18 @@ void Account::setCapabilities(const QVariantMap &caps)
 {
     _capabilities = Capabilities(caps);
 
+    setupUserStatusConnector();
     trySetupPushNotifications();
 }
 
+void Account::setupUserStatusConnector()
+{
+    _userStatusConnector = std::make_shared<OcsUserStatusConnector>(sharedFromThis());
+    connect(_userStatusConnector.get(), &UserStatusConnector::userStatusFetched, this, [this](const UserStatus &) {
+        emit userStatusChanged();
+    });
+}
+
 QString Account::serverVersion() const
 {
     return _serverVersion;
@@ -744,4 +776,9 @@ PushNotifications *Account::pushNotifications() const
     return _pushNotifications;
 }
 
+std::shared_ptr<UserStatusConnector> Account::userStatusConnector() const
+{
+    return _userStatusConnector;
+}
+
 } // namespace OCC
index 00930b2b3f94cf533be82356451830070167fa16..334425853dc4adbc7536e468564c2d308fd65539 100644 (file)
@@ -55,6 +55,7 @@ using AccountPtr = QSharedPointer<Account>;
 class AccessManager;
 class SimpleNetworkJob;
 class PushNotifications;
+class UserStatusConnector;
 
 /**
  * @brief Reimplement this to handle SSL errors from libsync
@@ -150,6 +151,9 @@ public:
         QNetworkRequest req = QNetworkRequest(),
         QIODevice *data = nullptr);
 
+    QNetworkReply *sendRawRequest(const QByteArray &verb,
+        const QUrl &url, QNetworkRequest req, const QByteArray &data);
+
     /** Create and start network job for a simple one-off request.
      *
      * More complicated requests typically create their own job types.
@@ -251,10 +255,13 @@ public:
     // Check for the directEditing capability
     void fetchDirectEditors(const QUrl &directEditingURL, const QString &directEditingETag);
 
+    void setupUserStatusConnector();
     void trySetupPushNotifications();
     PushNotifications *pushNotifications() const;
     void setPushNotificationsReconnectInterval(int interval);
 
+    std::shared_ptr<UserStatusConnector> userStatusConnector() const;
+
 public slots:
     /// Used when forgetting credentials
     void clearQNAMCache();
@@ -287,6 +294,8 @@ signals:
     void pushNotificationsReady(Account *account);
     void pushNotificationsDisabled(Account *account);
 
+    void userStatusChanged();
+
 protected Q_SLOTS:
     void slotCredentialsFetched();
     void slotCredentialsAsked();
@@ -343,6 +352,8 @@ private:
 
     PushNotifications *_pushNotifications = nullptr;
 
+    std::shared_ptr<UserStatusConnector> _userStatusConnector;
+
     /* IMPORTANT - remove later - FIXME MS@2019-12-07 -->
      * TODO: For "Log out" & "Remove account": Remove client CA certs and KEY!
      *
index 04db298b142d4272d03e8e67410bccb7857ed817..9983821aec34da20a71afa434379bcc822bbbdc7 100644 (file)
@@ -187,13 +187,31 @@ bool Capabilities::chunkingNg() const
     return _capabilities["dav"].toMap()["chunking"].toByteArray() >= "1.0";
 }
 
-bool Capabilities::userStatus() const
+bool Capabilities::userStatusNotification() const
 {
     return _capabilities.contains("notifications") &&
         _capabilities["notifications"].toMap().contains("ocs-endpoints") &&
         _capabilities["notifications"].toMap()["ocs-endpoints"].toStringList().contains("user-status");
 }
 
+bool Capabilities::userStatus() const
+{
+    if (!_capabilities.contains("user_status")) {
+        return false;
+    }
+    const auto userStatusMap = _capabilities["user_status"].toMap();
+    return userStatusMap.value("enabled", false).toBool();
+}
+
+bool Capabilities::userStatusSupportsEmoji() const
+{
+    if (!userStatus()) {
+        return false;
+    }
+    const auto userStatusMap = _capabilities["user_status"].toMap();
+    return userStatusMap.value("supports_emoji", false).toBool();
+}
+
 PushNotificationTypes Capabilities::availablePushNotifications() const
 {
     if (!_capabilities.contains("notify_push")) {
index 078a0cd35469f32cd63e196c1357339fac1b5d68..3040db89059b06920fb717ac0e574c5b51fee6d4 100644 (file)
@@ -58,7 +58,9 @@ public:
     bool sharePublicLinkMultiple() const;
     bool shareResharing() const;
     bool chunkingNg() const;
+    bool userStatusNotification() const;
     bool userStatus() const;
+    bool userStatusSupportsEmoji() const;
 
     /// Returns which kind of push notfications are available
     PushNotificationTypes availablePushNotifications() const;
index 4ea561da34011977cd31164a18775809ade90a14..2a8b1a189296024b16631641d429ed9b9d87db58 100644 (file)
@@ -56,7 +56,8 @@ Q_LOGGING_CATEGORY(lcCse, "nextcloud.sync.clientsideencryption", QtInfoMsg)
 Q_LOGGING_CATEGORY(lcCseDecryption, "nextcloud.e2e", QtInfoMsg)
 Q_LOGGING_CATEGORY(lcCseMetadata, "nextcloud.metadata", QtInfoMsg)
 
-QString baseUrl(){
+QString e2eeBaseUrl()
+{
     return QStringLiteral("ocs/v2.php/apps/end_to_end_encryption/api/v1/");
 }
 
@@ -1180,7 +1181,7 @@ void ClientSideEncryption::generateCSR(const AccountPtr &account, EVP_PKEY *keyP
     qCInfo(lcCse()) << "Returning the certificate";
     qCInfo(lcCse()) << output;
 
-    auto job = new SignPublicKeyApiJob(account, baseUrl() + "public-key", this);
+    auto job = new SignPublicKeyApiJob(account, e2eeBaseUrl() + "public-key", this);
     job->setCsr(output);
 
     connect(job, &SignPublicKeyApiJob::jsonReceived, [this, account](const QJsonDocument& json, int retCode) {
@@ -1212,7 +1213,7 @@ void ClientSideEncryption::encryptPrivateKey(const AccountPtr &account)
     auto cryptedText = EncryptionHelper::encryptPrivateKey(secretKey, EncryptionHelper::privateKeyToPem(_privateKey), salt);
 
     // Send private key to the server
-    auto job = new StorePrivateKeyApiJob(account, baseUrl() + "private-key", this);
+    auto job = new StorePrivateKeyApiJob(account, e2eeBaseUrl() + "private-key", this);
     job->setPrivateKey(cryptedText);
     connect(job, &StorePrivateKeyApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
         Q_UNUSED(doc);
@@ -1296,7 +1297,7 @@ void ClientSideEncryption::decryptPrivateKey(const AccountPtr &account, const QB
 void ClientSideEncryption::getPrivateKeyFromServer(const AccountPtr &account)
 {
     qCInfo(lcCse()) << "Retrieving private key from server";
-    auto job = new JsonApiJob(account, baseUrl() + "private-key", this);
+    auto job = new JsonApiJob(account, e2eeBaseUrl() + "private-key", this);
     connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
             if (retCode == 200) {
                 QString key = doc.object()["ocs"].toObject()["data"].toObject()["private-key"].toString();
@@ -1315,7 +1316,7 @@ void ClientSideEncryption::getPrivateKeyFromServer(const AccountPtr &account)
 void ClientSideEncryption::getPublicKeyFromServer(const AccountPtr &account)
 {
     qCInfo(lcCse()) << "Retrieving public key from server";
-    auto job = new JsonApiJob(account, baseUrl() + "public-key", this);
+    auto job = new JsonApiJob(account, e2eeBaseUrl() + "public-key", this);
     connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
             if (retCode == 200) {
                 QString publicKey = doc.object()["ocs"].toObject()["data"].toObject()["public-keys"].toObject()[account->davUser()].toString();
@@ -1336,7 +1337,7 @@ void ClientSideEncryption::getPublicKeyFromServer(const AccountPtr &account)
 void ClientSideEncryption::fetchAndValidatePublicKeyFromServer(const AccountPtr &account)
 {
     qCInfo(lcCse()) << "Retrieving public key from server";
-    auto job = new JsonApiJob(account, baseUrl() + "server-key", this);
+    auto job = new JsonApiJob(account, e2eeBaseUrl() + "server-key", this);
     connect(job, &JsonApiJob::jsonReceived, [this, account](const QJsonDocument& doc, int retCode) {
         if (retCode == 200) {
             const auto serverPublicKey = doc.object()["ocs"].toObject()["data"].toObject()["public-key"].toString().toLatin1();
index e89586451bc4e597cdc226ea4112db9dca4a9e9e..751c7d4c824fff37765a400d8b665a653e297cb8 100644 (file)
@@ -23,7 +23,7 @@ class ReadPasswordJob;
 
 namespace OCC {
 
-QString baseUrl();
+QString e2eeBaseUrl();
 
 namespace EncryptionHelper {
     QByteArray generateRandomFilename();
index ad8e30c66d290a07d8c1a7ed0bd89d3ebb86946f..71bb8a510609a6abf566f162720803807dc197d8 100644 (file)
@@ -27,7 +27,7 @@ namespace OCC {
 GetMetadataApiJob::GetMetadataApiJob(const AccountPtr& account,
                                     const QByteArray& fileId,
                                     QObject* parent)
-: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
+: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
 {
 }
 
@@ -63,7 +63,7 @@ StoreMetaDataApiJob::StoreMetaDataApiJob(const AccountPtr& account,
                                                  const QByteArray& fileId,
                                                  const QByteArray& b64Metadata,
                                                  QObject* parent)
-: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId), _b64Metadata(b64Metadata)
+: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId), _b64Metadata(b64Metadata)
 {
 }
 
@@ -104,8 +104,8 @@ UpdateMetadataApiJob::UpdateMetadataApiJob(const AccountPtr& account,
                                                  const QByteArray& b64Metadata,
                                                  const QByteArray& token,
                                                  QObject* parent)
-: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent),
-_fileId(fileId),
+: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("meta-data/") + fileId, parent)
+_fileId(fileId),
 _b64Metadata(b64Metadata),
 _token(token)
 {
@@ -154,7 +154,7 @@ UnlockEncryptFolderApiJob::UnlockEncryptFolderApiJob(const AccountPtr& account,
                                                  const QByteArray& fileId,
                                                  const QByteArray& token,
                                                  QObject* parent)
-: AbstractNetworkJob(account, baseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId), _token(token)
+: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId), _token(token)
 {
 }
 
@@ -185,11 +185,10 @@ bool UnlockEncryptFolderApiJob::finished()
 }
 
 
-
 DeleteMetadataApiJob::DeleteMetadataApiJob(const AccountPtr& account,
                                                   const QByteArray& fileId,
                                                  QObject* parent)
-: AbstractNetworkJob(account, baseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
+: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("meta-data/") + fileId, parent), _fileId(fileId)
 {
 }
 
@@ -219,7 +218,7 @@ bool DeleteMetadataApiJob::finished()
 }
 
 LockEncryptFolderApiJob::LockEncryptFolderApiJob(const AccountPtr& account, const QByteArray& fileId, QObject* parent)
-: AbstractNetworkJob(account, baseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId)
+: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("lock/") + fileId, parent), _fileId(fileId)
 {
 }
 
@@ -258,7 +257,7 @@ bool LockEncryptFolderApiJob::finished()
 }
 
 SetEncryptionFlagApiJob::SetEncryptionFlagApiJob(const AccountPtr& account, const QByteArray& fileId, FlagAction flagAction, QObject* parent)
-: AbstractNetworkJob(account, baseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId), _flagAction(flagAction)
+: AbstractNetworkJob(account, e2eeBaseUrl() + QStringLiteral("encrypted/") + fileId, parent), _fileId(fileId), _flagAction(flagAction)
 {
 }
 
diff --git a/src/libsync/datetimeprovider.cpp b/src/libsync/datetimeprovider.cpp
new file mode 100644 (file)
index 0000000..0840723
--- /dev/null
@@ -0,0 +1,17 @@
+#include "datetimeprovider.h"
+
+namespace OCC {
+
+DateTimeProvider::~DateTimeProvider() = default;
+
+QDateTime DateTimeProvider::currentDateTime() const
+{
+    return QDateTime::currentDateTime();
+}
+
+QDate DateTimeProvider::currentDate() const
+{
+    return QDate::currentDate();
+}
+
+}
diff --git a/src/libsync/datetimeprovider.h b/src/libsync/datetimeprovider.h
new file mode 100644 (file)
index 0000000..1525a2e
--- /dev/null
@@ -0,0 +1,18 @@
+#pragma once
+
+#include "owncloudlib.h"
+
+#include <QDateTime>
+
+namespace OCC {
+
+class OWNCLOUDSYNC_EXPORT DateTimeProvider
+{
+public:
+    virtual ~DateTimeProvider();
+
+    virtual QDateTime currentDateTime() const;
+
+    virtual QDate currentDate() const;
+};
+}
index 9224afc769b48b24e47f569713b8f2c77c08c7b7..2800af0d6514c7a1f4945de23a2a68bb3d93735d 100644 (file)
@@ -830,13 +830,49 @@ void JsonApiJob::addRawHeader(const QByteArray &headerName, const QByteArray &va
    _request.setRawHeader(headerName, value);
 }
 
+void JsonApiJob::setBody(const QJsonDocument &body)
+{
+    _body = body.toJson();
+    qCDebug(lcJsonApiJob) << "Set body for request:" << _body;
+    if (!_body.isEmpty()) {
+        _request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
+    }
+}
+
+
+void JsonApiJob::setVerb(Verb value)
+{
+    _verb = value;
+}
+
+
+QByteArray JsonApiJob::verbToString() const
+{
+    switch (_verb) {
+    case Verb::Get:
+        return "GET";
+    case Verb::Post:
+        return "POST";
+    case Verb::Put:
+        return "PUT";
+    case Verb::Delete:
+        return "DELETE";
+    }
+    return "GET";
+}
+
 void JsonApiJob::start()
 {
     addRawHeader("OCS-APIREQUEST", "true");
     auto query = _additionalParams;
     query.addQueryItem(QLatin1String("format"), QLatin1String("json"));
     QUrl url = Utility::concatUrlPath(account()->url(), path(), query);
-    sendRequest(_usePOST ? "POST" : "GET", url, _request);
+    const auto httpVerb = verbToString();
+    if (!_body.isEmpty()) {
+        sendRequest(httpVerb, url, _request, _body);
+    } else {
+        sendRequest(httpVerb, url, _request);
+    }
     AbstractNetworkJob::start();
 }
 
index 45fb12a2231b385264f7c08cae1dc796096da4aa..01cfcdedd7ede47276b591acfcc214b9e85fb1c3 100644 (file)
@@ -22,6 +22,7 @@
 
 #include <QBuffer>
 #include <QUrlQuery>
+#include <QJsonDocument>
 #include <functional>
 
 class QUrl;
@@ -375,6 +376,13 @@ class OWNCLOUDSYNC_EXPORT JsonApiJob : public AbstractNetworkJob
 {
     Q_OBJECT
 public:
+    enum class Verb {
+        Get,
+        Post,
+        Put,
+        Delete,
+    };
+
     explicit JsonApiJob(const AccountPtr &account, const QString &path, QObject *parent = nullptr);
 
     /**
@@ -390,15 +398,9 @@ public:
     void addQueryParams(const QUrlQuery &params);
     void addRawHeader(const QByteArray &headerName, const QByteArray &value);
 
-    /**
-     * @brief usePOST - allow job to do an anonymous POST request instead of GET
-     * @param params: (optional) true for POST, false for GET (default).
-     *
-     * This function needs to be called before start() obviously.
-     */
-    void usePOST(bool usePOST = true) {
-        _usePOST = usePOST;
-    }
+    void setBody(const QJsonDocument &body);
+
+    void setVerb(Verb value);
 
 public slots:
     void start() override;
@@ -429,10 +431,13 @@ signals:
     void allowDesktopNotificationsChanged(bool isAllowed);
 
 private:
+    QByteArray _body;
     QUrlQuery _additionalParams;
     QNetworkRequest _request;
 
-    bool _usePOST = false;
+    Verb _verb = Verb::Get;
+
+    QByteArray verbToString() const;
 };
 
 /**
diff --git a/src/libsync/ocsuserstatusconnector.cpp b/src/libsync/ocsuserstatusconnector.cpp
new file mode 100644 (file)
index 0000000..95f3810
--- /dev/null
@@ -0,0 +1,455 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "ocsuserstatusconnector.h"
+#include "account.h"
+#include "userstatusconnector.h"
+
+#include <networkjobs.h>
+
+#include <QDateTime>
+#include <QtGlobal>
+#include <QJsonDocument>
+#include <QJsonValue>
+#include <QLoggingCategory>
+#include <QString>
+#include <QJsonObject>
+#include <QJsonArray>
+#include <qdatetime.h>
+#include <qjsonarray.h>
+#include <qjsonobject.h>
+#include <qloggingcategory.h>
+
+namespace {
+
+Q_LOGGING_CATEGORY(lcOcsUserStatusConnector, "nextcloud.gui.ocsuserstatusconnector", QtInfoMsg)
+
+OCC::UserStatus::OnlineStatus stringToUserOnlineStatus(const QString &status)
+{
+    // it needs to match the Status enum
+    const QHash<QString, OCC::UserStatus::OnlineStatus> preDefinedStatus {
+        { "online", OCC::UserStatus::OnlineStatus::Online },
+        { "dnd", OCC::UserStatus::OnlineStatus::DoNotDisturb },
+        { "away", OCC::UserStatus::OnlineStatus::Away },
+        { "offline", OCC::UserStatus::OnlineStatus::Offline },
+        { "invisible", OCC::UserStatus::OnlineStatus::Invisible }
+    };
+
+    // api should return invisible, dnd,... toLower() it is to make sure
+    // it matches _preDefinedStatus, otherwise the default is online (0)
+    return preDefinedStatus.value(status.toLower(), OCC::UserStatus::OnlineStatus::Online);
+}
+
+QString onlineStatusToString(OCC::UserStatus::OnlineStatus status)
+{
+    switch (status) {
+    case OCC::UserStatus::OnlineStatus::Online:
+        return QStringLiteral("online");
+    case OCC::UserStatus::OnlineStatus::DoNotDisturb:
+        return QStringLiteral("dnd");
+    case OCC::UserStatus::OnlineStatus::Away:
+        return QStringLiteral("offline");
+    case OCC::UserStatus::OnlineStatus::Offline:
+        return QStringLiteral("offline");
+    case OCC::UserStatus::OnlineStatus::Invisible:
+        return QStringLiteral("invisible");
+    }
+    return QStringLiteral("online");
+}
+
+OCC::Optional<OCC::ClearAt> jsonExtractClearAt(QJsonObject jsonObject)
+{
+    OCC::Optional<OCC::ClearAt> clearAt {};
+    if (jsonObject.contains("clearAt") && !jsonObject.value("clearAt").isNull()) {
+        OCC::ClearAt clearAtValue;
+        clearAtValue._type = OCC::ClearAtType::Timestamp;
+        clearAtValue._timestamp = jsonObject.value("clearAt").toInt();
+        clearAt = clearAtValue;
+    }
+    return clearAt;
+}
+
+OCC::UserStatus jsonExtractUserStatus(QJsonObject json)
+{
+    const auto clearAt = jsonExtractClearAt(json);
+
+    const OCC::UserStatus userStatus(json.value("messageId").toString(),
+        json.value("message").toString().trimmed(),
+        json.value("icon").toString().trimmed(), stringToUserOnlineStatus(json.value("status").toString()),
+        json.value("messageIsPredefined").toBool(false), clearAt);
+
+    return userStatus;
+}
+
+OCC::UserStatus jsonToUserStatus(const QJsonDocument &json)
+{
+    const QJsonObject defaultValues {
+        { "icon", "" },
+        { "message", "" },
+        { "status", "online" },
+        { "messageIsPredefined", "false" },
+        { "statusIsUserDefined", "false" }
+    };
+    const auto retrievedData = json.object().value("ocs").toObject().value("data").toObject(defaultValues);
+    return jsonExtractUserStatus(retrievedData);
+}
+
+quint64 clearAtEndOfToTimestamp(const OCC::ClearAt &clearAt)
+{
+    Q_ASSERT(clearAt._type == OCC::ClearAtType::EndOf);
+
+    if (clearAt._endof == "day") {
+        return QDate::currentDate().addDays(1).startOfDay().toTime_t();
+    } else if (clearAt._endof == "week") {
+        const auto days = Qt::Sunday - QDate::currentDate().dayOfWeek();
+        return QDate::currentDate().addDays(days + 1).startOfDay().toTime_t();
+    }
+    qCWarning(lcOcsUserStatusConnector) << "Can not handle clear at endof day type" << clearAt._endof;
+    return QDateTime::currentDateTime().toTime_t();
+}
+
+quint64 clearAtPeriodToTimestamp(const OCC::ClearAt &clearAt)
+{
+    return QDateTime::currentDateTime().addSecs(clearAt._period).toTime_t();
+}
+
+quint64 clearAtToTimestamp(const OCC::ClearAt &clearAt)
+{
+    switch (clearAt._type) {
+    case OCC::ClearAtType::Period: {
+        return clearAtPeriodToTimestamp(clearAt);
+    }
+
+    case OCC::ClearAtType::EndOf: {
+        return clearAtEndOfToTimestamp(clearAt);
+    }
+
+    case OCC::ClearAtType::Timestamp: {
+        return clearAt._timestamp;
+    }
+    }
+
+    return 0;
+}
+
+quint64 clearAtToTimestamp(const OCC::Optional<OCC::ClearAt> &clearAt)
+{
+    if (clearAt) {
+        return clearAtToTimestamp(*clearAt);
+    }
+    return 0;
+}
+
+OCC::Optional<OCC::ClearAt> jsonToClearAt(QJsonObject jsonObject)
+{
+    OCC::Optional<OCC::ClearAt> clearAt;
+
+    if (jsonObject.value("clearAt").isObject() && !jsonObject.value("clearAt").isNull()) {
+        OCC::ClearAt clearAtValue;
+        const auto clearAtObject = jsonObject.value("clearAt").toObject();
+        const auto typeValue = clearAtObject.value("type").toString("period");
+        if (typeValue == "period") {
+            const auto timeValue = clearAtObject.value("time").toInt(0);
+            clearAtValue._type = OCC::ClearAtType::Period;
+            clearAtValue._period = timeValue;
+        } else if (typeValue == "end-of") {
+            const auto timeValue = clearAtObject.value("time").toString("day");
+            clearAtValue._type = OCC::ClearAtType::EndOf;
+            clearAtValue._endof = timeValue;
+        } else {
+            qCWarning(lcOcsUserStatusConnector) << "Can not handle clear type value" << typeValue;
+        }
+        clearAt = clearAtValue;
+    }
+
+    return clearAt;
+}
+
+OCC::UserStatus jsonToUserStatus(QJsonObject jsonObject)
+{
+    const auto clearAt = jsonToClearAt(jsonObject);
+
+    OCC::UserStatus userStatus(
+        jsonObject.value("id").toString("no-id"),
+        jsonObject.value("message").toString("No message"),
+        jsonObject.value("icon").toString("no-icon"),
+        OCC::UserStatus::OnlineStatus::Online,
+        true,
+        clearAt);
+
+    return userStatus;
+}
+
+std::vector<OCC::UserStatus> jsonToPredefinedStatuses(QJsonArray jsonDataArray)
+{
+    std::vector<OCC::UserStatus> statuses;
+    for (const auto &jsonEntry : jsonDataArray) {
+        Q_ASSERT(jsonEntry.isObject());
+        if (!jsonEntry.isObject()) {
+            continue;
+        }
+        statuses.push_back(jsonToUserStatus(jsonEntry.toObject()));
+    }
+
+    return statuses;
+}
+
+
+const QString baseUrl("/ocs/v2.php/apps/user_status/api/v1");
+const QString userStatusBaseUrl = baseUrl + QStringLiteral("/user_status");
+}
+
+namespace OCC {
+
+OcsUserStatusConnector::OcsUserStatusConnector(AccountPtr account, QObject *parent)
+    : UserStatusConnector(parent)
+    , _account(account)
+{
+    Q_ASSERT(_account);
+    _userStatusSupported = _account->capabilities().userStatus();
+    _userStatusEmojisSupported = _account->capabilities().userStatusSupportsEmoji();
+}
+
+void OcsUserStatusConnector::fetchUserStatus()
+{
+    qCDebug(lcOcsUserStatusConnector) << "Try to fetch user status";
+
+    if (!_userStatusSupported) {
+        qCDebug(lcOcsUserStatusConnector) << "User status not supported";
+        emit error(Error::UserStatusNotSupported);
+        return;
+    }
+
+    startFetchUserStatusJob();
+}
+
+void OcsUserStatusConnector::startFetchUserStatusJob()
+{
+    if (_getUserStatusJob) {
+        qCDebug(lcOcsUserStatusConnector) << "Get user status job is already running.";
+        return;
+    }
+
+    _getUserStatusJob = new JsonApiJob(_account, userStatusBaseUrl, this);
+    connect(_getUserStatusJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onUserStatusFetched);
+    _getUserStatusJob->start();
+}
+
+void OcsUserStatusConnector::onUserStatusFetched(const QJsonDocument &json, int statusCode)
+{
+    logResponse("user status fetched", json, statusCode);
+
+    if (statusCode != 200) {
+        qCInfo(lcOcsUserStatusConnector) << "Slot fetch UserStatus finished with status code" << statusCode;
+        emit error(Error::CouldNotFetchUserStatus);
+        return;
+    }
+
+    _userStatus = jsonToUserStatus(json);
+    emit userStatusFetched(_userStatus);
+}
+
+void OcsUserStatusConnector::startFetchPredefinedStatuses()
+{
+    if (_getPredefinedStausesJob) {
+        qCDebug(lcOcsUserStatusConnector) << "Get predefined statuses job is already running";
+        return;
+    }
+
+    _getPredefinedStausesJob = new JsonApiJob(_account,
+        baseUrl + QStringLiteral("/predefined_statuses"), this);
+    connect(_getPredefinedStausesJob, &JsonApiJob::jsonReceived, this,
+        &OcsUserStatusConnector::onPredefinedStatusesFetched);
+    _getPredefinedStausesJob->start();
+}
+
+void OcsUserStatusConnector::fetchPredefinedStatuses()
+{
+    if (!_userStatusSupported) {
+        emit error(Error::UserStatusNotSupported);
+        return;
+    }
+    startFetchPredefinedStatuses();
+}
+
+void OcsUserStatusConnector::onPredefinedStatusesFetched(const QJsonDocument &json, int statusCode)
+{
+    logResponse("predefined statuses", json, statusCode);
+
+    if (statusCode != 200) {
+        qCInfo(lcOcsUserStatusConnector) << "Slot predefined user statuses finished with status code" << statusCode;
+        emit error(Error::CouldNotFetchPredefinedUserStatuses);
+        return;
+    }
+    const auto jsonData = json.object().value("ocs").toObject().value("data");
+    Q_ASSERT(jsonData.isArray());
+    if (!jsonData.isArray()) {
+        return;
+    }
+    const auto statuses = jsonToPredefinedStatuses(jsonData.toArray());
+    emit predefinedStatusesFetched(statuses);
+}
+
+void OcsUserStatusConnector::logResponse(const QString &message, const QJsonDocument &json, int statusCode)
+{
+    qCDebug(lcOcsUserStatusConnector) << "Response from:" << message << "Status:" << statusCode << "Json:" << json;
+}
+
+void OcsUserStatusConnector::setUserStatusOnlineStatus(UserStatus::OnlineStatus onlineStatus)
+{
+    _setOnlineStatusJob = new JsonApiJob(_account,
+        userStatusBaseUrl + QStringLiteral("/status"), this);
+    _setOnlineStatusJob->setVerb(JsonApiJob::Verb::Put);
+    // Set body
+    QJsonObject dataObject;
+    dataObject.insert("statusType", onlineStatusToString(onlineStatus));
+    QJsonDocument body;
+    body.setObject(dataObject);
+    _setOnlineStatusJob->setBody(body);
+    connect(_setOnlineStatusJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onUserStatusOnlineStatusSet);
+    _setOnlineStatusJob->start();
+}
+
+void OcsUserStatusConnector::setUserStatusMessagePredefined(const UserStatus &userStatus)
+{
+    Q_ASSERT(userStatus.messagePredefined());
+    if (!userStatus.messagePredefined()) {
+        return;
+    }
+
+    _setMessageJob = new JsonApiJob(_account, userStatusBaseUrl + QStringLiteral("/message/predefined"), this);
+    _setMessageJob->setVerb(JsonApiJob::Verb::Put);
+    // Set body
+    QJsonObject dataObject;
+    dataObject.insert("messageId", userStatus.id());
+    if (userStatus.clearAt()) {
+        dataObject.insert("clearAt", static_cast<int>(clearAtToTimestamp(userStatus.clearAt())));
+    } else {
+        dataObject.insert("clearAt", QJsonValue());
+    }
+    QJsonDocument body;
+    body.setObject(dataObject);
+    _setMessageJob->setBody(body);
+    connect(_setMessageJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onUserStatusMessageSet);
+    _setMessageJob->start();
+}
+
+void OcsUserStatusConnector::setUserStatusMessageCustom(const UserStatus &userStatus)
+{
+    Q_ASSERT(!userStatus.messagePredefined());
+    if (userStatus.messagePredefined()) {
+        return;
+    }
+
+    if (!_userStatusEmojisSupported) {
+        emit error(Error::EmojisNotSupported);
+        return;
+    }
+    _setMessageJob = new JsonApiJob(_account, userStatusBaseUrl + QStringLiteral("/message/custom"), this);
+    _setMessageJob->setVerb(JsonApiJob::Verb::Put);
+    // Set body
+    QJsonObject dataObject;
+    dataObject.insert("statusIcon", userStatus.icon());
+    dataObject.insert("message", userStatus.message());
+    const auto clearAt = userStatus.clearAt();
+    if (clearAt) {
+        dataObject.insert("clearAt", static_cast<int>(clearAtToTimestamp(*clearAt)));
+    } else {
+        dataObject.insert("clearAt", QJsonValue());
+    }
+    QJsonDocument body;
+    body.setObject(dataObject);
+    _setMessageJob->setBody(body);
+    connect(_setMessageJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onUserStatusMessageSet);
+    _setMessageJob->start();
+}
+
+void OcsUserStatusConnector::setUserStatusMessage(const UserStatus &userStatus)
+{
+    if (userStatus.messagePredefined()) {
+        setUserStatusMessagePredefined(userStatus);
+        return;
+    }
+    setUserStatusMessageCustom(userStatus);
+}
+
+void OcsUserStatusConnector::setUserStatus(const UserStatus &userStatus)
+{
+    if (!_userStatusSupported) {
+        emit error(Error::UserStatusNotSupported);
+        return;
+    }
+
+    if (_setOnlineStatusJob || _setMessageJob) {
+        qCDebug(lcOcsUserStatusConnector) << "Set online status job or set message job are already running.";
+        return;
+    }
+
+    setUserStatusOnlineStatus(userStatus.state());
+    setUserStatusMessage(userStatus);
+}
+
+void OcsUserStatusConnector::onUserStatusOnlineStatusSet(const QJsonDocument &json, int statusCode)
+{
+    logResponse("Online status set", json, statusCode);
+
+    if (statusCode != 200) {
+        emit error(Error::CouldNotSetUserStatus);
+        return;
+    }
+}
+
+void OcsUserStatusConnector::onUserStatusMessageSet(const QJsonDocument &json, int statusCode)
+{
+    logResponse("Message set", json, statusCode);
+
+    if (statusCode != 200) {
+        emit error(Error::CouldNotSetUserStatus);
+        return;
+    }
+
+    // We fetch the user status again because json does not contain
+    // the new message when user status was set from a predefined
+    // message
+    fetchUserStatus();
+
+    emit userStatusSet();
+}
+
+void OcsUserStatusConnector::clearMessage()
+{
+    _clearMessageJob = new JsonApiJob(_account, userStatusBaseUrl + QStringLiteral("/message"));
+    _clearMessageJob->setVerb(JsonApiJob::Verb::Delete);
+    connect(_clearMessageJob, &JsonApiJob::jsonReceived, this, &OcsUserStatusConnector::onMessageCleared);
+    _clearMessageJob->start();
+}
+
+UserStatus OcsUserStatusConnector::userStatus() const
+{
+    return _userStatus;
+}
+
+void OcsUserStatusConnector::onMessageCleared(const QJsonDocument &json, int statusCode)
+{
+    logResponse("Message cleared", json, statusCode);
+
+    if (statusCode != 200) {
+        emit error(Error::CouldNotClearMessage);
+        return;
+    }
+
+    _userStatus = {};
+    emit messageCleared();
+}
+}
diff --git a/src/libsync/ocsuserstatusconnector.h b/src/libsync/ocsuserstatusconnector.h
new file mode 100644 (file)
index 0000000..0d36641
--- /dev/null
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#pragma once
+
+#include "accountfwd.h"
+#include "userstatusconnector.h"
+
+#include <QPointer>
+
+namespace OCC {
+
+class JsonApiJob;
+class SimpleNetworkJob;
+
+class OWNCLOUDSYNC_EXPORT OcsUserStatusConnector : public UserStatusConnector
+{
+public:
+    explicit OcsUserStatusConnector(AccountPtr account, QObject *parent = nullptr);
+
+    void fetchUserStatus() override;
+
+    void fetchPredefinedStatuses() override;
+
+    void setUserStatus(const UserStatus &userStatus) override;
+
+    void clearMessage() override;
+
+    UserStatus userStatus() const override;
+
+private:
+    void onUserStatusFetched(const QJsonDocument &json, int statusCode);
+    void onPredefinedStatusesFetched(const QJsonDocument &json, int statusCode);
+    void onUserStatusOnlineStatusSet(const QJsonDocument &json, int statusCode);
+    void onUserStatusMessageSet(const QJsonDocument &json, int statusCode);
+    void onMessageCleared(const QJsonDocument &json, int statusCode);
+
+    void logResponse(const QString &message, const QJsonDocument &json, int statusCode);
+    void startFetchUserStatusJob();
+    void startFetchPredefinedStatuses();
+    void setUserStatusOnlineStatus(UserStatus::OnlineStatus onlineStatus);
+    void setUserStatusMessage(const UserStatus &userStatus);
+    void setUserStatusMessagePredefined(const UserStatus &userStatus);
+    void setUserStatusMessageCustom(const UserStatus &userStatus);
+
+    AccountPtr _account;
+
+    bool _userStatusSupported = false;
+    bool _userStatusEmojisSupported = false;
+
+    QPointer<JsonApiJob> _clearMessageJob {};
+    QPointer<JsonApiJob> _setMessageJob {};
+    QPointer<JsonApiJob> _setOnlineStatusJob {};
+    QPointer<JsonApiJob> _getPredefinedStausesJob {};
+    QPointer<JsonApiJob> _getUserStatusJob {};
+
+    UserStatus _userStatus;
+};
+}
diff --git a/src/libsync/userstatusconnector.cpp b/src/libsync/userstatusconnector.cpp
new file mode 100644 (file)
index 0000000..178e8d7
--- /dev/null
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "userstatusconnector.h"
+#include "theme.h"
+
+namespace OCC {
+
+UserStatus::UserStatus() = default;
+
+UserStatus::UserStatus(
+    const QString &id, const QString &message, const QString &icon,
+    OnlineStatus state, bool messagePredefined, const Optional<ClearAt> &clearAt)
+    : _id(id)
+    , _message(message)
+    , _icon(icon)
+    , _state(state)
+    , _messagePredefined(messagePredefined)
+    , _clearAt(clearAt)
+{
+}
+
+QString UserStatus::id() const
+{
+    return _id;
+}
+
+QString UserStatus::message() const
+{
+    return _message;
+}
+
+QString UserStatus::icon() const
+{
+    return _icon;
+}
+
+auto UserStatus::state() const -> OnlineStatus
+{
+    return _state;
+}
+
+bool UserStatus::messagePredefined() const
+{
+    return _messagePredefined;
+}
+
+QUrl UserStatus::stateIcon() const
+{
+    switch (_state) {
+    case UserStatus::OnlineStatus::Away:
+        return Theme::instance()->statusAwayImageSource();
+
+    case UserStatus::OnlineStatus::DoNotDisturb:
+        return Theme::instance()->statusDoNotDisturbImageSource();
+
+    case UserStatus::OnlineStatus::Invisible:
+    case UserStatus::OnlineStatus::Offline:
+        return Theme::instance()->statusInvisibleImageSource();
+
+    case UserStatus::OnlineStatus::Online:
+        return Theme::instance()->statusOnlineImageSource();
+    }
+
+    Q_UNREACHABLE();
+}
+
+Optional<ClearAt> UserStatus::clearAt() const
+{
+    return _clearAt;
+}
+
+void UserStatus::setId(const QString &id)
+{
+    _id = id;
+}
+
+void UserStatus::setMessage(const QString &message)
+{
+    _message = message;
+}
+
+void UserStatus::setState(OnlineStatus state)
+{
+    _state = state;
+}
+
+void UserStatus::setIcon(const QString &icon)
+{
+    _icon = icon;
+}
+
+void UserStatus::setMessagePredefined(bool value)
+{
+    _messagePredefined = value;
+}
+
+void UserStatus::setClearAt(const Optional<ClearAt> &dateTime)
+{
+    _clearAt = dateTime;
+}
+
+
+UserStatusConnector::UserStatusConnector(QObject *parent)
+    : QObject(parent)
+{
+}
+
+UserStatusConnector::~UserStatusConnector() = default;
+}
diff --git a/src/libsync/userstatusconnector.h b/src/libsync/userstatusconnector.h
new file mode 100644 (file)
index 0000000..d5593fe
--- /dev/null
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#pragma once
+
+#include "common/result.h"
+#include "owncloudlib.h"
+
+#include <QObject>
+#include <QString>
+#include <QMetaType>
+#include <QUrl>
+#include <QDateTime>
+#include <QtGlobal>
+#include <QVariant>
+
+#include <vector>
+
+
+namespace OCC {
+
+enum class OWNCLOUDSYNC_EXPORT ClearAtType {
+    Period,
+    EndOf,
+    Timestamp
+};
+
+// TODO: If we can use C++17 make it a std::variant
+struct OWNCLOUDSYNC_EXPORT ClearAt
+{
+    ClearAtType _type = ClearAtType::Period;
+
+    quint64 _timestamp;
+    int _period;
+    QString _endof;
+};
+
+class OWNCLOUDSYNC_EXPORT UserStatus
+{
+    Q_GADGET
+
+    Q_PROPERTY(QString id MEMBER _id)
+    Q_PROPERTY(QString message MEMBER _message)
+    Q_PROPERTY(QString icon MEMBER _icon)
+    Q_PROPERTY(OnlineStatus state MEMBER _state)
+
+public:
+    enum class OnlineStatus : quint8 {
+        Online,
+        DoNotDisturb,
+        Away,
+        Offline,
+        Invisible
+    };
+    Q_ENUM(OnlineStatus);
+
+    UserStatus();
+
+    UserStatus(const QString &id, const QString &message, const QString &icon,
+        OnlineStatus state, bool messagePredefined, const Optional<ClearAt> &clearAt = {});
+
+    Q_REQUIRED_RESULT QString id() const;
+    Q_REQUIRED_RESULT QString message() const;
+    Q_REQUIRED_RESULT QString icon() const;
+    Q_REQUIRED_RESULT OnlineStatus state() const;
+    Q_REQUIRED_RESULT Optional<ClearAt> clearAt() const;
+
+    void setId(const QString &id);
+    void setMessage(const QString &message);
+    void setState(OnlineStatus state);
+    void setIcon(const QString &icon);
+    void setMessagePredefined(bool value);
+    void setClearAt(const Optional<ClearAt> &dateTime);
+
+    Q_REQUIRED_RESULT bool messagePredefined() const;
+
+    Q_REQUIRED_RESULT QUrl stateIcon() const;
+
+private:
+    QString _id;
+    QString _message;
+    QString _icon;
+    OnlineStatus _state = OnlineStatus::Online;
+    bool _messagePredefined;
+    Optional<ClearAt> _clearAt;
+};
+
+class OWNCLOUDSYNC_EXPORT UserStatusConnector : public QObject
+{
+    Q_OBJECT
+
+public:
+    enum class Error {
+        CouldNotFetchUserStatus,
+        CouldNotFetchPredefinedUserStatuses,
+        UserStatusNotSupported,
+        EmojisNotSupported,
+        CouldNotSetUserStatus,
+        CouldNotClearMessage
+    };
+    Q_ENUM(Error)
+
+    explicit UserStatusConnector(QObject *parent = nullptr);
+
+    ~UserStatusConnector() override;
+
+    virtual void fetchUserStatus() = 0;
+
+    virtual void fetchPredefinedStatuses() = 0;
+
+    virtual void setUserStatus(const UserStatus &userStatus) = 0;
+
+    virtual void clearMessage() = 0;
+
+    virtual UserStatus userStatus() const = 0;
+
+signals:
+    void userStatusFetched(const UserStatus &userStatus);
+    void predefinedStatusesFetched(const std::vector<UserStatus> &statuses);
+    void userStatusSet();
+    void messageCleared();
+    void error(Error error);
+};
+}
+
+Q_DECLARE_METATYPE(OCC::UserStatusConnector *)
+Q_DECLARE_METATYPE(OCC::UserStatus)
index 9245209413a864bdb3896f950b6b82e9399505de..1197d1cbd355c3b9f22b37b20db85cd3a9860aef 100644 (file)
@@ -59,6 +59,7 @@ nextcloud_add_test(PushNotifications)
 nextcloud_add_test(Theme)
 nextcloud_add_test(IconUtils)
 nextcloud_add_test(NotificationCache)
+nextcloud_add_test(SetUserStatusDialog)
 
 if( UNIX AND NOT APPLE )
     nextcloud_add_test(InotifyWatcher)
index 1d85d0a375a82fd5d87966325013342d887c6b5b..05259f8f6ba5cef1914b254fb9d15351dd5ced20 100644 (file)
@@ -138,6 +138,82 @@ private slots:
 
         QCOMPARE(capabilities.pushNotificationsWebSocketUrl(), websocketUrl);
     }
+
+    void testUserStatus_userStatusAvailable_returnTrue()
+    {
+        QVariantMap userStatusMap;
+        userStatusMap["enabled"] = true;
+
+        QVariantMap capabilitiesMap;
+        capabilitiesMap["user_status"] = userStatusMap;
+
+        const OCC::Capabilities capabilities(capabilitiesMap);
+
+        QVERIFY(capabilities.userStatus());
+    }
+
+    void testUserStatus_userStatusNotAvailable_returnFalse()
+    {
+        QVariantMap userStatusMap;
+        userStatusMap["enabled"] = false;
+
+        QVariantMap capabilitiesMap;
+        capabilitiesMap["user_status"] = userStatusMap;
+
+        const OCC::Capabilities capabilities(capabilitiesMap);
+
+        QVERIFY(!capabilities.userStatus());
+    }
+
+    void testUserStatus_userStatusNotInCapabilites_returnFalse()
+    {
+        QVariantMap capabilitiesMap;
+
+        const OCC::Capabilities capabilities(capabilitiesMap);
+
+        QVERIFY(!capabilities.userStatus());
+    }
+
+    void testUserStatusSupportsEmoji_supportsEmojiAvailable_returnTrue()
+    {
+        QVariantMap userStatusMap;
+        userStatusMap["enabled"] = true;
+        userStatusMap["supports_emoji"] = true;
+
+        QVariantMap capabilitiesMap;
+        capabilitiesMap["user_status"] = userStatusMap;
+
+        const OCC::Capabilities capabilities(capabilitiesMap);
+
+        QVERIFY(capabilities.userStatus());
+    }
+
+    void testUserStatusSupportsEmoji_supportsEmojiNotAvailable_returnFalse()
+    {
+        QVariantMap userStatusMap;
+        userStatusMap["enabled"] = true;
+        userStatusMap["supports_emoji"] = false;
+
+        QVariantMap capabilitiesMap;
+        capabilitiesMap["user_status"] = userStatusMap;
+
+        const OCC::Capabilities capabilities(capabilitiesMap);
+
+        QVERIFY(!capabilities.userStatusSupportsEmoji());
+    }
+
+    void testUserStatusSupportsEmoji_supportsEmojiNotInCapabilites_returnFalse()
+    {
+        QVariantMap userStatusMap;
+        userStatusMap["enabled"] = true;
+
+        QVariantMap capabilitiesMap;
+        capabilitiesMap["user_status"] = userStatusMap;
+
+        const OCC::Capabilities capabilities(capabilitiesMap);
+
+        QVERIFY(!capabilities.userStatusSupportsEmoji());
+    }
 };
 
 QTEST_GUILESS_MAIN(TestCapabilities)
index b7e00583f4d54a574b6daa83f25a4d4efa479a54..9345918e50111ec2a77236657b9cec231a754b20 100644 (file)
@@ -65,6 +65,20 @@ class TestPushNotifications : public QObject
     Q_OBJECT
 
 private slots:
+    void testTryReconnect_capabilitesReportPushNotificationsAvailable_reconnectForEver()
+    {
+        FakeWebSocketServer fakeServer;
+        auto account = FakeWebSocketServer::createAccount();
+        account->setPushNotificationsReconnectInterval(0);
+
+        // Let if fail a few times
+        QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
+        QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
+
+        // Push notifications should try to reconnect
+        QVERIFY(fakeServer.authenticateAccount(account));
+    }
+
     void testSetup_correctCredentials_authenticateAndEmitReady()
     {
         FakeWebSocketServer fakeServer;
@@ -272,20 +286,6 @@ private slots:
                 QVERIFY(verifyCalledOnceWithAccount(*activitiesChangedSpy, account));
             }));
     }
-
-    void testTryReconnect_capabilitesReportPushNotificationsAvailable_reconnectForEver()
-    {
-        FakeWebSocketServer fakeServer;
-        auto account = FakeWebSocketServer::createAccount();
-        account->setPushNotificationsReconnectInterval(0);
-
-        // Let if fail a few times
-        QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
-        QVERIFY(failThreeAuthenticationAttempts(fakeServer, account));
-
-        // Push notifications should try to reconnect
-        QVERIFY(fakeServer.authenticateAccount(account));
-    }
 };
 
 QTEST_GUILESS_MAIN(TestPushNotifications)
diff --git a/test/testsetuserstatusdialog.cpp b/test/testsetuserstatusdialog.cpp
new file mode 100644 (file)
index 0000000..0454638
--- /dev/null
@@ -0,0 +1,747 @@
+/*
+ * Copyright (C) by Felix Weilbach <felix.weilbach@nextcloud.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+
+#include "userstatusconnector.h"
+#include "userstatusselectormodel.h"
+
+#include <QTest>
+#include <QSignalSpy>
+#include <QDateTime>
+
+#include <memory>
+
+class FakeUserStatusConnector : public OCC::UserStatusConnector
+{
+public:
+    void fetchUserStatus() override
+    {
+        if (_couldNotFetchUserStatus) {
+            emit error(Error::CouldNotFetchUserStatus);
+            return;
+        } else if (_userStatusNotSupported) {
+            emit error(Error::UserStatusNotSupported);
+            return;
+        } else if (_emojisNotSupported) {
+            emit error(Error::EmojisNotSupported);
+            return;
+        }
+
+        emit userStatusFetched(_userStatus);
+    }
+
+    void fetchPredefinedStatuses() override
+    {
+        if (_couldNotFetchPredefinedUserStatuses) {
+            emit error(Error::CouldNotFetchPredefinedUserStatuses);
+            return;
+        }
+        emit predefinedStatusesFetched(_predefinedStatuses);
+    }
+
+    void setUserStatus(const OCC::UserStatus &userStatus) override
+    {
+        if (_couldNotSetUserStatusMessage) {
+            emit error(Error::CouldNotSetUserStatus);
+            return;
+        }
+
+        _userStatusSetByCallerOfSetUserStatus = userStatus;
+        emit UserStatusConnector::userStatusSet();
+    }
+
+    void clearMessage() override
+    {
+        if (_couldNotClearUserStatusMessage) {
+            emit error(Error::CouldNotClearMessage);
+        } else {
+            _isMessageCleared = true;
+        }
+    }
+
+    OCC::UserStatus userStatus() const override
+    {
+        return {}; // Not implemented
+    }
+
+    void setFakeUserStatus(const OCC::UserStatus &userStatus)
+    {
+        _userStatus = userStatus;
+    }
+
+    void setFakePredefinedStatuses(
+        const std::vector<OCC::UserStatus> &statuses)
+    {
+        _predefinedStatuses = statuses;
+    }
+
+    OCC::UserStatus userStatusSetByCallerOfSetUserStatus() const { return _userStatusSetByCallerOfSetUserStatus; }
+
+    bool messageCleared() const { return _isMessageCleared; }
+
+    void setErrorCouldNotFetchPredefinedUserStatuses(bool value)
+    {
+        _couldNotFetchPredefinedUserStatuses = value;
+    }
+
+    void setErrorCouldNotFetchUserStatus(bool value)
+    {
+        _couldNotFetchUserStatus = value;
+    }
+
+    void setErrorCouldNotSetUserStatusMessage(bool value)
+    {
+        _couldNotSetUserStatusMessage = value;
+    }
+
+    void setErrorUserStatusNotSupported(bool value)
+    {
+        _userStatusNotSupported = value;
+    }
+
+    void setErrorEmojisNotSupported(bool value)
+    {
+        _emojisNotSupported = value;
+    }
+
+    void setErrorCouldNotClearUserStatusMessage(bool value)
+    {
+        _couldNotClearUserStatusMessage = value;
+    }
+
+private:
+    OCC::UserStatus _userStatusSetByCallerOfSetUserStatus;
+    OCC::UserStatus _userStatus;
+    std::vector<OCC::UserStatus> _predefinedStatuses;
+    bool _isMessageCleared = false;
+    bool _couldNotFetchPredefinedUserStatuses = false;
+    bool _couldNotFetchUserStatus = false;
+    bool _couldNotSetUserStatusMessage = false;
+    bool _userStatusNotSupported = false;
+    bool _emojisNotSupported = false;
+    bool _couldNotClearUserStatusMessage = false;
+};
+
+class FakeDateTimeProvider : public OCC::DateTimeProvider
+{
+public:
+    void setCurrentDateTime(const QDateTime &dateTime) { _dateTime = dateTime; }
+
+    QDateTime currentDateTime() const override { return _dateTime; }
+
+    QDate currentDate() const override { return _dateTime.date(); }
+
+private:
+    QDateTime _dateTime;
+};
+
+static std::vector<OCC::UserStatus>
+createFakePredefinedStatuses(const QDateTime &currentTime)
+{
+    std::vector<OCC::UserStatus> statuses;
+
+    const QString userStatusId("fake-id");
+    const QString userStatusMessage("Predefined status");
+    const QString userStatusIcon("🏖");
+    const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::Online);
+    const bool userStatusMessagePredefined(true);
+    OCC::Optional<OCC::ClearAt> userStatusClearAt;
+    OCC::ClearAt clearAt;
+    clearAt._type = OCC::ClearAtType::Timestamp;
+    clearAt._timestamp = currentTime.addSecs(60 * 60).toTime_t();
+    userStatusClearAt = clearAt;
+
+    statuses.emplace_back(userStatusId, userStatusMessage, userStatusIcon,
+        userStatusState, userStatusMessagePredefined, userStatusClearAt);
+
+    return statuses;
+}
+
+static QDateTime createDateTime(int year = 2021, int month = 7, int day = 27,
+    int hour = 12, int minute = 0, int second = 0)
+{
+    QDate fakeDate(year, month, day);
+    QTime fakeTime(hour, minute, second);
+    QDateTime fakeDateTime;
+
+    fakeDateTime.setDate(fakeDate);
+    fakeDateTime.setTime(fakeTime);
+
+    return fakeDateTime;
+}
+
+class TestSetUserStatusDialog : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void testCtor_fetchStatusAndPredefinedStatuses()
+    {
+        const QDateTime currentDateTime(QDateTime::currentDateTime());
+
+        const QString userStatusId("fake-id");
+        const QString userStatusMessage("Some status");
+        const QString userStatusIcon("❤");
+        const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::DoNotDisturb);
+        const bool userStatusMessagePredefined(false);
+        OCC::Optional<OCC::ClearAt> userStatusClearAt;
+        {
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Timestamp;
+            clearAt._timestamp = currentDateTime.addDays(1).toTime_t();
+            userStatusClearAt = clearAt;
+        }
+
+        const OCC::UserStatus userStatus(userStatusId, userStatusMessage,
+            userStatusIcon, userStatusState, userStatusMessagePredefined, userStatusClearAt);
+
+        const auto fakePredefinedStatuses = createFakePredefinedStatuses(createDateTime());
+
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+        fakeDateTimeProvider->setCurrentDateTime(currentDateTime);
+        fakeUserStatusJob->setFakeUserStatus(userStatus);
+        fakeUserStatusJob->setFakePredefinedStatuses(fakePredefinedStatuses);
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob, std::move(fakeDateTimeProvider));
+
+        // Was user status set correctly?
+        QCOMPARE(model.userStatusMessage(), userStatusMessage);
+        QCOMPARE(model.userStatusEmoji(), userStatusIcon);
+        QCOMPARE(model.onlineStatus(), userStatusState);
+        QCOMPARE(model.clearAt(), tr("1 day"));
+
+        // Were predefined statuses fetched correctly?
+        const auto predefinedStatusesCount = model.predefinedStatusesCount();
+        QCOMPARE(predefinedStatusesCount, fakePredefinedStatuses.size());
+        for (int i = 0; i < predefinedStatusesCount; ++i) {
+            const auto predefinedStatus = model.predefinedStatus(i);
+            QCOMPARE(predefinedStatus.id(),
+                fakePredefinedStatuses[i].id());
+            QCOMPARE(predefinedStatus.message(),
+                fakePredefinedStatuses[i].message());
+            QCOMPARE(predefinedStatus.icon(),
+                fakePredefinedStatuses[i].icon());
+            QCOMPARE(predefinedStatus.messagePredefined(),
+                fakePredefinedStatuses[i].messagePredefined());
+        }
+    }
+
+    void testCtor_noStatusSet_showSensibleDefaults()
+    {
+        OCC::UserStatusSelectorModel model(nullptr, nullptr);
+
+        QCOMPARE(model.userStatusMessage(), "");
+        QCOMPARE(model.userStatusEmoji(), "😀");
+        QCOMPARE(model.clearAt(), tr("Don't clear"));
+    }
+
+    void testCtor_fetchStatusButNoStatusSet_showSensibleDefaults()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setFakeUserStatus({ "", "", "",
+            OCC::UserStatus::OnlineStatus::Offline, false, {} });
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        QCOMPARE(model.onlineStatus(), OCC::UserStatus::OnlineStatus::Online);
+        QCOMPARE(model.userStatusMessage(), "");
+        QCOMPARE(model.userStatusEmoji(), "😀");
+        QCOMPARE(model.clearAt(), tr("Don't clear"));
+    }
+
+    void testSetOnlineStatus_emitOnlineStatusChanged()
+    {
+        const OCC::UserStatus::OnlineStatus onlineStatus(OCC::UserStatus::OnlineStatus::Invisible);
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        QSignalSpy onlineStatusChangedSpy(&model,
+            &OCC::UserStatusSelectorModel::onlineStatusChanged);
+
+        model.setOnlineStatus(onlineStatus);
+
+        QCOMPARE(onlineStatusChangedSpy.count(), 1);
+    }
+
+    void testSetUserStatus_setCustomMessage_userStatusSetCorrect()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        QSignalSpy finishedSpy(&model, &OCC::UserStatusSelectorModel::finished);
+
+        const QString userStatusMessage("Some status");
+        const QString userStatusIcon("❤");
+        const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::Online);
+
+        model.setOnlineStatus(userStatusState);
+        model.setUserStatusMessage(userStatusMessage);
+        model.setUserStatusEmoji(userStatusIcon);
+        model.setClearAt(1);
+
+        model.setUserStatus();
+        QCOMPARE(finishedSpy.count(), 1);
+
+        const auto userStatusSet = fakeUserStatusJob->userStatusSetByCallerOfSetUserStatus();
+        QCOMPARE(userStatusSet.icon(), userStatusIcon);
+        QCOMPARE(userStatusSet.message(), userStatusMessage);
+        QCOMPARE(userStatusSet.state(), userStatusState);
+        QCOMPARE(userStatusSet.messagePredefined(), false);
+        const auto clearAt = userStatusSet.clearAt();
+        QVERIFY(clearAt.isValid());
+        QCOMPARE(clearAt->_type, OCC::ClearAtType::Period);
+        QCOMPARE(clearAt->_period, 60 * 30);
+    }
+
+    void testSetUserStatusMessage_predefinedStatusWasSet_userStatusSetCorrect()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setFakePredefinedStatuses(createFakePredefinedStatuses(createDateTime()));
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        model.setPredefinedStatus(0);
+        QSignalSpy finishedSpy(&model, &OCC::UserStatusSelectorModel::finished);
+
+        const QString userStatusMessage("Some status");
+        const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::Online);
+
+        model.setOnlineStatus(userStatusState);
+        model.setUserStatusMessage(userStatusMessage);
+        model.setClearAt(1);
+
+        model.setUserStatus();
+        QCOMPARE(finishedSpy.count(), 1);
+
+        const auto userStatusSet = fakeUserStatusJob->userStatusSetByCallerOfSetUserStatus();
+        QCOMPARE(userStatusSet.message(), userStatusMessage);
+        QCOMPARE(userStatusSet.state(), userStatusState);
+        QCOMPARE(userStatusSet.messagePredefined(), false);
+        const auto clearAt = userStatusSet.clearAt();
+        QVERIFY(clearAt.isValid());
+        QCOMPARE(clearAt->_type, OCC::ClearAtType::Period);
+        QCOMPARE(clearAt->_period, 60 * 30);
+    }
+
+    void testSetUserStatusEmoji_predefinedStatusWasSet_userStatusSetCorrect()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setFakePredefinedStatuses(createFakePredefinedStatuses(createDateTime()));
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        model.setPredefinedStatus(0);
+        QSignalSpy finishedSpy(&model, &OCC::UserStatusSelectorModel::finished);
+
+        const QString userStatusIcon("❤");
+        const OCC::UserStatus::OnlineStatus userStatusState(OCC::UserStatus::OnlineStatus::Online);
+
+        model.setOnlineStatus(userStatusState);
+        model.setUserStatusEmoji(userStatusIcon);
+        model.setClearAt(1);
+
+        model.setUserStatus();
+        QCOMPARE(finishedSpy.count(), 1);
+
+        const auto userStatusSet = fakeUserStatusJob->userStatusSetByCallerOfSetUserStatus();
+        QCOMPARE(userStatusSet.icon(), userStatusIcon);
+        QCOMPARE(userStatusSet.state(), userStatusState);
+        QCOMPARE(userStatusSet.messagePredefined(), false);
+        const auto clearAt = userStatusSet.clearAt();
+        QVERIFY(clearAt.isValid());
+        QCOMPARE(clearAt->_type, OCC::ClearAtType::Period);
+        QCOMPARE(clearAt->_period, 60 * 30);
+    }
+
+    void testSetPredefinedStatus_emitUserStatusChangedAndSetUserStatus()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+        const auto currentTime = createDateTime();
+        fakeDateTimeProvider->setCurrentDateTime(currentTime);
+        const auto fakePredefinedStatuses = createFakePredefinedStatuses(currentTime);
+        fakeUserStatusJob->setFakePredefinedStatuses(fakePredefinedStatuses);
+        OCC::UserStatusSelectorModel model(std::move(fakeUserStatusJob),
+            std::move(fakeDateTimeProvider));
+
+        QSignalSpy userStatusChangedSpy(&model,
+            &OCC::UserStatusSelectorModel::userStatusChanged);
+        QSignalSpy clearAtChangedSpy(&model,
+            &OCC::UserStatusSelectorModel::clearAtChanged);
+
+        const auto fakePredefinedUserStatusIndex = 0;
+        model.setPredefinedStatus(fakePredefinedUserStatusIndex);
+
+        QCOMPARE(userStatusChangedSpy.count(), 1);
+        QCOMPARE(clearAtChangedSpy.count(), 1);
+
+        // Was user status set correctly?
+        const auto fakePredefinedUserStatus = fakePredefinedStatuses[fakePredefinedUserStatusIndex];
+        QCOMPARE(model.userStatusMessage(), fakePredefinedUserStatus.message());
+        QCOMPARE(model.userStatusEmoji(), fakePredefinedUserStatus.icon());
+        QCOMPARE(model.onlineStatus(), fakePredefinedUserStatus.state());
+        QCOMPARE(model.clearAt(), tr("1 hour"));
+    }
+
+    void testSetClear_setClearAtStage0_emitClearAtChangedAndClearAtSet()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
+
+        const auto clearAtIndex = 0;
+        model.setClearAt(clearAtIndex);
+
+        QCOMPARE(clearAtChangedSpy.count(), 1);
+        QCOMPARE(model.clearAt(), tr("Don't clear"));
+    }
+
+    void testSetClear_setClearAtStage1_emitClearAtChangedAndClearAtSet()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
+
+        const auto clearAtIndex = 1;
+        model.setClearAt(clearAtIndex);
+
+        QCOMPARE(clearAtChangedSpy.count(), 1);
+        QCOMPARE(model.clearAt(), tr("30 minutes"));
+    }
+
+    void testSetClear_setClearAtStage2_emitClearAtChangedAndClearAtSet()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
+
+        const auto clearAtIndex = 2;
+        model.setClearAt(clearAtIndex);
+
+        QCOMPARE(clearAtChangedSpy.count(), 1);
+        QCOMPARE(model.clearAt(), tr("1 hour"));
+    }
+
+    void testSetClear_setClearAtStage3_emitClearAtChangedAndClearAtSet()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
+
+        const auto clearAtIndex = 3;
+        model.setClearAt(clearAtIndex);
+
+        QCOMPARE(clearAtChangedSpy.count(), 1);
+        QCOMPARE(model.clearAt(), tr("4 hours"));
+    }
+
+    void testSetClear_setClearAtStage4_emitClearAtChangedAndClearAtSet()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
+
+        const auto clearAtIndex = 4;
+        model.setClearAt(clearAtIndex);
+
+        QCOMPARE(clearAtChangedSpy.count(), 1);
+        QCOMPARE(model.clearAt(), tr("Today"));
+    }
+
+    void testSetClear_setClearAtStage5_emitClearAtChangedAndClearAtSet()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        QSignalSpy clearAtChangedSpy(&model, &OCC::UserStatusSelectorModel::clearAtChanged);
+
+        const auto clearAtIndex = 5;
+        model.setClearAt(clearAtIndex);
+
+        QCOMPARE(clearAtChangedSpy.count(), 1);
+        QCOMPARE(model.clearAt(), tr("This week"));
+    }
+
+    void testClearAtStages()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        QCOMPARE(model.clearAt(), tr("Don't clear"));
+        const auto clearAtValues = model.clearAtValues();
+        QCOMPARE(clearAtValues.count(), 6);
+
+        QCOMPARE(clearAtValues[0], tr("Don't clear"));
+        QCOMPARE(clearAtValues[1], tr("30 minutes"));
+        QCOMPARE(clearAtValues[2], tr("1 hour"));
+        QCOMPARE(clearAtValues[3], tr("4 hours"));
+        QCOMPARE(clearAtValues[4], tr("Today"));
+        QCOMPARE(clearAtValues[5], tr("This week"));
+    }
+
+    void testClearAt_clearAtTimestamp()
+    {
+        const auto currentTime = createDateTime();
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Timestamp;
+            clearAt._timestamp = currentTime.addSecs(30).toTime_t();
+            userStatus.setClearAt(clearAt);
+
+            auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+            fakeDateTimeProvider->setCurrentDateTime(currentTime);
+
+            OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
+
+            QCOMPARE(model.clearAt(), tr("Less than a minute"));
+        }
+
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Timestamp;
+            clearAt._timestamp = currentTime.addSecs(60).toTime_t();
+            userStatus.setClearAt(clearAt);
+
+            auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+            fakeDateTimeProvider->setCurrentDateTime(currentTime);
+
+            OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
+
+            QCOMPARE(model.clearAt(), tr("1 minute"));
+        }
+
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Timestamp;
+            clearAt._timestamp = currentTime.addSecs(60 * 30).toTime_t();
+            userStatus.setClearAt(clearAt);
+
+            auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+            fakeDateTimeProvider->setCurrentDateTime(currentTime);
+
+            OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
+
+            QCOMPARE(model.clearAt(), tr("30 minutes"));
+        }
+
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Timestamp;
+            clearAt._timestamp = currentTime.addSecs(60 * 60).toTime_t();
+            userStatus.setClearAt(clearAt);
+
+            auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+            fakeDateTimeProvider->setCurrentDateTime(currentTime);
+
+            OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
+
+            QCOMPARE(model.clearAt(), tr("1 hour"));
+        }
+
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Timestamp;
+            clearAt._timestamp = currentTime.addSecs(60 * 60 * 4).toTime_t();
+            userStatus.setClearAt(clearAt);
+
+            auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+            fakeDateTimeProvider->setCurrentDateTime(currentTime);
+
+            OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
+
+            QCOMPARE(model.clearAt(), tr("4 hours"));
+        }
+
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Timestamp;
+            clearAt._timestamp = currentTime.addDays(1).toTime_t();
+            userStatus.setClearAt(clearAt);
+
+            auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+            fakeDateTimeProvider->setCurrentDateTime(currentTime);
+
+            OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
+
+            QCOMPARE(model.clearAt(), tr("1 day"));
+        }
+
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Timestamp;
+            clearAt._timestamp = currentTime.addDays(7).toTime_t();
+            userStatus.setClearAt(clearAt);
+
+            auto fakeDateTimeProvider = std::make_unique<FakeDateTimeProvider>();
+            fakeDateTimeProvider->setCurrentDateTime(currentTime);
+
+            OCC::UserStatusSelectorModel model(userStatus, std::move(fakeDateTimeProvider));
+
+            QCOMPARE(model.clearAt(), tr("7 days"));
+        }
+    }
+
+    void testClearAt_clearAtEndOf()
+    {
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::EndOf;
+            clearAt._endof = "day";
+            userStatus.setClearAt(clearAt);
+
+            OCC::UserStatusSelectorModel model(userStatus);
+
+            QCOMPARE(model.clearAt(), tr("Today"));
+        }
+
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::EndOf;
+            clearAt._endof = "week";
+            userStatus.setClearAt(clearAt);
+
+            OCC::UserStatusSelectorModel model(userStatus);
+
+            QCOMPARE(model.clearAt(), tr("This week"));
+        }
+    }
+
+    void testClearAt_clearAtAfterPeriod()
+    {
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Period;
+            clearAt._period = 60 * 30;
+            userStatus.setClearAt(clearAt);
+
+            OCC::UserStatusSelectorModel model(userStatus);
+
+            QCOMPARE(model.clearAt(), tr("30 minutes"));
+        }
+
+        {
+            OCC::UserStatus userStatus;
+            OCC::ClearAt clearAt;
+            clearAt._type = OCC::ClearAtType::Period;
+            clearAt._period = 60 * 60;
+            userStatus.setClearAt(clearAt);
+
+            OCC::UserStatusSelectorModel model(userStatus);
+
+            QCOMPARE(model.clearAt(), tr("1 hour"));
+        }
+    }
+
+    void testClearUserStatus()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        model.clearUserStatus();
+
+        QVERIFY(fakeUserStatusJob->messageCleared());
+    }
+
+    void testError_couldNotFetchPredefinedStatuses_emitError()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setErrorCouldNotFetchPredefinedUserStatuses(true);
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        QCOMPARE(model.errorMessage(),
+            tr("Could not fetch predefined statuses. Make sure you are connected to the server."));
+    }
+
+    void testError_couldNotFetchUserStatus_emitError()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setErrorCouldNotFetchUserStatus(true);
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        QCOMPARE(model.errorMessage(),
+            tr("Could not fetch user status. Make sure you are connected to the server."));
+    }
+
+    void testError_userStatusNotSupported_emitError()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setErrorUserStatusNotSupported(true);
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        QCOMPARE(model.errorMessage(),
+            tr("User status feature is not supported. You will not be able to set your user status."));
+    }
+
+    void testError_couldSetUserStatus_emitError()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(true);
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        model.setUserStatus();
+
+        QCOMPARE(model.errorMessage(),
+            tr("Could not set user status. Make sure you are connected to the server."));
+    }
+
+    void testError_emojisNotSupported_emitError()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setErrorEmojisNotSupported(true);
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        QCOMPARE(model.errorMessage(),
+            tr("Emojis feature is not supported. Some user status functionality may not work."));
+    }
+
+    void testError_couldNotClearMessage_emitError()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        fakeUserStatusJob->setErrorCouldNotClearUserStatusMessage(true);
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+        model.clearUserStatus();
+
+        QCOMPARE(model.errorMessage(),
+            tr("Could not clear user status message. Make sure you are connected to the server."));
+    }
+
+    void testError_setUserStatus_clearErrorMessage()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(true);
+        model.setUserStatus();
+        QVERIFY(!model.errorMessage().isEmpty());
+        fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(false);
+        model.setUserStatus();
+        QVERIFY(model.errorMessage().isEmpty());
+    }
+
+    void testError_clearUserStatus_clearErrorMessage()
+    {
+        auto fakeUserStatusJob = std::make_shared<FakeUserStatusConnector>();
+        OCC::UserStatusSelectorModel model(fakeUserStatusJob);
+
+        fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(true);
+        model.setUserStatus();
+        QVERIFY(!model.errorMessage().isEmpty());
+        fakeUserStatusJob->setErrorCouldNotSetUserStatusMessage(false);
+        model.clearUserStatus();
+        QVERIFY(model.errorMessage().isEmpty());
+    }
+};
+
+QTEST_GUILESS_MAIN(TestSetUserStatusDialog)
+#include "testsetuserstatusdialog.moc"