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 StatusChatCommandPopup 1.0 StatusChatCommandPopup.qml
StatusChatInfo 1.0 StatusChatInfo.qml StatusChatInfo 1.0 StatusChatInfo.qml
StatusChatInfoButton 1.0 StatusChatInfoButton.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 StatusIconButton 1.0 StatusIconButton.qml
StatusImageIdenticon 1.0 StatusImageIdenticon.qml StatusImageIdenticon 1.0 StatusImageIdenticon.qml
StatusLetterIdenticon 1.0 StatusLetterIdenticon.qml StatusLetterIdenticon 1.0 StatusLetterIdenticon.qml