status-desktop/ui/imports/shared/status/StatusEmojiPopup.qml

351 lines
12 KiB
QML

import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import utils 1.0
import shared.panels 1.0
import shared.controls 1.0
Popup {
id: popup
property var categories: []
property alias searchString: searchBox.text
property var skinColors: ["1f3fb", "1f3fc", "1f3fd", "1f3fe", "1f3ff"]
property string emojiSize: ""
signal emojiSelected(string emoji, bool atCu)
modal: false
width: 360
background: Rectangle {
radius: Style.current.radius
color: Style.current.background
border.color: Style.current.border
layer.enabled: true
layer.effect: DropShadow{
verticalOffset: 3
radius: 8
samples: 15
cached: true
color: "#22000000"
}
}
function containsSkinColor(code) {
return skinColors.some(function (color) {
return code.includes(color)
});
}
function addEmoji(emoji) {
const MAX_EMOJI_NUMBER = 36
const extenstionIndex = emoji.filename.lastIndexOf('.');
let iconCodePoint = emoji.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);
// Add at the start of the list
let recentEmojis = localAccountSensitiveSettings.recentEmojis
if (recentEmojis === undefined) {
recentEmojis = []
}
recentEmojis.unshift(emoji)
// Remove duplicates
recentEmojis = recentEmojis.filter(function (e, index) {
return !recentEmojis.some(function (e2, index2) {
return index2 < index && e2.filename === e.filename
})
})
if (recentEmojis.length > MAX_EMOJI_NUMBER) {
// remove last one
recentEmojis.splice(MAX_EMOJI_NUMBER - 1)
}
emojiSectionsRepeater.itemAt(0).allEmojis = recentEmojis
localAccountSensitiveSettings.recentEmojis = recentEmojis
// Adding a space because otherwise, some emojis would fuse since emoji is just a string
popup.emojiSelected(StatusQUtils.Emoji.parse(encodedIcon, popup.emojiSize || undefined) + ' ', true)
popup.close()
}
function populateCategories() {
var categoryNames = {"recent": 0}
var newCategories = [[]]
StatusQUtils.Emoji.emojiJSON.emoji_json.forEach(function (emoji) {
if (!categoryNames[emoji.category] && categoryNames[emoji.category] !== 0) {
categoryNames[emoji.category] = newCategories.length
newCategories.push([])
}
var emojisWithColors = [
"1f64c",
"1f44f",
"1f44b",
"1f44d",
"1f44e",
"1f44a",
"270a",
"270c",
"1f44c",
"270b",
"1f450",
"1f4aa",
"1f64f",
"261d",
"1f446",
"1f447",
"1f448",
"1f449",
"1f595",
"1f590",
"1f918",
"1f596",
"270d",
"1f485",
"1f442",
"1f443",
"1f476",
"1f466",
"1f467",
"1f468",
"1f469",
"1f471",
"1f474",
"1f475",
"1f472",
"1f473",
"1f46e",
"1f477",
"1f482",
"1f385",
"1f47c",
"1f478",
"1f470",
"1f6b6",
"1f3c3",
"1f483",
"1f647",
"1f481",
"1f645",
"1f646",
"1f64b",
"1f64e",
"1f64d",
"1f487",
"1f486",
"1f6a3",
"1f3ca",
"1f3c4",
"1f6c0",
"26f9",
"1f3cb",
"1f6b4",
"1f6b5",
"1f3c7",
"1f575"
]
if (localAccountSensitiveSettings.skinColor !== "") {
if (emoji.unicode.includes(localAccountSensitiveSettings.skinColor)) {
newCategories[categoryNames[emoji.category]].push(Object.assign({}, emoji, {filename: emoji.unicode}));
} else {
if (!emojisWithColors.includes(emoji.unicode) && !containsSkinColor(emoji.unicode)) {
newCategories[categoryNames[emoji.category]].push(Object.assign({}, emoji, {filename: emoji.unicode}));
}
}
} else {
if (!containsSkinColor(emoji.unicode)) {
newCategories[categoryNames[emoji.category]].push(Object.assign({}, emoji, {filename: emoji.unicode}));
}
}
})
if (newCategories[categoryNames.recent].length === 0) {
newCategories[categoryNames.recent].push({category: "recent", empty: true })
}
categories = newCategories;
const recent = localAccountSensitiveSettings.recentEmojis;
if (!!recent) {
emojiSectionsRepeater.itemAt(0).allEmojis = recent;
}
}
onOpened: {
searchBox.text = ""
searchBox.input.edit.forceActiveFocus()
Qt.callLater(populateCategories);
}
onClosed: {
popup.emojiSize = ""
}
contentItem: ColumnLayout {
anchors.fill: parent
spacing: Style.current.smallPadding
Item {
readonly property int headerMargin: 8
id: emojiHeader
Layout.fillWidth: true
height: searchBox.height + emojiHeader.headerMargin
SearchBox {
input.edit.objectName: "StatusEmojiPopup_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
}
Row {
id: skinToneEmoji
property bool expandSkinColorOptions: false
clip: true
width: expandSkinColorOptions ? (22 * skinColorEmojiRepeater.count) : 22
height: 22
opacity: expandSkinColorOptions ? 1.0 : 0.0
Behavior on width { NumberAnimation { duration: 400 } }
Behavior on opacity { NumberAnimation { duration: 200 } }
visible: (opacity > 0.1)
anchors.verticalCenter: searchBox.verticalCenter
anchors.right: parent.right
anchors.rightMargin: emojiHeader.headerMargin
Repeater {
id: skinColorEmojiRepeater
model: ["1f590-1f3fb", "1f590-1f3fc", "1f590-1f3fd", "1f590-1f3fe", "1f590-1f3ff", "1f590"]
delegate: StatusEmoji {
width: 22
height: 22
emojiId: modelData
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
localAccountSensitiveSettings.skinColor = (index === 5) ? "" : modelData.split("-")[1];
popup.populateCategories();
skinToneEmoji.expandSkinColorOptions = false;
}
}
}
}
}
StatusEmoji {
width: 22
height: 22
anchors.verticalCenter: searchBox.verticalCenter
anchors.right: parent.right
anchors.rightMargin: emojiHeader.headerMargin
visible: !skinToneEmoji.expandSkinColorOptions
emojiId: "1f590" + ((localAccountSensitiveSettings.skinColor !== "" && visible) ? ("-" + localAccountSensitiveSettings.skinColor) : "")
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
skinToneEmoji.expandSkinColorOptions = true;
}
}
}
}
StatusScrollView {
readonly property ScrollBar vScrollBar: ScrollBar.vertical
property var categrorySectionHeightRatios: []
property int activeCategory: 0
id: scrollView
padding: Style.current.smallPadding
Layout.fillWidth: true
Layout.fillHeight: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
contentWidth: availableWidth
ScrollBar.vertical.onPositionChanged: function () {
if (vScrollBar.position < categrorySectionHeightRatios[scrollView.activeCategory - 1]) {
scrollView.activeCategory--
} else if (vScrollBar.position > categrorySectionHeightRatios[scrollView.activeCategory]) {
scrollView.activeCategory++
}
}
function scrollToCategory(category) {
if (category === 0) {
return vScrollBar.setPosition(0)
}
vScrollBar.setPosition(categrorySectionHeightRatios[category - 1])
}
contentHeight: {
var totalHeight = 0
var categoryHeights = []
for (let i = 0; i < emojiSectionsRepeater.count; i++) {
totalHeight += emojiSectionsRepeater.itemAt(i).height
categoryHeights.push(totalHeight)
}
var ratios = []
categoryHeights.forEach(function (catHeight) {
ratios.push(catHeight / totalHeight)
})
categrorySectionHeightRatios = ratios
return totalHeight - scrollView.topPadding - scrollView.bottomPadding
}
Repeater {
id: emojiSectionsRepeater
model: popup.categories
StatusEmojiSection {
width: scrollView.availableWidth
searchString: popup.searchString
addEmoji: popup.addEmoji
}
}
}
Row {
Layout.fillWidth: true
height: 40
spacing: 0
Repeater {
model: StatusQUtils.Emoji.emojiJSON.emojiCategories
StatusTabBarIconButton {
icon.name: modelData
highlighted: index === scrollView.activeCategory
onClicked: {
scrollView.activeCategory = index
scrollView.scrollToCategory(index)
}
}
}
}
}
}