From 549f1ff7f24b9591b637644fb58bd54f4f4c0203 Mon Sep 17 00:00:00 2001 From: Jonathan Rainville Date: Tue, 28 Jul 2020 16:10:38 -0400 Subject: [PATCH] feat: categorize emojis in the menu and add category buttons Signed-off-by: Jonathan Rainville --- .../Chat/components/EmojiCategoryButton.qml | 55 +++++ .../AppLayouts/Chat/components/EmojiPopup.qml | 177 ++++++++++----- .../Chat/components/EmojiSection.qml | 106 +++++++++ .../AppLayouts/Chat/components/emojiList.js | 202 +++--------------- ui/app/img/emojiCategories/activity.svg | 3 + ui/app/img/emojiCategories/flags.svg | 3 + ui/app/img/emojiCategories/food.svg | 3 + ui/app/img/emojiCategories/nature.svg | 3 + ui/app/img/emojiCategories/objects.svg | 4 + ui/app/img/emojiCategories/recent.svg | 4 + ui/app/img/emojiCategories/smileys.svg | 6 + ui/app/img/emojiCategories/symbols.svg | 3 + ui/app/img/emojiCategories/travel.svg | 4 + ui/nim-status-client.pro | 2 + 14 files changed, 344 insertions(+), 231 deletions(-) create mode 100644 ui/app/AppLayouts/Chat/components/EmojiCategoryButton.qml create mode 100644 ui/app/AppLayouts/Chat/components/EmojiSection.qml create mode 100644 ui/app/img/emojiCategories/activity.svg create mode 100644 ui/app/img/emojiCategories/flags.svg create mode 100644 ui/app/img/emojiCategories/food.svg create mode 100644 ui/app/img/emojiCategories/nature.svg create mode 100644 ui/app/img/emojiCategories/objects.svg create mode 100644 ui/app/img/emojiCategories/recent.svg create mode 100644 ui/app/img/emojiCategories/smileys.svg create mode 100644 ui/app/img/emojiCategories/symbols.svg create mode 100644 ui/app/img/emojiCategories/travel.svg diff --git a/ui/app/AppLayouts/Chat/components/EmojiCategoryButton.qml b/ui/app/AppLayouts/Chat/components/EmojiCategoryButton.qml new file mode 100644 index 0000000000..056cfdf661 --- /dev/null +++ b/ui/app/AppLayouts/Chat/components/EmojiCategoryButton.qml @@ -0,0 +1,55 @@ +import QtQuick 2.13 +import QtGraphicalEffects 1.0 +import "../../../../imports" +import "../../../../shared" + +Rectangle { + property bool active: false + property var changeCategory: function () {} + property url source: "../../../img/emojiCategories/recent.svg" + + id: categoryButton + width: 40 + height: 40 + + SVGImage { + width: 20 + height: 20 + fillMode: Image.PreserveAspectFit + anchors.verticalCenter: parent.verticalCenter + anchors.horizontalCenter: parent.horizontalCenter + source: categoryButton.source + + ColorOverlay { + anchors.fill: parent + source: parent + color: categoryButton.active ? Style.current.blue : Style.current.transparent + } + + Rectangle { + visible: categoryButton.active + width: parent.width + height: 2 + radius: 1 + color: Style.current.blue + anchors.bottom: parent.bottom + anchors.bottomMargin: -Style.current.smallPadding + } + } + + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: function () { + categoryButton.changeCategory() + } + } +} + + + +/*##^## +Designer { + D{i:0;formeditorColor:"#ffffff";height:440;width:360} +} +##^##*/ diff --git a/ui/app/AppLayouts/Chat/components/EmojiPopup.qml b/ui/app/AppLayouts/Chat/components/EmojiPopup.qml index 84b6e57382..10ac0bdde2 100644 --- a/ui/app/AppLayouts/Chat/components/EmojiPopup.qml +++ b/ui/app/AppLayouts/Chat/components/EmojiPopup.qml @@ -10,10 +10,11 @@ import "./emojiList.js" as EmojiJSON Popup { property var addToChat: function () {} + property var categories: [] id: popup modal: false - property int selectedPackId + width: 360 closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent background: Rectangle { radius: Style.current.radius @@ -30,14 +31,26 @@ Popup { } } - ListModel { - id: emojiModel - } - Component.onCompleted: { + var categoryNames = {"recent": 0} + var newCategories = [[]] + EmojiJSON.emoji_json.forEach(function (emoji) { - emojiModel.append({filename: emoji.unicode + '.png'}) + if (!categoryNames[emoji.category] && categoryNames[emoji.category] !== 0) { + categoryNames[emoji.category] = newCategories.length + newCategories.push([]) + } + + newCategories[categoryNames[emoji.category]].push(emoji) }) + if (newCategories[categoryNames.recent].length === 0) { + newCategories[categoryNames.recent].push({ + category: "recent", + empty: true + }) + } + + categories = newCategories } contentItem: ColumnLayout { @@ -45,60 +58,114 @@ Popup { spacing: 0 Item { + property int headerMargin: 8 + + id: emojiHeader Layout.fillWidth: true - Layout.leftMargin: 4 - Layout.rightMargin: 4 - Layout.topMargin: 4 - Layout.bottomMargin: 0 + height: searchBox.height + emojiHeader.headerMargin + + SearchBox { + id: searchBox + anchors.right: skinToneEmoji.left + anchors.rightMargin: emojiHeader.headerMargin + anchors.top: parent.top + anchors.topMargin: emojiHeader.headerMargin + anchors.left: parent.left + anchors.leftMargin: emojiHeader.headerMargin + } + + SVGImage { + id: skinToneEmoji + width: 22 + height: 22 + anchors.verticalCenter: searchBox.verticalCenter + anchors.right: parent.right + anchors.rightMargin: emojiHeader.headerMargin + source: "../../../../imports/twemoji/26x26/1f590.png" + + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: function () { + console.log('Change skin tone') + } + } + } + } + + ScrollView { + property ScrollBar vScrollBar: ScrollBar.vertical + property var categrorySectionHeightRatios: [] + property int activeCategory: 0 + + id: scrollView + topPadding: Style.current.smallPadding + leftPadding: Style.current.smallPadding + rightPadding: Style.current.smallPadding / 2 + Layout.fillWidth: true + Layout.rightMargin: Style.current.smallPadding / 2 + Layout.topMargin: Style.current.smallPadding Layout.alignment: Qt.AlignTop | Qt.AlignLeft - Layout.preferredHeight: 400 - 4 + Layout.preferredHeight: 400 - Style.current.smallPadding - emojiHeader.height + clip: true + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - GridView { - property int imageWidth: 26 - property int imageMargin: 4 + ScrollBar.vertical.onPositionChanged: function () { + if (vScrollBar.position < categrorySectionHeightRatios[scrollView.activeCategory - 1]) { + scrollView.activeCategory-- + } else if (vScrollBar.position > categrorySectionHeightRatios[scrollView.activeCategory]) { + scrollView.activeCategory++ + } + } - id: emojiGrid - visible: count > 0 - anchors.fill: parent - cellWidth: imageWidth + emojiGrid.imageMargin * 2 - cellHeight: imageWidth + emojiGrid.imageMargin * 2 - model: emojiModel - focus: true - clip: true - delegate: Item { - width: emojiGrid.cellWidth - height: emojiGrid.cellHeight - Column { - anchors.fill: parent - anchors.topMargin: emojiGrid.imageMargin - anchors.leftMargin: emojiGrid.imageMargin - SVGImage { - width: emojiGrid.imageWidth - height: emojiGrid.imageWidth - source: "../../../../imports/twemoji/26x26/" + filename - // fillMode: Image.PreserveAspectFit - MouseArea { - cursorShape: Qt.PointingHandCursor - anchors.fill: parent - onClicked: { - const extenstionIndex = filename.lastIndexOf('.'); - let iconCodePoint = filename - if (extenstionIndex > -1) { - iconCodePoint = iconCodePoint.substring(0, extenstionIndex) - } + function scrollToCategory(category) { + if (category === 0) { + return vScrollBar.setPosition(0) + } + vScrollBar.setPosition(categrorySectionHeightRatios[category - 1]) + } - // Split the filename to get all the parts and then encode them from hex to utf8 - const splitCodePoint = iconCodePoint.split('-') - let codePointParts = [] - splitCodePoint.forEach(function (codePoint) { - codePointParts.push(`0x${codePoint}`) - }) - const encodedIcon = String.fromCodePoint(...codePointParts); - popup.addToChat(encodedIcon + ' ') // Adding a space because otherwise, some emojis would fuse since it's just an emoji is just a string - popup.close() - } - } - } + contentHeight: { + var totalHeight = 0 + var categoryHeights = [] + for (let i = 0; i < emojiSectionsRepeater.count; i++) { + totalHeight += emojiSectionsRepeater.itemAt(i).height + Style.current.padding + categoryHeights.push(totalHeight) + } + var ratios = [] + categoryHeights.forEach(function (catHeight) { + ratios.push(catHeight / totalHeight) + }) + + categrorySectionHeightRatios = ratios + return totalHeight + Style.current.padding + } + + Repeater { + id: emojiSectionsRepeater + model: popup.categories + + EmojiSection {} + } + } + + Row { + Layout.fillWidth: true + height: 40 + leftPadding: Style.current.smallPadding / 2 + rightPadding: Style.current.smallPadding / 2 + spacing: 0 + + Repeater { + model: EmojiJSON.emojiCategories + + EmojiCategoryButton { + source: `../../../img/emojiCategories/${modelData}.svg` + active: index === scrollView.activeCategory + changeCategory: function () { + scrollView.activeCategory = index + scrollView.scrollToCategory(index) } } } diff --git a/ui/app/AppLayouts/Chat/components/EmojiSection.qml b/ui/app/AppLayouts/Chat/components/EmojiSection.qml new file mode 100644 index 0000000000..01edd97d8b --- /dev/null +++ b/ui/app/AppLayouts/Chat/components/EmojiSection.qml @@ -0,0 +1,106 @@ +import QtQuick 2.13 +import QtQuick.Layouts 1.3 +import "../../../../imports" +import "../../../../shared" + + +Item { + property int imageWidth: 26 + property int imageMargin: 4 + + id: emojiSection + + anchors.top: index === 0 ? parent.top : parent.children[index - 1].bottom + anchors.topMargin: index === 0 ? 0 : Style.current.padding + + width: parent.width + height: childrenRect.height + Style.current.padding + + StyledText { + id: categoryText + text: modelData && modelData.length ? modelData[0].category.toUpperCase() : "" + color: Style.current.darkGrey + font.pixelSize: 13 + } + + StyledText { + visible: !!(modelData && modelData.length && modelData[0].empty) + text: qsTr("No recent emojis") + color: Style.current.darkGrey + font.pixelSize: 10 + anchors.top: categoryText.bottom + anchors.topMargin: Style.current.smallPadding + } + + Component.onCompleted: { + modelData.forEach(function (emoji) { + if (emoji.empty) { + return + } + emojiModel.append({filename: emoji.unicode + '.png'}) + }) + } + + ListModel { + id: emojiModel + } + + GridView { + id: emojiGrid + anchors.top: categoryText.bottom + anchors.topMargin: Style.current.smallPadding + width: parent.width + height: childrenRect.height + visible: count > 0 + cellWidth: emojiSection.imageWidth + emojiSection.imageMargin * 2 + cellHeight: emojiSection.imageWidth + emojiSection.imageMargin * 2 + model: emojiModel + focus: true + clip: true + interactive: false + + delegate: Item { + width: emojiGrid.cellWidth + height: emojiGrid.cellHeight + + Column { + anchors.fill: parent + anchors.topMargin: emojiSection.imageMargin + anchors.leftMargin: emojiSection.imageMargin + + SVGImage { + width: emojiSection.imageWidth + height: emojiSection.imageWidth + source: "../../../../imports/twemoji/26x26/" + filename + + MouseArea { + cursorShape: Qt.PointingHandCursor + anchors.fill: parent + onClicked: { + const extenstionIndex = filename.lastIndexOf('.'); + let iconCodePoint = filename + if (extenstionIndex > -1) { + iconCodePoint = iconCodePoint.substring(0, extenstionIndex) + } + + // Split the filename to get all the parts and then encode them from hex to utf8 + const splitCodePoint = iconCodePoint.split('-') + let codePointParts = [] + splitCodePoint.forEach(function (codePoint) { + codePointParts.push(`0x${codePoint}`) + }) + const encodedIcon = String.fromCodePoint(...codePointParts); + popup.addToChat(encodedIcon + ' ') // Adding a space because otherwise, some emojis would fuse since it's just an emoji is just a string + popup.close() + } + } + } + } + } + } +} +/*##^## +Designer { + D{i:0;formeditorColor:"#ffffff";height:440;width:360} +} +##^##*/ diff --git a/ui/app/AppLayouts/Chat/components/emojiList.js b/ui/app/AppLayouts/Chat/components/emojiList.js index 586cca74ec..4ca1a651c2 100644 --- a/ui/app/AppLayouts/Chat/components/emojiList.js +++ b/ui/app/AppLayouts/Chat/components/emojiList.js @@ -1,24 +1,16 @@ +var emojiCategories = [ + "recent", + "smileys", + "nature", + "food", + "activity", + "travel", + "objects", + "symbols", + "flags", +] + var emoji_json = [ - { - "unicode": "1f4af", - "unicode_alternates": "", - "name": "hundred points symbol", - "shortname": ":100:", - "category": "symbols", - "emoji_order": "856", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "1f522", - "unicode_alternates": "", - "name": "input symbol for numbers", - "shortname": ":1234:", - "category": "symbols", - "emoji_order": "913", - "aliases": [], - "aliases_ascii": [] - }, { "unicode": "1f600", "unicode_alternates": "", @@ -8749,6 +8741,16 @@ var emoji_json = [ "aliases": [], "aliases_ascii": [] }, + { + "unicode": "1f4af", + "unicode_alternates": "", + "name": "hundred points symbol", + "shortname": ":100:", + "category": "symbols", + "emoji_order": "856", + "aliases": [], + "aliases_ascii": [] + }, { "unicode": "1f505", "unicode_alternates": "", @@ -9200,112 +9202,12 @@ var emoji_json = [ "aliases_ascii": [] }, { - "unicode": "0030-20e3", - "unicode_alternates": "0030-fe0f-20e3", - "name": "keycap digit zero", - "shortname": ":zero:", - "category": "symbols", - "emoji_order": "902", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0031-20e3", - "unicode_alternates": "0031-fe0f-20e3", - "name": "keycap digit one", - "shortname": ":one:", - "category": "symbols", - "emoji_order": "903", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0032-20e3", - "unicode_alternates": "0032-fe0f-20e3", - "name": "keycap digit two", - "shortname": ":two:", - "category": "symbols", - "emoji_order": "904", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0033-20e3", - "unicode_alternates": "0033-fe0f-20e3", - "name": "keycap digit three", - "shortname": ":three:", - "category": "symbols", - "emoji_order": "905", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0034-20e3", - "unicode_alternates": "0034-fe0f-20e3", - "name": "keycap digit four", - "shortname": ":four:", - "category": "symbols", - "emoji_order": "906", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0035-20e3", - "unicode_alternates": "0035-fe0f-20e3", - "name": "keycap digit five", - "shortname": ":five:", - "category": "symbols", - "emoji_order": "907", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0036-20e3", - "unicode_alternates": "0036-fe0f-20e3", - "name": "keycap digit six", - "shortname": ":six:", - "category": "symbols", - "emoji_order": "908", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0037-20e3", - "unicode_alternates": "0037-fe0f-20e3", - "name": "keycap digit seven", - "shortname": ":seven:", - "category": "symbols", - "emoji_order": "909", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0038-20e3", - "unicode_alternates": "0038-fe0f-20e3", - "name": "keycap digit eight", - "shortname": ":eight:", - "category": "symbols", - "emoji_order": "910", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "0039-20e3", - "unicode_alternates": "0039-fe0f-20e3", - "name": "keycap digit nine", - "shortname": ":nine:", - "category": "symbols", - "emoji_order": "911", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "1f51f", + "unicode": "1f522", "unicode_alternates": "", - "name": "keycap ten", - "shortname": ":ten:", + "name": "input symbol for numbers", + "shortname": ":1234:", "category": "symbols", - "emoji_order": "912", + "emoji_order": "913", "aliases": [], "aliases_ascii": [] }, @@ -9635,28 +9537,6 @@ var emoji_json = [ "aliases": [], "aliases_ascii": [] }, - { - "unicode": "0023-20e3", - "unicode_alternates": "0023-fe0f-20e3", - "name": "keycap number sign", - "shortname": ":hash:", - "category": "symbols", - "emoji_order": "946", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "002a-20e3", - "unicode_alternates": "002a-fe0f-20e3", - "name": "keycap asterisk", - "shortname": ":asterisk:", - "category": "symbols", - "emoji_order": "947", - "aliases": [ - ":keycap_asterisk:" - ], - "aliases_ascii": [] - }, { "unicode": "2139", "unicode_alternates": "2139-fe0f", @@ -9827,26 +9707,6 @@ var emoji_json = [ "aliases": [], "aliases_ascii": [] }, - { - "unicode": "00a9", - "unicode_alternates": "00a9-fe0f", - "name": "copyright sign", - "shortname": ":copyright:", - "category": "symbols", - "emoji_order": "965", - "aliases": [], - "aliases_ascii": [] - }, - { - "unicode": "00ae", - "unicode_alternates": "00ae-fe0f", - "name": "registered sign", - "shortname": ":registered:", - "category": "symbols", - "emoji_order": "966", - "aliases": [], - "aliases_ascii": [] - }, { "unicode": "2122", "unicode_alternates": "2122-fe0f", @@ -10549,16 +10409,6 @@ var emoji_json = [ "aliases": [], "aliases_ascii": [] }, - { - "unicode": "1f441-1f5e8", - "unicode_alternates": "1f441-200d-1f5e8", - "name": "eye in speech bubble", - "shortname": ":eye_in_speech_bubble:", - "category": "symbols", - "emoji_order": "1037", - "aliases": [], - "aliases_ascii": [] - }, { "unicode": "1f1e6-1f1e8", "unicode_alternates": "", diff --git a/ui/app/img/emojiCategories/activity.svg b/ui/app/img/emojiCategories/activity.svg new file mode 100644 index 0000000000..3a0741bed7 --- /dev/null +++ b/ui/app/img/emojiCategories/activity.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/app/img/emojiCategories/flags.svg b/ui/app/img/emojiCategories/flags.svg new file mode 100644 index 0000000000..de88cd5b7b --- /dev/null +++ b/ui/app/img/emojiCategories/flags.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/app/img/emojiCategories/food.svg b/ui/app/img/emojiCategories/food.svg new file mode 100644 index 0000000000..e4b8a04f6f --- /dev/null +++ b/ui/app/img/emojiCategories/food.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/app/img/emojiCategories/nature.svg b/ui/app/img/emojiCategories/nature.svg new file mode 100644 index 0000000000..fffc795bce --- /dev/null +++ b/ui/app/img/emojiCategories/nature.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/app/img/emojiCategories/objects.svg b/ui/app/img/emojiCategories/objects.svg new file mode 100644 index 0000000000..c181b1d82a --- /dev/null +++ b/ui/app/img/emojiCategories/objects.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/app/img/emojiCategories/recent.svg b/ui/app/img/emojiCategories/recent.svg new file mode 100644 index 0000000000..46b6cce695 --- /dev/null +++ b/ui/app/img/emojiCategories/recent.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/app/img/emojiCategories/smileys.svg b/ui/app/img/emojiCategories/smileys.svg new file mode 100644 index 0000000000..f5932a8893 --- /dev/null +++ b/ui/app/img/emojiCategories/smileys.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/ui/app/img/emojiCategories/symbols.svg b/ui/app/img/emojiCategories/symbols.svg new file mode 100644 index 0000000000..d4a318886e --- /dev/null +++ b/ui/app/img/emojiCategories/symbols.svg @@ -0,0 +1,3 @@ + + + diff --git a/ui/app/img/emojiCategories/travel.svg b/ui/app/img/emojiCategories/travel.svg new file mode 100644 index 0000000000..edaf25e972 --- /dev/null +++ b/ui/app/img/emojiCategories/travel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/nim-status-client.pro b/ui/nim-status-client.pro index 39c55ad804..fb40e51e3c 100644 --- a/ui/nim-status-client.pro +++ b/ui/nim-status-client.pro @@ -134,8 +134,10 @@ DISTFILES += \ app/AppLayouts/Chat/ChatColumn/MessageComponents/UsernameLabel.qml \ app/AppLayouts/Chat/ChatColumn/MessageComponents/qmldir \ app/AppLayouts/Chat/ContactsColumn/ClosedEmptyView.qml \ + app/AppLayouts/Chat/components/EmojiCategoryButton.qml \ app/AppLayouts/Chat/components/EmojiPopup.qml \ app/AppLayouts/Chat/components/EmojiReaction.qml \ + app/AppLayouts/Chat/components/EmojiSection.qml \ app/AppLayouts/Chat/components/InviteFriendsPopup.qml \ app/AppLayouts/Profile/LeftTab/Constants.js \ app/AppLayouts/Profile/LeftTab/components/MenuButton.qml \