uiux: introduce Emoji popup components for new chat input

This commit is contained in:
Pascal Precht 2020-09-29 10:37:08 +02:00 committed by Iuri Matias
parent dcc0a1d321
commit 961a370002
5 changed files with 17285 additions and 0 deletions

View File

@ -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: "../app/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}
}
##^##*/

View File

@ -0,0 +1,232 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import "../../imports"
import "../../shared"
import "./emojiList.js" as EmojiJSON
Popup {
property var emojiSelected: function () {}
property var categories: []
property string searchString: searchBox.text
id: popup
modal: false
width: 360
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
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
fast: true
cached: true
color: "#22000000"
}
}
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 = appSettings.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
appSettings.recentEmojis = recentEmojis
popup.emojiSelected(Emoji.parse(encodedIcon, "26x26") + ' ', true) // Adding a space because otherwise, some emojis would fuse since emoji is just a string
popup.close()
}
Component.onCompleted: {
var categoryNames = {"recent": 0}
var newCategories = [[]]
EmojiJSON.emoji_json.forEach(function (emoji) {
if (!categoryNames[emoji.category] && categoryNames[emoji.category] !== 0) {
categoryNames[emoji.category] = newCategories.length
newCategories.push([])
}
newCategories[categoryNames[emoji.category]].push(Object.assign({}, emoji, {filename: emoji.unicode + '.png'}))
})
if (newCategories[categoryNames.recent].length === 0) {
newCategories[categoryNames.recent].push({
category: "recent",
empty: true
})
}
categories = newCategories
}
Connections {
target: applicationWindow
onSettingsLoaded: {
// Add recent
if (!appSettings.recentEmojis || !appSettings.recentEmojis.length) {
return
}
emojiSectionsRepeater.itemAt(0).allEmojis = appSettings.recentEmojis
}
}
onOpened: {
searchBox.forceActiveFocus(Qt.MouseFocusReason)
}
contentItem: ColumnLayout {
anchors.fill: parent
spacing: 0
Item {
property int headerMargin: 8
id: emojiHeader
Layout.fillWidth: true
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 - Style.current.smallPadding - emojiHeader.height
clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
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 + 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
StatusEmojiSection {
searchString: popup.searchString
addEmoji: popup.addEmoji
}
}
}
Row {
Layout.fillWidth: true
height: 40
leftPadding: Style.current.smallPadding / 2
rightPadding: Style.current.smallPadding / 2
spacing: 0
Repeater {
model: EmojiJSON.emojiCategories
StatusEmojiCategoryButton {
source: `../../app/img/emojiCategories/${modelData}.svg`
active: index === scrollView.activeCategory
changeCategory: function () {
scrollView.activeCategory = index
scrollView.scrollToCategory(index)
}
}
}
}
}
}
/*##^##
Designer {
D{i:0;formeditorColor:"#ffffff";height:440;width:360}
}
##^##*/

View File

@ -0,0 +1,108 @@
import QtQuick 2.13
import QtQuick.Layouts 1.3
import "../../imports"
import "../../shared"
Item {
property string searchString: ""
property string searchStringLowercase: searchString.toLowerCase()
property int imageWidth: 26
property int imageMargin: 4
property var emojis: []
property var allEmojis: modelData
property var addEmoji: function () {}
id: emojiSection
visible: emojis.length > 0 || !!(modelData && modelData.length && modelData[0].empty && searchString === "")
anchors.top: index === 0 ? parent.top : parent.children[index - 1].bottom
anchors.topMargin: index === 0 ? 0 : Style.current.padding
width: parent.width
// childrenRect caused a binding loop here
height: this.visible ? emojiGrid.height + categoryText.height + noRecentText.height + Style.current.padding : 0
StyledText {
id: categoryText
text: modelData && modelData.length ? modelData[0].category.toUpperCase() : ""
color: Style.current.darkGrey
font.pixelSize: 13
}
StyledText {
id: noRecentText
visible: !!(allEmojis && allEmojis.length && allEmojis[0].empty)
//% "No recent emojis"
text: qsTrId("no-recent-emojis")
color: Style.current.darkGrey
font.pixelSize: 10
anchors.top: categoryText.bottom
anchors.topMargin: Style.current.smallPadding
}
onSearchStringLowercaseChanged: {
if (emojiSection.searchStringLowercase === "") {
this.emojis = modelData
return
}
this.emojis = modelData.filter(function (emoji) {
return emoji.name.includes(emojiSection.searchStringLowercase) ||
emoji.shortname.includes(emojiSection.searchStringLowercase) ||
emoji.aliases.some(a => a.includes(emojiSection.searchStringLowercase))
})
}
onAllEmojisChanged: {
if (this.allEmojis[0].empty) {
return
}
this.emojis = this.allEmojis
}
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: emojiSection.emojis
focus: true
clip: true
interactive: false
delegate: Item {
id: emojiContainer
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/" + modelData.filename
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: {
emojiSection.addEmoji(modelData)
}
}
}
}
}
}
}
/*##^##
Designer {
D{i:0;formeditorColor:"#ffffff";height:440;width:360}
}
##^##*/

16887
ui/shared/status/emojiList.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,9 @@ StatusChatCommandButton 1.0 StatusChatCommandButton.qml
StatusChatCommandPopup 1.0 StatusChatCommandPopup.qml
StatusChatInfo 1.0 StatusChatInfo.qml
StatusChatInfoButton 1.0 StatusChatInfoButton.qml
StatusEmojiCategoryButton 1.0 StatusEmojiCategoryButton.qml
StatusEmojiPopup 1.0 StatusEmojiPopup.qml
StatusEmojiSection 1.0 StatusEmojiSection.qml
StatusIconButton 1.0 StatusIconButton.qml
StatusImageIdenticon 1.0 StatusImageIdenticon.qml
StatusLetterIdenticon 1.0 StatusLetterIdenticon.qml