Merge branch 'wunderfitz_master' into mbarashkov_master

This commit is contained in:
Mikhail Barashkov 2024-05-14 18:12:33 +03:00
commit a984b94f2f
68 changed files with 1828 additions and 72 deletions

Binary file not shown.

View file

@ -22,6 +22,7 @@ DEFINES += QT_STATICPLUGIN
SOURCES += src/harbour-fernschreiber.cpp \
src/appsettings.cpp \
src/boolfiltermodel.cpp \
src/chatpermissionfiltermodel.cpp \
src/chatlistmodel.cpp \
src/chatmodel.cpp \
@ -105,14 +106,21 @@ DISTFILES += qml/harbour-fernschreiber.qml \
qml/components/messageContent/MessageGame.qml \
qml/components/messageContent/MessageLocation.qml \
qml/components/messageContent/MessagePhoto.qml \
qml/components/messageContent/MessagePhotoAlbum.qml \
qml/components/messageContent/MessagePoll.qml \
qml/components/messageContent/MessageSticker.qml \
qml/components/messageContent/MessageVenue.qml \
qml/components/messageContent/MessageVideoAlbum.qml \
qml/components/messageContent/MessageVideoNote.qml \
qml/components/messageContent/MessageVideo.qml \
qml/components/messageContent/MessageVoiceNote.qml \
qml/components/messageContent/SponsoredMessage.qml \
qml/components/messageContent/WebPagePreview.qml \
qml/components/messageContent/mediaAlbumPage/FullscreenOverlay.qml \
qml/components/messageContent/mediaAlbumPage/PhotoComponent.qml \
qml/components/messageContent/mediaAlbumPage/VideoComponent.qml \
qml/components/messageContent/mediaAlbumPage/ZoomArea.qml \
qml/components/messageContent/mediaAlbumPage/ZoomImage.qml \
qml/components/settingsPage/Accordion.qml \
qml/components/settingsPage/AccordionItem.qml \
qml/components/settingsPage/ResponsiveGrid.qml \
@ -130,6 +138,7 @@ DISTFILES += qml/harbour-fernschreiber.qml \
qml/pages/CoverPage.qml \
qml/pages/DebugPage.qml \
qml/pages/InitializationPage.qml \
qml/pages/MediaAlbumPage.qml \
qml/pages/NewChatPage.qml \
qml/pages/OverviewPage.qml \
qml/pages/AboutPage.qml \
@ -212,6 +221,7 @@ INSTALLS += telegram 86.png 108.png 128.png 172.png 256.png \
HEADERS += \
src/appsettings.h \
src/boolfiltermodel.h \
src/chatpermissionfiltermodel.h \
src/chatlistmodel.h \
src/chatmodel.h \

View file

@ -32,6 +32,7 @@ ListItem {
property int messageIndex
property int messageViewCount
property var myMessage
property var messageAlbumMessageIds
property var reactions
property bool canReplyToMessage
readonly property bool isAnonymous: myMessage.sender_id["@type"] === "messageSenderChat"
@ -68,7 +69,7 @@ ListItem {
property var chatReactions
property var messageReactions
highlighted: (down || isSelected || additionalOptionsOpened || wasNavigatedTo) && !menuOpen
highlighted: (down || (isSelected && messageAlbumMessageIds.length === 0) || additionalOptionsOpened || wasNavigatedTo) && !menuOpen
openMenuOnPressAndHold: !messageListItem.precalculatedValues.pageIsSelecting
signal replyToMessage()
@ -268,20 +269,20 @@ ListItem {
Connections {
target: chatModel
onMessagesReceived: {
messageBackground.isUnread = index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
messageBackground.isUnread = messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
}
onMessagesIncrementalUpdate: {
messageBackground.isUnread = index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
messageBackground.isUnread = messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
}
onNewMessageReceived: {
messageBackground.isUnread = index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
messageBackground.isUnread = messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
}
onUnreadCountUpdated: {
messageBackground.isUnread = index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
messageBackground.isUnread = messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage";
}
onLastReadSentMessageUpdated: {
Debug.log("[ChatModel] Messages in this chat were read, new last read: ", lastReadSentIndex, ", updating description for index ", index, ", status: ", (index <= lastReadSentIndex));
messageDateText.text = getMessageStatusText(myMessage, index, lastReadSentIndex, messageDateText.useElapsed);
Debug.log("[ChatModel] Messages in this chat were read, new last read: ", lastReadSentIndex, ", updating description for index ", index, ", status: ", (messageIndex <= lastReadSentIndex));
messageDateText.text = getMessageStatusText(myMessage, messageIndex, lastReadSentIndex, messageDateText.useElapsed);
}
}
@ -302,7 +303,7 @@ ListItem {
pageStack.currentPage === chatPage) {
Debug.log("Available reactions for this message: " + reactions);
messageListItem.messageReactions = reactions;
showItemCompletelyTimer.requestedIndex = index;
showItemCompletelyTimer.requestedIndex = messageIndex;
showItemCompletelyTimer.start();
} else {
messageListItem.messageReactions = null;
@ -323,6 +324,13 @@ ListItem {
interval: 200
triggeredOnStart: false
onTriggered: {
if (requestedIndex === messageIndex) {
chatView.highlightMoveDuration = -1;
chatView.highlightResizeDuration = -1;
chatView.scrollToIndex(requestedIndex);
chatView.highlightMoveDuration = 0;
chatView.highlightResizeDuration = 0;
}
Debug.log("Show item completely timer triggered, requested index: " + requestedIndex + ", current index: " + index)
if (requestedIndex === index) {
var p = chatView.contentItem.mapFromItem(reactionsColumn, 0, 0)
@ -376,8 +384,10 @@ ListItem {
onTriggered: {
if (messageListItem.hasContentComponent) {
var type = myMessage.content["@type"];
var albumComponentPart = (myMessage.media_album_id !== "0" && ['messagePhoto', 'messageVideo'].indexOf(type) !== -1) ? 'Album' : '';
console.log('delegateComponentLoadingTimer', myMessage.media_album_id, albumComponentPart)
extraContentLoader.setSource(
"../components/messageContent/" + type.charAt(0).toUpperCase() + type.substring(1) + ".qml",
"../components/messageContent/" + type.charAt(0).toUpperCase() + type.substring(1) + albumComponentPart + ".qml",
{
messageListItem: messageListItem
})
@ -441,7 +451,7 @@ ListItem {
}
height: messageTextColumn.height + precalculatedValues.paddingMediumDouble
width: precalculatedValues.backgroundWidth
property bool isUnread: index > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage"
property bool isUnread: messageIndex > chatModel.getLastReadMessageIndex() && myMessage['@type'] !== "sponsoredMessage"
color: Theme.colorScheme === Theme.LightOnDark ? (isUnread ? Theme.secondaryHighlightColor : Theme.secondaryColor) : (isUnread ? Theme.backgroundGlowColor : Theme.overlayBackgroundColor)
radius: parent.width / 50
opacity: isUnread ? 0.5 : 0.2
@ -463,7 +473,13 @@ ListItem {
id: userText
width: parent.width
text: messageListItem.isOwnMessage ? qsTr("You") : Emoji.emojify( myMessage['@type'] === "sponsoredMessage" ? tdLibWrapper.getChat(myMessage.sponsor_chat_id).title : ( messageListItem.isAnonymous ? page.chatInformation.title : Functions.getUserName(messageListItem.userInformation) ), font.pixelSize)
text: messageListItem.isOwnMessage
? qsTr("You")
: Emoji.emojify( myMessage['@type'] === "sponsoredMessage"
? tdLibWrapper.getChat(myMessage.sponsor_chat_id).title
: ( messageListItem.isAnonymous
? page.chatInformation.title
: Functions.getUserName(messageListItem.userInformation) ), font.pixelSize)
font.pixelSize: Theme.fontSizeExtraSmall
font.weight: Font.ExtraBold
color: messageListItem.textColor
@ -646,7 +662,8 @@ ListItem {
id: extraContentLoader
width: parent.width * getContentWidthMultiplier()
asynchronous: true
height: item ? item.height : (messageListItem.hasContentComponent ? chatView.getContentComponentHeight(model.content_type, myMessage.content, width) : 0)
readonly property var defaultExtraContentHeight: messageListItem.hasContentComponent ? chatView.getContentComponentHeight(model.content_type, myMessage.content, width, model.album_message_ids.length) : 0
height: item ? item.height : defaultExtraContentHeight
}
Binding {
@ -671,7 +688,7 @@ ListItem {
running: true
repeat: true
onTriggered: {
messageDateText.text = getMessageStatusText(myMessage, index, chatView.lastReadSentIndex, messageDateText.useElapsed);
messageDateText.text = getMessageStatusText(myMessage, messageIndex, chatView.lastReadSentIndex, messageDateText.useElapsed);
}
}
@ -684,13 +701,13 @@ ListItem {
font.pixelSize: Theme.fontSizeTiny
color: messageListItem.isOwnMessage ? Theme.secondaryHighlightColor : Theme.secondaryColor
horizontalAlignment: messageListItem.textAlign
text: getMessageStatusText(myMessage, index, chatView.lastReadSentIndex, messageDateText.useElapsed)
text: getMessageStatusText(myMessage, messageIndex, chatView.lastReadSentIndex, messageDateText.useElapsed)
MouseArea {
anchors.fill: parent
enabled: !messageListItem.precalculatedValues.pageIsSelecting
onClicked: {
messageDateText.useElapsed = !messageDateText.useElapsed;
messageDateText.text = getMessageStatusText(myMessage, index, chatView.lastReadSentIndex, messageDateText.useElapsed);
messageDateText.text = getMessageStatusText(myMessage, messageIndex, chatView.lastReadSentIndex, messageDateText.useElapsed);
}
}
}

View file

@ -24,6 +24,7 @@ Loader {
id: loader
property var minithumbnail
property bool highlighted
property int fillMode: tdLibImage.fillMode
anchors.fill: parent
active: !!minithumbnail
sourceComponent: Component {
@ -32,7 +33,7 @@ Loader {
id: minithumbnailImage
anchors.fill: parent
source: "data:image/jpg;base64,"+minithumbnail.data
fillMode: tdLibImage.fillMode
fillMode: loader.fillMode
opacity: status === Image.Ready ? 1.0 : 0.0
cache: false
visible: opacity > 0
@ -43,12 +44,12 @@ Loader {
effect: PressEffect { source: minithumbnailImage }
}
}
FastBlur {
anchors.fill: parent
source: minithumbnailImage
radius: Theme.paddingLarge
}
// this had a visible impact on performance
// FastBlur {
// anchors.fill: parent
// source: minithumbnailImage
// radius: Theme.paddingLarge
// }
}
}
}

View file

@ -59,7 +59,7 @@ Item {
readonly property bool hasVisibleThumbnail: thumbnailImage.opacity !== 1.0
&& !(videoThumbnailLoader.item && videoThumbnailLoader.item.opacity === 1.0)
property alias fillMode: thumbnailImage.fillMode
layer {
enabled: highlighted
effect: PressEffect { source: tdlibThumbnail }
@ -67,6 +67,7 @@ Item {
TDLibMinithumbnail {
id: minithumbnailLoader
fillMode: thumbnailImage.fillMode
active: !!minithumbnail && thumbnailImage.opacity < 1.0
}
BackgroundImage {
@ -103,6 +104,7 @@ Item {
sourceSize.width: width
sourceSize.height: height
mimeType: tdlibThumbnail.videoMimeType
fillMode: thumbnailImage.fillMode == Image.PreserveAspectFit ? Thumbnail.PreserveAspectFit : Thumbnail.PreserveAspectCrop
visible: opacity > 0
opacity: status === Thumbnail.Ready ? 1.0 : 0.0
Behavior on opacity { FadeAnimation {} }

View file

@ -20,7 +20,6 @@ import QtQuick 2.6
import Sailfish.Silica 1.0
import QtMultimedia 5.6
import "../"
import "../../js/functions.js" as Functions
import "../../js/debug.js" as Debug
Item {

View file

@ -22,28 +22,25 @@ import "../"
MessageContentBase {
function calculateBiggest() {
var candidateBiggest = rawMessage.content.photo.sizes[rawMessage.content.photo.sizes.length - 1];
if (candidateBiggest.width === 0 && rawMessage.content.photo.sizes.length > 1) {
for (var i = (rawMessage.content.photo.sizes.length - 2); i >= 0; i--) {
candidateBiggest = rawMessage.content.photo.sizes[i];
if (candidateBiggest.width > 0) {
height: Math.max(Theme.itemSizeExtraSmall, Math.min(Math.round(width * 0.66666666), width / getAspectRatio()))
readonly property alias photoData: photo.photo;
onClicked: {
pageStack.push(Qt.resolvedUrl("../../pages/MediaAlbumPage.qml"), {
"messages" : [rawMessage],
})
}
function getAspectRatio() {
var candidate = photoData.sizes[photoData.sizes.length - 1];
if (candidate.width === 0 && photoData.sizes.length > 1) {
for (var i = (photoData.sizes.length - 2); i >= 0; i--) {
candidate = photoData.sizes[i];
if (candidate.width > 0) {
break;
}
}
}
return candidateBiggest;
}
height: Math.max(Theme.itemSizeExtraSmall, Math.min(defaultHeight, width / (biggest.width/biggest.height)))
readonly property int defaultHeight: Math.round(width * 0.66666666)
readonly property var biggest: calculateBiggest();
onClicked: {
pageStack.push(Qt.resolvedUrl("../../pages/ImagePage.qml"), {
"photoData" : photo.photo,
// "pictureFileInformation" : photo.fileInformation
})
return candidate.width / candidate.height;
}
TDLibPhoto {
id: photo
@ -51,7 +48,4 @@ MessageContentBase {
photo: rawMessage.content.photo
highlighted: parent.highlighted
}
BackgroundImage {
visible: !rawMessage.content.photo.minithumbnail && photo.image.status !== Image.Ready
}
}

View file

@ -0,0 +1,207 @@
/*
Copyright (C) 2020 Sebastian J. Wolf and other contributors
This file is part of Fernschreiber.
Fernschreiber 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 3 of the License, or
(at your option) any later version.
Fernschreiber 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.
You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.6
import Sailfish.Silica 1.0
import "../"
MessageContentBase {
id: messageContent
property string chatId
readonly property int heightUnit: Math.round(width * 0.66666666)
readonly property var albumId: rawMessage.media_album_id
property var albumMessageIds: messageListItem ? messageListItem.messageAlbumMessageIds : []//overlayFlickable.messageAlbumMessageIds
onAlbumMessageIdsChanged: albumMessages = getMessages() //chatModel.getMessagesForAlbum(messageContent.albumId)
property var albumMessages: getMessages()//chatModel.getMessagesForAlbum(messageContent.albumId)
property bool firstLarge: albumMessages.length % 2 !== 0;
clip: true
height: defaultExtraContentHeight//(firstLarge ? heightUnit * 0.75 : 0 ) + heightUnit * 0.25 * albumMessageIds.length
onClicked: {
if(messageListItem.precalculatedValues.pageIsSelecting) {
page.toggleMessageSelection(rawMessage);
return;
}
openDetail(-1);
}
function getMessages() {
var msgs = [rawMessage];
if(messageContent.albumId === '0' || messageContent.albumMessageIds.length < 2) {
return msgs;
}
// var othermsgIds =
// getMessages from tdlib isn't faster
// if(rawMessage && rawMessage.chat_id) {
// var messages = [];
// return albumMessageIds.map(function(msgId){
// if(msgId === rawMessage.id) {
// return rawMessage;
// }
// return tdLibWrapper.getMessage(rawMessage.chat_id, msgId);
// })
// }
chatModel.getMessagesForAlbum(messageContent.albumId, 1).forEach(function(msg){
msgs.push(msg);
});
//
return msgs; //chatModel.getMessagesForAlbum(messageContent.albumId);
}
function openDetail(index) {
console.log('open detail', index || 0);
pageStack.push(Qt.resolvedUrl("../../pages/MediaAlbumPage.qml"), {
"messages" : albumMessages,
"index": index || 0
})
}
Connections { // TODO: needed?
target: tdLibWrapper
onReceivedMessage: {
if (albumMessageIds.indexOf(messageId)) {
// albumMessages = getMessages()
}
}
}
Component {
id: photoPreviewComponent
MessagePhoto {
// width: parent.width
// height: parent.height
messageListItem: messageContent.messageListItem
overlayFlickable: messageContent.overlayFlickable
rawMessage: albumMessages[modelIndex]
highlighted: mediaBackgroundItem.highlighted
}
}
Component {
id: videoPreviewComponent
Item {
property bool highlighted: mediaBackgroundItem.highlighted
anchors.fill: parent
clip: true
TDLibThumbnail {
id: tdLibImage
width: parent.width //don't use anchors here for easier custom scaling
height: parent.height
highlighted: parent.highlighted
thumbnail: albumMessages[modelIndex].content.video.thumbnail
minithumbnail: albumMessages[modelIndex].content.video.minithumbnail
}
Rectangle {
anchors {
fill: videoIcon
leftMargin: -Theme.paddingSmall
topMargin: -Theme.paddingSmall
bottomMargin: -Theme.paddingSmall
rightMargin: -Theme.paddingLarge
}
radius: Theme.paddingSmall
color: Theme.rgba(Theme.overlayBackgroundColor, 0.4)
}
Icon {
id: videoIcon
source: "image://theme/icon-m-video"
width: Theme.iconSizeSmall
height: Theme.iconSizeSmall
highlighted: parent.highlighted
anchors {
right: parent.right
rightMargin: Theme.paddingSmall
bottom: parent.bottom
}
}
}
}
Flow {
id: contentGrid
property int firstWidth: firstLarge ? contentGrid.width : normalWidth
property int firstHeight: firstLarge ? heightUnit - contentGrid.spacing : normalHeight
property int normalWidth: (contentGrid.width - contentGrid.spacing) / 2
property int normalHeight: (heightUnit / 2) - contentGrid.spacing
anchors.fill: parent
spacing: Theme.paddingMedium
Repeater {
model: albumMessages
delegate: BackgroundItem {
id: mediaBackgroundItem
property bool isLarge: firstLarge && model.index === 0
width: model.index === 0 ? contentGrid.firstWidth : contentGrid.normalWidth
height: model.index === 0 ? contentGrid.firstHeight : contentGrid.normalHeight
readonly property bool isSelected: messageListItem.precalculatedValues.pageIsSelecting && page.selectedMessages.some(function(existingMessage) {
return existingMessage.id === albumMessages[index].id
});
highlighted: isSelected || down || messageContent.highlighted
onClicked: {
if(messageListItem.precalculatedValues.pageIsSelecting) {
page.toggleMessageSelection(albumMessages[index]);
return;
}
openDetail(index);
}
onPressAndHold: {
page.toggleMessageSelection(albumMessages[index]);
}
Loader {
anchors.fill: parent
// asynchronous: true
readonly property int modelIndex: index
sourceComponent: albumMessages[index].content["@type"] === 'messageVideo' ? videoPreviewComponent : photoPreviewComponent
opacity: status === Loader.Ready
Behavior on opacity {FadeAnimator{}}
}
/*
TODO video:
rawMessage.content.video.thumbnail
TDLibPhoto {
id: photo
anchors.fill: parent
photo: rawMessage.content.photo
highlighted: parent.highlighted
}
*/
Rectangle {
visible: mediaBackgroundItem.isSelected
anchors {
fill: parent
}
color: 'transparent'
border.color: Theme.highlightColor
border.width: Theme.paddingSmall
}
}
}
}
}

View file

@ -26,7 +26,12 @@ import "../../js/debug.js" as Debug
MessageContentBase {
id: videoMessageComponent
property var videoData: ( rawMessage.content['@type'] === "messageVideo" ) ? rawMessage.content.video : ( ( rawMessage.content['@type'] === "messageAnimation" ) ? rawMessage.content.animation : rawMessage.content.video_note )
property var videoData: ( rawMessage.content['@type'] === "messageVideo" )
? rawMessage.content.video
: (
( rawMessage.content['@type'] === "messageAnimation" )
? rawMessage.content.animation
: rawMessage.content.video_note )
property string videoUrl;
property int previewFileId;
property int videoFileId;

View file

@ -0,0 +1,19 @@
/*
Copyright (C) 2020 Sebastian J. Wolf and other contributors
This file is part of Fernschreiber.
Fernschreiber 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 3 of the License, or
(at your option) any later version.
Fernschreiber 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.
You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/
MessagePhotoAlbum {}

View file

@ -0,0 +1,279 @@
/*
Copyright (C) 2020 Sebastian J. Wolf and other contributors
This file is part of Fernschreiber.
Fernschreiber 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 3 of the License, or
(at your option) any later version.
Fernschreiber 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.
You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick 2.6
import QtGraphicalEffects 1.0
import Sailfish.Silica 1.0
import "../../../js/functions.js" as Functions
Item {
// id
id: overlay
// property declarations
property int pageCount
property int currentIndex
property alias text: captionLabel.text
property bool active: true
property var message
readonly property color gradientColor: '#bb000000'
readonly property int gradientPadding: Theme.itemSizeMedium
// signal declarations
// JavaScript functions
// object properties
anchors.fill: parent
opacity: active ? 1 : 0
Behavior on opacity { FadeAnimator {} }
// large property bindings
// child objects
// states
// transitions
onActiveChanged: {
console.log('overlay active', active)
}
function forwardMessage() {
var neededPermissions = Functions.getMessagesNeededForwardPermissions([message]);
pageStack.push(Qt.resolvedUrl("../../../pages/ChatSelectionPage.qml"), {
myUserId: tdLibWrapper.getUserInformation().id,
headerDescription: qsTr("Forward %Ln messages", "dialog header", 1),
payload: {fromChatId: message.chat_id, messageIds:[message.id], neededPermissions: neededPermissions},
state: "forwardMessages"
});
}
// "header"
LinearGradient {
id: topGradient
property int startY: 0;
// Behavior on startY { NumberAnimation {duration: 2000} }
start: Qt.point(0, Math.min(height-gradientPadding*2, startY))
anchors {
left: parent.left
right: parent.right
top: parent.top
bottom: closeButton.bottom
bottomMargin: -gradientPadding
}
gradient: Gradient {
GradientStop { position: 0.0; color: gradientColor }
GradientStop { position: 1.0; color: 'transparent' }
}
}
IconButton {
id: closeButton
icon.source: "image://theme/icon-m-cancel?" + (pressed
? Theme.highlightColor
: Theme.lightPrimaryColor)
onClicked: pageStack.pop()
anchors {
right: parent.right
top: parent.top
margins: Theme.horizontalPageMargin
}
}
SilicaFlickable {
id: captionFlickable
anchors {
left: parent.left
// leftMargin: Theme.horizontalPageMargin
right: closeButton.left
top: parent.top
// topMargin: Theme.horizontalPageMargin
}
interactive: captionLabel.expanded && contentHeight > height
clip: true
height: Math.min(contentHeight, parent.height / 4)
contentHeight: captionLabel.height + Theme.horizontalPageMargin
flickableDirection: Flickable.VerticalFlick
VerticalScrollDecorator {
opacity: visible ? 1.0 : 0.0
flickable: captionFlickable
}
Label {
id: captionLabel
property bool expandable: expanded || height < contentHeight
property bool expanded
height: text ?
expanded
? contentHeight
: Theme.itemSizeMedium
: 0;
// maximumLineCount: expanded ? 0 : 3
color: Theme.primaryColor
// text: model.modelData.content.caption.text
text: Emoji.emojify(Functions.enhanceMessageText(message.content.caption, false), Theme.fontSizeExtraSmall)
onTextChanged: expanded = false
font.pixelSize: Theme.fontSizeExtraSmall
wrapMode: Text.WrapAnywhere
bottomPadding: expanded ? Theme.paddingLarge : 0
anchors {
left: parent.left
leftMargin: Theme.horizontalPageMargin
rightMargin: Theme.paddingLarge
right: parent.right
top: parent.top
topMargin: Theme.horizontalPageMargin
}
Behavior on height { NumberAnimation {duration: 300} }
Behavior on text {
SequentialAnimation {
FadeAnimation {
target: captionLabel
to: 0.0
duration: 300
}
PropertyAction {}
FadeAnimation {
target: captionLabel
to: 1.0
duration: 300
}
}
}
}
OpacityRampEffect {
sourceItem: captionLabel
enabled: !captionLabel.expanded
direction: OpacityRamp.TopToBottom
}
MouseArea {
anchors.fill: captionLabel
enabled: captionLabel.expandable
onClicked: {
captionLabel.expanded = !captionLabel.expanded
}
}
}
// "footer"
LinearGradient {
anchors {
left: parent.left
right: parent.right
top: buttons.top
bottom: parent.bottom
topMargin: -gradientPadding
}
gradient: Gradient {
GradientStop { position: 0.0; color: 'transparent' }
GradientStop { position: 1.0; color: gradientColor }
}
}
Loader {
asynchronous: true
active: overlay.pageCount > 1
anchors {
horizontalCenter: parent.horizontalCenter
verticalCenter: buttons.bottom
}
sourceComponent: Component {
Row {
id: pageIndicatorRow
height: Theme.paddingSmall
spacing: height
Repeater {
id: pageIndicator
model: overlay.pageCount
Rectangle {
property bool active: model.index === overlay.currentIndex
width: pageIndicatorRow.height
height: pageIndicatorRow.height
color: active ? Theme.lightPrimaryColor : Theme.rgba(Theme.lightSecondaryColor, Theme.opacityLow)
Behavior on color { ColorAnimation {} }
radius: Theme.paddingSmall
}
}
}
}
}
Row {
id: buttons
height: Theme.itemSizeSmall
width: childrenRect.width
spacing: Theme.paddingLarge
anchors {
horizontalCenter: parent.horizontalCenter
bottom: parent.bottom
bottomMargin: Theme.paddingLarge
}
// IconButton {
// icon.source: "image://theme/icon-m-cancel?" + (pressed
// ? Theme.highlightColor
// : Theme.lightPrimaryColor)
// onClicked: pageStack.pop()
// }
IconButton {
icon.source: "image://theme/icon-m-downloads?" + (pressed
? Theme.highlightColor
: Theme.lightPrimaryColor)
onClicked: pageStack.pop()
}
Item {
width: Theme.itemSizeSmall
height: Theme.itemSizeSmall
}
IconButton {
enabled: message.can_be_forwarded
opacity: enabled ? 1.0 : 0.2
icon.source: "image://theme/icon-m-share?" + (pressed
? Theme.highlightColor
: Theme.lightPrimaryColor)
onClicked: forwardMessage()
}
}
states: [
State {
name: 'hasCaption'
when: captionLabel.height > 0
PropertyChanges { target: topGradient;
startY: captionFlickable.height
}
AnchorChanges {
target: topGradient
// anchors.top: captionLabel.verticalCenter
anchors.bottom: captionFlickable.bottom
}
}
]
transitions:
Transition {
AnchorAnimation { duration: 200 }
NumberAnimation { properties: "startY"; duration: 200 }
}
}

View file

@ -0,0 +1,16 @@
import QtQuick 2.6
ZoomImage {
photoData: model.modelData.content.photo
onClicked: {
console.log('clicked', zoomed)
if(zoomed) {
zoomOut(true)
page.overlayActive = true
} else {
page.overlayActive = !page.overlayActive
}
}
}

View file

@ -0,0 +1,181 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
import WerkWolf.Fernschreiber 1.0
import QtMultimedia 5.6
import QtGraphicalEffects 1.0
import "../../"
Video {
id: video
property var videoData: model.modelData.content.video
readonly property bool isPlaying: playbackState === MediaPlayer.PlayingState
readonly property bool isCurrent: index === page.index
property bool shouldPlay
autoLoad: true
source: file.isDownloadingCompleted ? file.path : ''
onIsCurrentChanged: {
if(!isCurrent) {
pause()
}
}
onStatusChanged: {
if(status === MediaPlayer.EndOfMedia) {
page.overlayActive = true
}
}
TDLibThumbnail {
id: tdLibImage
property bool active: !file.isDownloadingCompleted || (!video.isPlaying && (video.position === 0 || video.status === MediaPlayer.EndOfMedia))
opacity: active ? 1 : 0
visible: active || opacity > 0
width: parent.width //don't use anchors here for easier custom scaling
height: parent.height
// highlighted: parent.highlighted
thumbnail: videoData.thumbnail
minithumbnail: videoData.minithumbnail
fillMode: Image.PreserveAspectFit
}
TDLibFile {
id: file
autoLoad: false
tdlib: tdLibWrapper
fileInformation: videoData.video
property real progress: isDownloadingCompleted ? 1.0 : (downloadedSize / size)
onDownloadingCompletedChanged: {
if(isDownloadingCompleted) {
video.source = file.path
if(video.shouldPlay) {
video.play()
delayedOverlayHide.start()
video.shouldPlay = false
}
}
}
}
Label {
anchors.centerIn: parent
text: 'dl: '+file.downloadedSize
+ ' \ns: '+file.size
+ ' \nes: '+file.expectedSize
+ ' \nd:'+file.isDownloadingActive
+ ' \nc:'+file.isDownloadingCompleted
}
MouseArea {
anchors.fill: parent
onClicked: page.overlayActive = !page.overlayActive
}
RadialGradient { // white videos = invisible button. I can't tell since which SFOS version the opaque button is available, so:
id: buttonBg
anchors.centerIn: parent
width: Theme.itemSizeLarge; height: Theme.itemSizeLarge
property color baseColor: Theme.rgba(palette.overlayBackgroundColor, 0.2)
enabled: videoUI.active || !file.isDownloadingCompleted
opacity: enabled ? 1 : 0
Behavior on opacity { FadeAnimator {} }
gradient: Gradient {
GradientStop { position: 0.0; color: buttonBg.baseColor }
GradientStop { position: 0.3; color: buttonBg.baseColor }
GradientStop { position: 0.5; color: 'transparent' }
}
IconButton {
anchors.fill: parent
icon.source: "image://theme/icon-l-"+(video.isPlaying || video.shouldPlay ? 'pause' : 'play')+"?" + (pressed
? Theme.highlightColor
: Theme.lightPrimaryColor)
onClicked: {
if (!file.isDownloadingCompleted) {
video.shouldPlay = !video.shouldPlay;
if(video.shouldPlay) {
file.load()
} else {
file.cancel()
}
return;
}
if (video.isPlaying) {
video.pause()
} else {
video.play()
delayedOverlayHide.start()
}
}
}
}
ProgressCircle {
property bool active: file.isDownloadingActive
opacity: active ? 1 : 0
Behavior on opacity { FadeAnimator {} }
anchors.centerIn: parent
value: file.progress
}
Item {
id: videoUI
property bool active: overlay.active// && file.isDownloadingCompleted
anchors.fill: parent
opacity: active ? 1 : 0
Behavior on opacity { FadeAnimator {} }
Slider {
id: slider
value: video.position
minimumValue: 0
maximumValue: video.duration || 0.1
enabled: parent.active && video.seekable
width: parent.width
handleVisible: false
animateValue: true
stepSize: 500
anchors {
bottom: parent.bottom
bottomMargin: Theme.itemSizeMedium
}
valueText: value > 0 || down ? Format.formatDuration((value)/1000, Formatter.Duration) : ''
leftMargin: Theme.horizontalPageMargin
rightMargin: Theme.horizontalPageMargin
onDownChanged: {
if(!down) {
video.seek(value)
value = Qt.binding(function() { return video.position })
}
}
Label {
anchors {
right: parent.right
rightMargin: Theme.horizontalPageMargin
bottom: parent.bottom
topMargin: Theme.paddingSmall
}
font.pixelSize: Theme.fontSizeExtraSmall
text: file.isDownloadingCompleted
? Format.formatDuration((parent.maximumValue - parent.value)/1000, Formatter.Duration)
: (video.videoData.duration
? Format.formatDuration(video.videoData.duration, Formatter.Duration) + ', '
: '') + Format.formatFileSize(file.size || file.expectedSize)
color: Theme.secondaryColor
}
}
Timer {
id: delayedOverlayHide
interval: 500
onTriggered: {
if(video.isPlaying) {
page.overlayActive = false
}
}
}
}
}

View file

@ -0,0 +1,148 @@
import QtQuick 2.6
import Sailfish.Silica 1.0
SilicaFlickable {
// id
id: flickable
// property declarations
property real zoom
property bool zoomed
// override if needed
property bool zoomEnabled: true
property real minimumZoom: fitZoom
property real maximumZoom: 4 //Math.max(fitZoom, 1) * 3
default property alias zoomContentItem: zoomContentItem.data
property alias implicitContentWidth: zoomContentItem.implicitWidth
property alias implicitContentHeight: zoomContentItem.implicitHeight
// factor for "PreserveAspectFit"
readonly property real fitZoom: implicitContentWidth > 0 && implicitContentHeight > 0
? Math.min(maximumZoom, width / implicitContentWidth, height / implicitContentHeight)
: 1.0
readonly property int minimumBoundaryAxis: (implicitContentWidth / implicitContentHeight) > (width / height) ? Qt.Horizontal : Qt.Vertical
// JavaScript functions
function zoomOut(animated) {
if (zoomed) {
if(animated) { zoomOutAnimation.start() }
else {
zoom = fitZoom
zoomed = false
}
}
}
// object properties
contentWidth: Math.max(width, zoomContentItem.width)
contentHeight: Math.max(height, zoomContentItem.height)
enabled: !zoomOutAnimation.running && implicitContentWidth > 0 && implicitContentHeight > 0
flickableDirection: Flickable.HorizontalAndVerticalFlick
interactive: zoomed
// According to Jolla, otherwise pinching would sometimes not work:
pressDelay: 0
Binding { // Update zoom on orientation changes and set as default
target: flickable
when: !zoomed
property: "zoom"
value: minimumZoom
}
// child objects
PinchArea {
id: pinchArea
parent: flickable.contentItem
width: flickable.contentWidth
height: flickable.contentHeight
enabled: zoomEnabled && minimumZoom !== maximumZoom && flickable.enabled
onPinchUpdated: {
scrollDecoratorTimer.restart()
var f = flickable;
var requestedZoomFactor = 1.0 + pinch.scale - pinch.previousScale;
var previousWidth = f.contentWidth
var previousHeight = f.contentHeight
var targetWidth
var targetHeight
var targetZoom = requestedZoomFactor * f.zoom;
if (targetZoom < f.minimumZoom) {
f.zoom = f.minimumZoom;
f.zoomed = false;
f.contentX = 0;
f.contentY = 0;
return
} else if(targetZoom >= f.maximumZoom) {
f.zoom = f.maximumZoom;
targetHeight = f.implicitContentHeight * f.zoom
targetWidth = f.implicitContentWidth * f.zoom
}
else if(targetZoom < f.maximumZoom) {
if (f.minimumBoundaryAxis == Qt.Horizontal) {
targetWidth = f.contentWidth * requestedZoomFactor
f.zoom = targetWidth / f.implicitContentWidth
targetHeight = f.implicitContentHeight * f.zoom
} else {
targetHeight = f.contentHeight * requestedZoomFactor
f.zoom = targetHeight / f.implicitContentHeight
targetWidth = f.implicitContentWidth * f.zoom
}
}
// calculate center difference
f.contentX += pinch.previousCenter.x - pinch.center.x
f.contentY += pinch.previousCenter.y - pinch.center.y
// move to new (zoomed) center. this jumps a tiny bit, but is bearable:
if (targetWidth > f.width)
f.contentX -= (previousWidth - targetWidth)/(previousWidth/pinch.previousCenter.x)
if (targetHeight > f.height)
f.contentY -= (previousHeight - targetHeight)/(previousHeight/pinch.previousCenter.y)
f.zoomed = true
}
onPinchFinished: {
returnToBounds()
}
Item {
id: zoomContentItem
anchors.centerIn: parent
implicitWidth: flickable.width
implicitHeight: flickable.height
width: Math.ceil(implicitWidth * zoom)
height: Math.ceil(implicitHeight * zoom)
}
}
// enable zoom to minimumZoom on click
ParallelAnimation {
id: zoomOutAnimation
NumberAnimation {
target: flickable
properties: "contentX, contentY"
to: 0
}
NumberAnimation {
target: flickable
property: "zoom"
to: fitZoom
}
onRunningChanged: {
if(!running) {
zoomed = false
}
}
}
// show scroll decorators when scrolling OR zooming
Timer {
id: scrollDecoratorTimer
readonly property bool moving: flickable.moving
readonly property bool showing: moving || running
onMovingChanged: restart()
interval: 300
}
VerticalScrollDecorator {
flickable: flickable
opacity: scrollDecoratorTimer.showing ? 1.0 : 0.0
}
HorizontalScrollDecorator {
flickable: flickable
opacity: scrollDecoratorTimer.showing ? 1.0 : 0.0
}
}

View file

@ -0,0 +1,127 @@
import QtQuick 2.0
import Sailfish.Silica 1.0
import WerkWolf.Fernschreiber 1.0
import "../../"
ZoomArea {
// id
id: zoomArea
property var photoData //albumMessages[index].content.photo
property bool active: true
property alias image: image
signal clicked
maximumZoom: Math.max(Screen.width, Screen.height) / 200
// maximumZoom: Math.max(fitZoom, 1) * 3
implicitContentWidth: image.implicitWidth
implicitContentHeight: image.implicitHeight
zoomEnabled: image.status == Image.Ready
onActiveChanged: {
if (!active) {
zoomOut()
}
}
Component.onCompleted: {
// var photoData = albumMessages[index].content.photo;
if (photoData) {
var biggestIndex = -1
for (var i = 0; i < photoData.sizes.length; i++) {
if (biggestIndex === -1 || photoData.sizes[i].width > photoData.sizes[biggestIndex].width) {
biggestIndex = i;
}
}
if (biggestIndex > -1) {
// imageDelegate.imageWidth = photoData.sizes[biggestIndex].width;
// imageDelegate.imageHeight = photoData.sizes[biggestIndex].height;
image.sourceSize.width = photoData.sizes[biggestIndex].width
image.sourceSize.height = photoData.sizes[biggestIndex].height
image.fileInformation = photoData.sizes[biggestIndex].photo
console.log('loading photo', JSON.stringify(image.fileInformation))
}
}
}
TDLibImage {
id: image
width: parent.width
height: parent.height
source: file.isDownloadingCompleted ? file.path : ""
// enabled: true //!!file.fileId
// anchors.fill: parent
anchors.centerIn: parent
fillMode: Image.PreserveAspectFit
asynchronous: true
smooth: !(movingVertically || movingHorizontally)
// sourceSize.width: Screen.height
// visible: opacity > 0
// opacity: status === Image.Ready ? 1 : 0
Behavior on opacity { FadeAnimator{} }
}
// Label {
// anchors.fill: parent
// text: 'ok?' + image.enabled +' fileid:' +!!(image.file.fileId)
// + '\n - dl?' + image.file.isDownloadingActive
// + '\n completed?' + image.file.isDownloadingCompleted + ' path:'+ image.file.path
// + '\n ' + image.source
// wrapMode: Text.WrapAtWordBoundaryOrAnywhere
// }
// Rectangle {
// color: 'green'
// anchors.fill: image
// opacity: 0.3
// }
// Image {
// id: image
// anchors.fill: parent
// smooth: !(movingVertically || movingHorizontally)
// sourceSize.width: Screen.height
// fillMode: Image.PreserveAspectFit
// asynchronous: true
// cache: false
// onSourceChanged: {
// zoomOut()
// }
// opacity: status == Image.Ready ? 1 : 0
// Behavior on opacity { FadeAnimator{} }
// }
Item {
anchors.fill: parent
}
MouseArea {
anchors.centerIn: parent
width: zoomArea.contentWidth
height: zoomArea.contentHeight
onClicked: zoomArea.clicked()
}
BusyIndicator {
running: image.file.isDownloadingActive && !delayBusyIndicator.running
size: BusyIndicatorSize.Large
anchors.centerIn: parent
parent: zoomArea
Timer {
id: delayBusyIndicator
running: image.file.isDownloadingActive
interval: 1000
}
}
// Rectangle {
// color: 'green'
// anchors.fill: parent
// parent: zoomArea
// }
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFAC33" d="M8.916 12.88c-.111 1.652 1.768 3.126-.712 2.959-2.48-.167-7.836-2.533-7.768-3.53s3.708-2.757 6.188-2.59c2.48.166 2.404 1.508 2.292 3.161m20.122 16.049a.966.966 0 0 0-.564.095c-2.325.232-3.225-1.885-3.225-1.885-.439-.336-.981-2.009-1.589-1.215l.187 1.402c.187 1.402 2.57 3.224 2.57 3.224l-1.215 1.589a1 1 0 1 0 1.589 1.215l.673-.88-.039.249a1 1 0 1 0 1.976.314l.47-2.963a1.003 1.003 0 0 0-.833-1.145zm-6.278.623a.984.984 0 0 0-.572.018c-2.335-.082-2.944-2.3-2.944-2.3-.39-.392-.703-2.123-1.412-1.417l-.003 1.414c-.003 1.414 2.115 3.539 2.115 3.539l-1.417 1.412a.999.999 0 1 0 1.411 1.417l.785-.782-.073.242a1 1 0 0 0 1.916.576l.862-2.873a.996.996 0 0 0-.668-1.246z"/><path fill="#31373D" d="M35.009 6.729c-.383-.17-.758-.057-1.05.244-.054.056-4.225 6.306-14.532 4.944-.34-.045 3.139 11.968 3.199 11.962.124-.014 3.07-.368 6.14-2.553 2.818-2.005 6.284-5.991 6.797-13.598.028-.418-.171-.828-.554-.999z"/><path fill="#31373D" d="M34.477 21.108c-.204-.336-.59-.56-.979-.471-1.293.295-3.197.543-4.53.453-6.357-.428-9.361-4.129-9.392-4.16-.275-.282.466 11.552.816 11.576 9.194.62 13.862-6.027 14.057-6.31.222-.326.233-.751.028-1.088"/><path fill="#31373D" d="M24.586 19.016c-.371 5.51 1.316 9.861-4.194 9.489-5.51-.371-10.145-4.92-9.774-10.431s14.34-4.568 13.968.942"/><path fill="#31373D" d="M23.257 12.412c-.353 5.235-3.922 9.257-9.156 8.904-5.235-.353-9.193-4.882-8.84-10.117.353-5.235 4.832-8.444 10.067-8.091 4.001.269 8.24 4.683 7.929 9.304z"/><circle cx="10.67" cy="8.989" r="2"/><path d="M18.179 16.645s7.63 5.648 12.387-4.459c.396-.842 1.685.793.099 4.162s-8.175 6.44-12.04 1.536c-.815-1.035-.446-1.239-.446-1.239"/><path fill="#31373D" d="M15.327 3.107s6.246.254 7.798-.477.136 2.932-3.262 3.789-4.536-3.312-4.536-3.312z"/><path fill="#31373D" d="M17.428 5.788s4.501.136 6.054-.594.136 2.932-3.262 3.789c-3.399.857-2.792-3.195-2.792-3.195z"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1
qml/js/emoji/1f6dc.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><defs><clipPath id="a"><path fill="none" d="M0 0h36v36H0z"/></clipPath></defs><g clip-path="url(#a)"><path fill="#2b87c7" d="M36 32c0 4-4 4-4 4H4c-4 0-4-4-4-4V4c0-4 4-4 4-4h28s4 0 4 4z"/></g><path fill="#fff" d="m5.1 13.01 1.46 1.65c.15.16.39.19.57.07 3.01-1.98 6.77-3.17 10.83-3.17s7.88 1.2 10.9 3.21c.18.12.42.09.57-.07l1.47-1.64c.17-.2.14-.5-.07-.65-3.56-2.45-8.02-3.91-12.87-3.91S8.72 9.94 5.17 12.36c-.22.15-.25.45-.08.65Z"/><path fill="#fff" d="m9.43 17.9 1.45 1.64c.15.16.39.2.58.08 1.8-1.2 4.06-1.92 6.51-1.92s4.74.73 6.54 1.95c.18.12.42.1.57-.07l1.47-1.64c.18-.2.14-.5-.08-.65-2.34-1.66-5.29-2.65-8.5-2.65s-6.12.98-8.45 2.61a.43.43 0 0 0-.08.65Zm4.22 4.77 1.46 1.64c.14.16.39.19.57.07.63-.41 1.42-.65 2.28-.65s1.67.25 2.3.66c.18.12.42.09.57-.07l1.46-1.64a.44.44 0 0 0-.06-.64c-1.17-.86-2.65-1.37-4.27-1.37s-3.08.51-4.24 1.35c-.21.15-.24.45-.07.64Z"/><circle cx="17.91" cy="27.64" r="1.86" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 977 B

1
qml/js/emoji/1fa75.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#88C9F9" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"/></svg>

After

Width:  |  Height:  |  Size: 368 B

1
qml/js/emoji/1fa76.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#99AAB5" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"/></svg>

After

Width:  |  Height:  |  Size: 368 B

1
qml/js/emoji/1fa77.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#F4ABBA" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"/></svg>

After

Width:  |  Height:  |  Size: 368 B

1
qml/js/emoji/1fa87.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><defs><clipPath id="a" clipPathUnits="userSpaceOnUse"><path d="M-35.367 13.848h36v-36h-36Z"/></clipPath><clipPath id="b" clipPathUnits="userSpaceOnUse"><path d="M-24.343 34.246h36v-36h-36Z"/></clipPath><clipPath id="c" clipPathUnits="userSpaceOnUse"><path d="M-36 10.269H0v-36h-36Z"/></clipPath><clipPath id="d" clipPathUnits="userSpaceOnUse"><path d="M-34.38 15.907h36v-36h-36Z"/></clipPath><clipPath id="e" clipPathUnits="userSpaceOnUse"><path d="M-31.406 19.303h36v-36h-36Z"/></clipPath><clipPath id="f" clipPathUnits="userSpaceOnUse"><path d="M-28.11 21.53h36v-36h-36Z"/></clipPath><clipPath id="g" clipPathUnits="userSpaceOnUse"><path d="M-12.71 6.294h36v-36h-36Z"/></clipPath><clipPath id="h" clipPathUnits="userSpaceOnUse"><path d="M-16.316 29.198h36v-36h-36Z"/></clipPath><clipPath id="i" clipPathUnits="userSpaceOnUse"><path d="M-11.042 3.065h36v-36h-36Z"/></clipPath><clipPath id="j" clipPathUnits="userSpaceOnUse"><path d="M-13.173 8.53h36v-36h-36Z"/></clipPath><clipPath id="k" clipPathUnits="userSpaceOnUse"><path d="M-12.869 13.034h36v-36h-36Z"/></clipPath><clipPath id="l" clipPathUnits="userSpaceOnUse"><path d="M-11.599 16.805h36v-36h-36Z"/></clipPath></defs><path fill="#fcd646" d="M0 0a10.926 10.926 0 0 0-.987-2.058l-.001-.001c-1.379-1.093-5.157-1.888-7.379-1.093-2.222.794-4 3-4.227 5.246.098.732.276 1.477.54 2.217a10.562 10.562 0 0 0 1.783 3.168c1.904-.631 3.847.105 5.904-.631 2.058-.736 3-2 5-3.269A10.622 10.622 0 0 0 0 0" clip-path="url(#a)" transform="matrix(1 0 0 -1 35.367 13.848)"/><path fill="#d4a086" d="M0 0c-.55-1.538-2.122-2.092-3.626-1.554-1.502.537-2.368 1.963-1.818 3.501.348.972.898 1.811 1.275 2.334 1.491 2.067 2.65 4.354 3.508 6.755l.036.101c.045.125.086.251.123.378 1.159.731 1.691.898 2.159.731.468-.168 0 0 .592-1.715a6.6 6.6 0 0 1-.145-.37l-.036-.101C1.209 7.659.654 5.156.496 2.612.456 1.969.394 1.102 0 0" clip-path="url(#b)" transform="matrix(1 0 0 -1 24.343 34.246)"/><path fill="#d24248" d="M0 0c-1.778-.237-3.836-.021-5.895.715-2.056.736-3.785 1.875-5.009 3.185 1.922 2.334 4.664 3.444 7.073 2.583C-1.422 5.621-.007 3.024 0 0" clip-path="url(#c)" transform="matrix(1 0 0 -1 36 10.269)"/><path fill="#82ae63" d="M0 0a10.36 10.36 0 0 0-.431-.64 12.303 12.303 0 0 0-2.543-2.757c-1.406-.696-3.867-.247-5.406.304-1.538.55-2 1-3.082 2.732a12.312 12.312 0 0 0-.217 3.745c.015.255.039.511.073.769C-10.373 2.709-8.535 1.445-6.314.65-4.091-.145-1.869-.333 0 0" clip-path="url(#d)" transform="matrix(1 0 0 -1 34.38 15.907)"/><path fill="#d24248" d="M0 0a10.735 10.735 0 0 0-1.724-1.11 6.727 6.727 0 0 1-1.573-1.117c-2.109 0-1.41-.72-2.109-.47-.697.25-1.013.987-1.946 1.92a6.698 6.698 0 0 1-.507 1.861 10.78 10.78 0 0 0-.629 1.952c1.096-.904 2.45-1.679 3.989-2.229C-2.961.256-1.422-.003 0 0" clip-path="url(#e)" transform="matrix(1 0 0 -1 31.406 19.303)"/><path fill="#ba6e54" d="M0 0a6.63 6.63 0 0 1-1.517-2.184 14.314 14.314 0 0 0-2.751.984c.252.865.32 1.766.213 2.651.614-.334 1.274-.63 1.971-.88C-1.385.321-.688.132 0 0" clip-path="url(#f)" transform="matrix(1 0 0 -1 28.11 21.53)"/><path fill="#fcd646" d="M0 0c.239-.75.391-1.499.463-2.235v-.001C.029-3.942-2.492-6.865-4.741-7.581c-2.248-.716-5-.041-6.542 1.608a10.935 10.935 0 0 0-.916 2.091 10.586 10.586 0 0 0-.504 3.599c1.897.653 2.996 2.416 5.077 3.079 2.083.663 3.599.229 5.958.433A10.592 10.592 0 0 0 0 0" clip-path="url(#g)" transform="matrix(1 0 0 -1 12.71 6.294)"/><path fill="#d4a086" d="M0 0c.496-1.557-.419-2.95-1.94-3.434s-3.074.124-3.569 1.681C-5.822-.77-5.893.23-5.911.875c-.068 2.548-.534 5.069-1.307 7.499l-.033.102c-.04.127-.084.252-.132.375.478 1.284.8 1.739 1.273 1.89.474.151 0 0 1.511-1.003.033-.129.069-.256.109-.383l.033-.103c.773-2.429 1.85-4.756 3.267-6.875C-.832 1.841-.355 1.115 0 0" clip-path="url(#h)" transform="matrix(1 0 0 -1 16.316 29.198)"/><path fill="#d24248" d="M0 0c-1.27-1.267-3.037-2.343-5.12-3.006-2.082-.663-4.147-.806-5.915-.506.113 3.022 1.62 5.567 4.057 6.343C-4.54 3.607-1.84 2.4 0 0" clip-path="url(#i)" transform="matrix(1 0 0 -1 11.042 3.065)"/><path fill="#ec9435" d="M0 0c.024-.258.04-.516.046-.77.059-1.085 0-2.378-.35-3.734C-1-5.911-3.229-7.046-4.786-7.542c-1.557-.495-2.197-.417-4.109.304a12.287 12.287 0 0 0-2.443 2.845c-.143.212-.279.43-.409.656 1.857-.4 4.084-.291 6.332.425C-3.165-2.596-1.284-1.398 0 0" clip-path="url(#j)" transform="matrix(1 0 0 -1 13.173 8.53)"/><path fill="#d24248" d="M0 0a10.72 10.72 0 0 0-.698-1.928A6.694 6.694 0 0 1-1.27-3.77c-1.677-1.279-.686-1.428-1.393-1.653-.705-.224-1.404.171-2.712.347a6.693 6.693 0 0 1-1.531 1.172c-.624.35-1.184.747-1.684 1.17 1.42-.054 2.966.151 4.523.647C-2.51-1.592-1.129-.865 0 0" clip-path="url(#k)" transform="matrix(1 0 0 -1 12.869 13.035)"/><path fill="#ba6e54" d="M0 0a6.594 6.594 0 0 1 .119-2.656 13.854 13.854 0 0 0-1.371-.514 14.2 14.2 0 0 0-1.414-.373 6.617 6.617 0 0 1-1.438 2.237c.691.107 1.395.272 2.101.497C-1.296-.584-.627-.312 0 0" clip-path="url(#l)" transform="matrix(1 0 0 -1 11.599 16.805)"/></svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

1
qml/js/emoji/1fa88.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFCC4D" d="m29.346.937 4.95 4.95L6.012 34.17l-4.95-4.95z"/><circle cx="4.5" cy="30.75" r="3.5" fill="#C1694F"/><circle cx="3.5" cy="31.75" r="3.5" fill="#FFCC4D"/><circle cx="3.5" cy="31.75" r="2" fill="#292F33"/><circle cx="31.75" cy="3.5" r="3.5" fill="#FFCC4D"/><circle cx="25.75" cy="9.5" r="3.5" fill="#C1694F"/><circle cx="24.75" cy="10.5" r="3.5" fill="#FFCC4D"/><circle cx="7.95" cy="26.15" r="1.25" fill="#C1694F"/><circle cx="8.2" cy="26.4" r="1" fill="#292F33"/><circle cx="10.95" cy="23.15" r="1.25" fill="#C1694F"/><circle cx="11.2" cy="23.4" r="1" fill="#292F33"/><circle cx="13.95" cy="20.15" r="1.25" fill="#C1694F"/><circle cx="14.2" cy="20.4" r="1" fill="#292F33"/><circle cx="16.95" cy="17.15" r="1.25" fill="#C1694F"/><circle cx="17.2" cy="17.4" r="1" fill="#292F33"/><circle cx="19.95" cy="14.15" r="1.25" fill="#C1694F"/><circle cx="20.2" cy="14.4" r="1" fill="#292F33"/><circle cx="22.95" cy="11.15" r="1.25" fill="#C1694F"/><circle cx="23.2" cy="11.4" r="1" fill="#292F33"/><circle cx="29.95" cy="4.15" r="1.25" fill="#C1694F"/><circle cx="30.2" cy="4.4" r="1" fill="#292F33"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

1
qml/js/emoji/1faad.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#e80040" d="m18 28 17.08-10.2c.42-.25.52-.84.19-1.2C30.98 11.94 24.84 9.01 18 9.01S5.02 11.93.73 16.59c-.33.36-.24.95.19 1.2L18 27.99Z"/><path fill="#a80018" d="m18 25.79.03-.05.47-1.71V9.01c-.17 0-.33-.01-.5-.01s-.33 0-.5.01v15.02l.47 1.71zm-.81 1.26-.06.1.23.15-.03-.1zm2.99-2.79 8.25-12.81c-.3-.15-.6-.3-.91-.43l-7.68 11.93-.58 2.12.92-.81Z"/><path fill="#a80018" d="m18.01 25.81.02-.07-.03.05zm.76 1.01 14.15-12.45c-.26-.22-.53-.43-.8-.63L20.17 24.25l-.92.81-.48 1.75Zm-1.54.03-.12.11.08.09.14.15zm1.21.62.06.23.04-.02.1-.38-.13.08z"/><path fill="#a80018" d="m19.25 25.07.58-2.12 3.63-13.3c-.32-.08-.65-.15-.98-.21L18.5 24.03l-.47 1.71-.02.07.55.86.2.18v-.03zm-3.08-2.12L8.49 11.02c-.31.14-.61.28-.91.43l8.25 12.81.92.81-.58-2.12Z"/><path fill="#a80018" d="m17.97 25.74.14.53.46.4-.56-.86-.01-.02zm.54 1.64.13-.08.23-.15-.06-.1z"/><path fill="#a80018" d="m18.57 26.67-.46-.4.33 1.2.07-.09.3-.33.08-.09-.12-.11zm-1.82-1.6-.92-.81L3.87 13.75c-.27.2-.54.41-.8.63l14.15 12.45-.48-1.75Z"/><path fill="#a80018" d="m18.44 27.47-.33-1.19-.14-.53-.47-1.71-3.98-14.59c-.33.06-.66.13-.98.21l3.63 13.3.58 2.12.48 1.75v.03l.1.35.03.09.1.38.54.32.5-.3-.06-.23Z"/><path fill="#de9a7e" d="m18 28 6.59-3.94a8.628 8.628 0 0 0-13.18 0z"/><path fill="#fff" d="M12.19 23.95c1.53-1.56 3.62-2.45 5.81-2.45s4.28.88 5.81 2.45L18 27.42l-5.81-3.47Z"/><path fill="#de9a7e" d="m19.52 27.09 5.07-3.03a8.628 8.628 0 0 0-13.18 0l5.07 3.03-2.08 1.49a.38.38 0 0 0-.08.55c.93 1.15 2.24 1.87 3.69 1.87s2.75-.72 3.69-1.87c.14-.17.09-.42-.08-.55l-2.08-1.49Zm3.47-3.23-3.4 2.03c-.1-.15-.22-.28-.35-.4l1.83-2.84c.69.3 1.34.71 1.92 1.21Zm-4.61-1.84c.68.03 1.34.16 1.97.36l-1.76 2.73c-.07-.02-.14-.04-.22-.06v-3.03Zm-.75 0v3.03c-.07.02-.15.03-.22.06l-1.76-2.73c.63-.21 1.3-.33 1.97-.36Zm-4.61 1.84c.58-.5 1.23-.9 1.92-1.21l1.83 2.84c-.13.12-.25.25-.35.4l-3.4-2.03Z"/><circle cx="18" cy="27" r="1" fill="#a80018"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1
qml/js/emoji/1faae.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373d" d="M35.9 12.22c-.13-.13-.37-.13-.51 0-.24.23-11.05 10.8-11.92 11.52-.19.16-.45.16-.61 0s-.16-.42 0-.61c.72-.86 11.29-11.67 11.52-11.92.13-.14.13-.38 0-.51s-.37-.13-.51 0c-.24.23-11.49 11.24-12.35 11.95-.19.16-.45.16-.61 0s-.16-.42 0-.61c.72-.86 11.72-12.11 11.95-12.35.13-.14.13-.38 0-.51s-.37-.13-.51 0c-.24.23-11.77 11.52-12.64 12.24-.19.16-.45.16-.61 0s-.16-.42 0-.61c.72-.86 12.01-12.39 12.24-12.64.13-.14.13-.38 0-.51s-.37-.13-.51 0c-.24.23-11.92 11.67-12.78 12.38-.19.16-.45.16-.61 0s-.16-.42 0-.61c.72-.86 12.15-12.54 12.38-12.78.13-.14.13-.38 0-.51s-.37-.13-.51 0c-.24.23-11.92 11.67-12.78 12.38-.19.16-.45.16-.61 0s-.16-.42 0-.61c.72-.86 12.15-12.54 12.38-12.78.13-.14.13-.38 0-.51s-.37-.13-.51 0c-.24.23-11.77 11.52-12.64 12.24-.19.16-.45.16-.61 0s-.16-.42 0-.61c.72-.86 12.01-12.39 12.24-12.64.13-.14.13-.38 0-.51s-.37-.13-.51 0c-.24.23-11.49 11.24-12.35 11.95-.19.16-.45.16-.61 0s-.16-.42 0-.61c.72-.86 11.72-12.11 11.95-12.35.13-.14.13-.38 0-.51s-.37-.13-.51 0c-.24.23-11.05 10.8-11.92 11.52-.19.16-.45.16-.61 0s-.16-.42 0-.61C12.94 11.63 23.51.82 23.74.57c.13-.14.13-.38 0-.51s-.37-.13-.51 0c-.24.23-11.31 10.98-12.15 11.73-4.62 4.17-2.53 6.17-2.3 8.58.37 3.97-2.44 6.21-4.38 6.41-2.52.26-4.45 2.06-4.45 4.59 0 1.27.51 2.42 1.35 3.25.83.83 1.98 1.35 3.25 1.35 2.54 0 4.33-1.93 4.59-4.45.2-1.94 2.44-4.75 6.41-4.38 2.41.22 4.41 2.32 8.58-2.3.75-.83 11.5-11.9 11.73-12.15.13-.14.13-.38 0-.51zM4.56 32.69c-.35 0-.66-.14-.88-.37a1.24 1.24 0 0 1-.37-.88c0-.69.56-1.25 1.25-1.25.35 0 .66.14.88.37s.37.54.37.88c0 .69-.56 1.25-1.25 1.25"/><path fill="none" stroke="#7a8891" stroke-linecap="round" stroke-miterlimit="10" d="M3.88 28.12c-1.46.3-2.58 1.55-2.67 3.08m9.28-17.1c-1.61 2.13-1.3 2.91-.65 5.23.55 1.97.06 3.81-.65 5.23"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

1
qml/js/emoji/1faaf.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 36 36"><path fill="#9266cc" d="M36 32c0 2.2-1.8 4-4 4H4c-2.2 0-4-1.8-4-4V4c0-2.2 1.8-4 4-4h28c2.2 0 4 1.8 4 4z"/><path fill="#fff" d="M23.5 6.9s4 2.3 3.6 7.5c-.4 5.1-8.3 10.1-8.3 10.1v-1.6l2.1-1.5c-.1-.5-.2-.9-.3-1.4 2.8-1.1 4.9-3.8 4.9-7s-2-5.9-4.8-7c.1-.7.2-1 .2-1L18 2l-2.9 3s.1.3.2.9c-2.8 1.1-4.8 3.8-4.8 7s2 5.9 4.8 7c-.1.5-.2.9-.3 1.4l2.1 1.5v1.6s-7.9-4.9-8.3-10.1c-.4-5.1 3.6-7.5 3.6-7.5s-6.1 1-6.5 7.5c-.4 6.3 5.8 11 5.8 11s1.1.9 1.2 1.9l2.7-2 1 .7-3.4 2.6c-.3.2-.5.6-.5 1 0 .8.6 1.4 1.4 1.4s1.4-.6 1.4-1.4c0-.3-.1-.6-.3-.8l1.9-1.3v3c-.6.3-1 .9-1 1.6 0 1 .8 1.9 1.9 1.9s1.9-.8 1.9-1.9c0-.7-.4-1.3-1-1.6v-3l1.9 1.3c-.2.2-.3.5-.3.8 0 .8.6 1.4 1.4 1.4s1.4-.6 1.4-1.4c0-.4-.2-.8-.5-1l-3.4-2.6 1-.7 2.7 2c.1-1 1.2-1.9 1.2-1.9s6.2-4.7 5.8-11c-.5-6.4-6.6-7.4-6.6-7.4m.5 6c0 2.5-1.5 4.6-3.6 5.5-.7-4.6-.3-8.6.1-11 2 1 3.5 3.1 3.5 5.5m-12 0c0-2.4 1.5-4.5 3.6-5.5.4 2.4.7 6.4.1 11-2.2-.9-3.7-3-3.7-5.5"/></svg>

After

Width:  |  Height:  |  Size: 982 B

1
qml/js/emoji/1fabb.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#69b546" d="M31.71 28.26c-1.78-.49-4.33.36-6.77 1.57 1.9-2.33 3.91-4.76 6.1-6.08 1.82-1.09 2.07-2.09 1.86-2.82-.24-.86-1.17-1.32-2.05-1.18-4.49.7-9.71 8.85-11.86 12.52v-5.53h-2v5.53c-2.14-3.68-7.36-11.82-11.86-12.52-.88-.14-1.81.32-2.05 1.18-.2.73.05 1.73 1.86 2.82 2.19 1.32 4.2 3.75 6.1 6.08-2.44-1.21-5-2.07-6.77-1.57-.68.19-1.2.83-1.09 1.53.09.59.6 1.24 2.28 1.43 4.21.49 9.43 4.74 12.53 4.52 3.1.23 8.32-4.03 12.53-4.52 1.68-.19 2.18-.84 2.28-1.43.11-.7-.41-1.34-1.09-1.53Z"/><path fill="#894fc4" d="M23.68 6.26c1.93-4-1.93-3-1.93-3C21-.24 18 2.32 18 2.32s-3-2.56-3.75.94c0 0-3.86-1-1.93 3 0 0-3.07 1-2.07 4l.82 3.68S8.75 21.85 13 19.56c0 0-1.75 3.7 2.25 2.7 0 0-.46 3.38 1.02 5.32a2.145 2.145 0 0 0 3.45 0c1.48-1.94 1.02-5.32 1.02-5.32 4 1 2.25-2.7 2.25-2.7 4.25 2.3 1.93-5.62 1.93-5.62l.82-3.68c1-3-2.07-4-2.07-4Z"/><path fill="#ae6dee" d="M19.17 2.98c0 1.93-.52 4.28-1.17 4.28s-1.17-2.34-1.17-4.28S17.35.26 18 .26s1.17.79 1.17 2.72"/><path fill="#ae6dee" d="M19.17 10.54c0-1.93-.52-4.28-1.17-4.28s-1.17 2.34-1.17 4.28.52 2.72 1.17 2.72 1.17-.79 1.17-2.72"/><path fill="#ae6dee" d="M21.62 5.05c-1.17 1.17-2.9 2.27-3.29 1.88s.71-2.12 1.88-3.29 1.96-1.33 2.35-.94.23 1.18-.94 2.35"/><path fill="#ae6dee" d="M21.62 8.47c-1.17-1.17-2.9-2.27-3.29-1.88s.71 2.12 1.88 3.29 1.96 1.33 2.35.94.23-1.18-.94-2.35m-7.24-3.42c1.17 1.17 2.9 2.27 3.29 1.88s-.71-2.12-1.88-3.29-1.96-1.33-2.35-.94-.23 1.18.94 2.35Z"/><path fill="#ae6dee" d="M14.38 8.47c1.17-1.17 2.9-2.27 3.29-1.88s-.71 2.12-1.88 3.29-1.96 1.33-2.35.94-.23-1.18.94-2.35Z"/><circle cx="18" cy="6.76" r=".8" fill="#cd95ff"/><path fill="#ae6dee" d="M19.17 16.98c0 1.93-.52 4.28-1.17 4.28s-1.17-2.34-1.17-4.28.52-2.72 1.17-2.72 1.17.79 1.17 2.72"/><path fill="#ae6dee" d="M19.17 24.54c0-1.93-.52-4.28-1.17-4.28s-1.17 2.34-1.17 4.28.52 2.72 1.17 2.72 1.17-.79 1.17-2.72"/><path fill="#ae6dee" d="M21.62 19.05c-1.17 1.17-2.9 2.27-3.29 1.88s.71-2.12 1.88-3.29 1.96-1.33 2.35-.94.23 1.18-.94 2.35"/><path fill="#ae6dee" d="M21.62 22.47c-1.17-1.17-2.9-2.27-3.29-1.88s.71 2.12 1.88 3.29 1.96 1.33 2.35.94.23-1.18-.94-2.35m-7.24-3.42c1.17 1.17 2.9 2.27 3.29 1.88s-.71-2.12-1.88-3.29-1.96-1.33-2.35-.94-.23 1.18.94 2.35Z"/><path fill="#ae6dee" d="M14.38 22.47c1.17-1.17 2.9-2.27 3.29-1.88s-.71 2.12-1.88 3.29-1.96 1.33-2.35.94-.23-1.18.94-2.35Z"/><circle cx="18" cy="20.76" r=".8" fill="#cd95ff"/><path fill="#ae6dee" d="M22.17 13.3c1.19.62 2.8 1.04 3.01.65s-1.07-1.47-2.26-2.08-1.84-.55-2.05-.15.11.97 1.31 1.59Z"/><path fill="#ae6dee" d="M22.17 14.21c1.19-.62 2.8-1.04 3.01-.65s-1.07 1.47-2.26 2.08-1.84.55-2.05.15.11-.97 1.31-1.59Z"/><path fill="#ae6dee" d="M27.83 13.3c-1.19.62-2.8 1.04-3.01.65s1.07-1.47 2.26-2.08 1.84-.55 2.05-.15-.11.97-1.31 1.59Z"/><path fill="#ae6dee" d="M27.83 14.21c-1.19-.62-2.8-1.04-3.01-.65s1.07 1.47 2.26 2.08 1.84.55 2.05.15-.11-.97-1.31-1.59Z"/><path fill="#ae6dee" d="M26.84 11.03c-.58 1.47-1.67 3.1-2.16 2.91s-.19-2.13.39-3.61 1.21-1.92 1.7-1.73.65.95.08 2.42Z"/><path fill="#ae6dee" d="M26.84 16.49c-.58-1.47-1.67-3.1-2.16-2.91s-.19 2.13.39 3.61 1.21 1.92 1.7 1.73.65-.95.08-2.42Z"/><ellipse cx="25.35" cy="13.76" fill="#cd95ff" rx=".4" ry=".8"/><path fill="#ae6dee" d="M13.83 13.3c-1.19.62-2.8 1.04-3.01.65s1.07-1.47 2.26-2.08 1.84-.55 2.05-.15-.11.97-1.31 1.59Z"/><path fill="#ae6dee" d="M13.83 14.21c-1.19-.62-2.8-1.04-3.01-.65s1.07 1.47 2.26 2.08 1.84.55 2.05.15-.11-.97-1.31-1.59Z"/><path fill="#ae6dee" d="M8.17 13.3c1.19.62 2.8 1.04 3.01.65s-1.07-1.47-2.26-2.08-1.84-.55-2.05-.15.11.97 1.31 1.59Z"/><path fill="#ae6dee" d="M8.17 14.21c1.19-.62 2.8-1.04 3.01-.65s-1.07 1.47-2.26 2.08-1.84.55-2.05.15.11-.97 1.31-1.59Z"/><path fill="#ae6dee" d="M9.16 11.03c.58 1.47 1.67 3.1 2.16 2.91s.19-2.13-.39-3.61-1.21-1.92-1.7-1.73-.65.95-.08 2.42Z"/><path fill="#ae6dee" d="M9.16 16.49c.58-1.47 1.67-3.1 2.16-2.91s.19 2.13-.39 3.61-1.21 1.92-1.7 1.73-.65-.95-.08-2.42Z"/><ellipse cx="10.65" cy="13.76" fill="#cd95ff" rx=".4" ry=".8"/></svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

1
qml/js/emoji/1fabc.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#44abf3" d="M13.5 35c-.26 0-.53-.07-.77-.21-.71-.43-.94-1.35-.51-2.06l1.59-2.65c.45-.75.3-1.71-.34-2.29a4.804 4.804 0 0 1-.84-6.19l2.2-3.44c.45-.7 1.37-.9 2.07-.45.7.45.9 1.37.45 2.07l-2.2 3.44c-.48.75-.35 1.73.32 2.33 1.72 1.54 2.1 4.09.92 6.06l-1.59 2.65a1.5 1.5 0 0 1-1.29.73Zm-6-2c-.26 0-.53-.07-.77-.21-.71-.43-.94-1.35-.51-2.06l1.59-2.65c.45-.75.3-1.71-.34-2.29a4.804 4.804 0 0 1-.84-6.19l2.2-3.44c.45-.7 1.37-.9 2.07-.45.7.45.9 1.38.45 2.07l-2.2 3.44c-.48.75-.35 1.73.32 2.33 1.72 1.54 2.1 4.09.92 6.06L8.8 32.26a1.5 1.5 0 0 1-1.29.73Zm14.44 2a1.5 1.5 0 0 1-1.29-.73l-1.59-2.65c-1.19-1.98-.8-4.53.92-6.06.67-.6.8-1.58.32-2.33l-2.2-3.44a1.499 1.499 0 0 1 2.52-1.62l2.2 3.44c1.28 2 .93 4.6-.84 6.19-.65.58-.79 1.54-.34 2.29l1.59 2.65c.43.71.2 1.63-.51 2.06-.24.15-.51.21-.77.21Zm6-2a1.5 1.5 0 0 1-1.29-.73l-1.59-2.65c-1.19-1.98-.8-4.53.92-6.06.67-.6.8-1.58.32-2.33l-2.2-3.44a1.499 1.499 0 0 1 2.52-1.62l2.2 3.44c1.28 2 .93 4.6-.84 6.19-.65.58-.79 1.54-.34 2.29l1.59 2.65c.43.71.2 1.63-.51 2.06-.24.15-.51.21-.77.21Z"/><path fill="#44abf3" d="M18 22.27c-1.17 0-2.3-.29-3.36-.85-.71-.38-1.28-.82-1.68-1.31-.61-.75-1.5-1.17-2.42-1.17-.11 0-.21 0-.32.02-.33.04-.72.06-1.15.06-3.21 0-5.26-1.36-6.1-4.04C1.6 10.6 4.73 7.51 7.59 5.7c1.13-.72 5.7-2.68 10.42-2.68S27.3 4.99 28.43 5.7c2.87 1.82 6 4.9 4.62 9.28-.84 2.68-2.89 4.04-6.1 4.04-.43 0-.82-.03-1.15-.06-.11-.01-.21-.02-.32-.02-.92 0-1.81.43-2.42 1.17-.41.5-.97.94-1.68 1.32-1.06.57-2.2.85-3.36.85Z"/><path fill="#2b87c7" d="M18 4c4.44 0 8.86 1.88 9.89 2.53 2.57 1.63 5.39 4.36 4.21 8.14-.26.83-1.05 3.34-5.15 3.34-.34 0-.7-.02-1.05-.06-.14-.02-.28-.02-.43-.02-1.23 0-2.39.56-3.19 1.54-.32.39-.79.75-1.38 1.07-.92.49-1.89.74-2.9.74s-1.98-.25-2.9-.74c-.59-.32-1.06-.67-1.38-1.07-.8-.98-1.97-1.54-3.19-1.54-.14 0-.28 0-.43.02-.35.04-.7.06-1.05.06-4.1 0-4.89-2.51-5.15-3.34-1.18-3.78 1.63-6.51 4.21-8.14C9.13 5.88 13.55 4 18 4m0-2C13 2 8.24 4.08 7.04 4.84 4.44 6.48.36 10.02 2 15.26 3.25 19.25 6.67 20 9.06 20c.47 0 .89-.03 1.26-.07.07 0 .14-.01.22-.01.61 0 1.21.27 1.65.81.54.66 1.25 1.17 1.99 1.56 1.22.65 2.53.97 3.83.97s2.62-.32 3.83-.97c.73-.39 1.44-.9 1.99-1.56.44-.54 1.04-.81 1.65-.81.07 0 .14 0 .22.01.36.04.79.07 1.26.07 2.39 0 5.81-.75 7.06-4.74 1.64-5.25-2.44-8.78-5.04-10.43-1.19-.76-5.96-2.84-10.96-2.84Z"/><path fill="#fff" d="M8.12 12.67c-.57 1.67-.98 2.83-1.99 2.49s-1.36-1.97-.79-3.64 1.84-2.75 2.84-2.41.5 1.89-.06 3.57Z"/><path fill="#2b87c7" d="M23.12 6.98c-.61-.48-1.76-1.72-5.12-1.72s-4.51 1.25-5.12 1.72c-.78.61-.86 1.53-.19 2.06.67.53 1.85.46 2.62-.15.11-.08.24-.21.39-.35-.11.37-.18.7-.18.9 0 .86 1.11 1.56 2.48 1.56s2.48-.7 2.48-1.56c0-.21-.06-.53-.18-.9.15.15.28.27.39.35.78.61 1.95.68 2.62.15.67-.53.59-1.45-.19-2.06"/><ellipse cx="10" cy="7.5" fill="#fff" rx="1.5" ry="1" transform="rotate(-42.63 9.993 7.502)"/><path fill="#fff" d="M30.24 15.68c-.77 1.03-1.9 1.49-2.52 1.03s-.5-1.68.28-2.71 1.9-1.49 2.52-1.03.5 1.67-.27 2.71Z"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

1
qml/js/emoji/1fabd.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CAD4DB" d="M33.86 3.06c.03-.8-.92-1.26-1.53-.75-6.2 5.13-13.64 8.46-18.68 9.1-4.06.52-5.34 2.11-6.26 5.18-.29.97-.69 1.9-1.22 2.75l-3.19 5.12-.117.252c-.428.923-.873 1.881-.873 2.898 0 2.47 3.16 3.67 4.89 4.14.39.11.74-.15.82-.51l-.044-.063.078.022-1.515-4.012.563.241.026.072c.038.107.075.213.122.32v.01c.2.48.44.96.71 1.44 2.03 3.27 5.56 3.85 7.66 3.88.73.01 1.14-.83.69-1.41l-.68-.88-.036.073-2.524-2.495.763.108.067.054-.04-.05.052.007c4.516 2.8 8.073 3.373 10.298 3.333.97-.01 1.34-1.27.54-1.82l-.17-.12a23.27 23.27 0 0 1-.243-.065l-3.11-1.666.62-.19c6.053 1.66 9.432-.137 10.953-1.439.49-.42.27-1.21-.37-1.3l-1.17-.18-.137.024-5.49-.743.189-.099.208.018-.19-.027.007-.004c3.684-.136 5.926-1.552 7.103-2.609.35-.31.26-.82-.06-1.06l-.18.045-6.015-.093.102-.106.113-.006c3.99-1.03 5.97-2.54 6.95-3.79.65-.82-.07-2.01-1.11-1.87l-.73.1.008.02-4.599 1.012.32-.353c.117-.035.234-.072.351-.109l-.28.04c4.44-1.27 6.06-3.74 6.66-5.38a.795.795 0 0 0-.81-1.07l-.029.021.023-.024-4.512 2.012c-.562.18-.857.3-1.16.392a4.781 4.781 0 0 1-.234.066l.015-.025c5.22-2.61 6.277-6.232 6.377-8.442z"/><path fill="#97A8B3" d="M24.8 21.53c3.37 0 6.05-.49 7.77-.92a.69.69 0 0 0-.41-.13h-5.68l.11-.03c-3.13.17-7.02-.05-11.37-1.25.31-.41.61-.86.9-1.33.58.03 1.19.04 1.82.04 4.11 0 9.13-.64 13.78-2.96l-.02-.05-3.92.56c-3.9 1.24-7.86 1.5-11.09 1.4.27-.52.52-1.08.75-1.66h.24c2.98 0 8.64-.88 15.67-6.16a.76.76 0 0 0-.25.06l-5.29 2.27c-4.39 2.28-7.9 2.77-10 2.8.06-.18.13-.35.19-.53a.5.5 0 0 0-.33-.62c-.04-.01-.08 0-.12-.01-.22 0-.43.12-.5.35-2.41 8.04-8.32 10.1-12 10.58-.19.03-.39.05-.57.07-.28.03-.48.27-.45.54.02.26.24.45.5.45h.05c.2-.02.41-.04.64-.07.19 2.07 1.04 4.19 2.52 6.32.02-.1.03-.21 0-.32L6.69 27.1c-.27-.79-.43-1.57-.49-2.34.78-.16 1.66-.39 2.59-.75 1.92 4.58 6.46 6.91 6.52 6.94l.04-.08-1.74-2.26c-1.33-1.07-2.96-2.73-3.9-4.98.36-.16.72-.35 1.08-.55 4.17 4.34 12.35 6.58 13.49 6.88l-2.8-1.93c-2.93-1.03-7.2-2.87-9.82-5.49.4-.26.79-.55 1.19-.87 4.01 1.96 9.68 3.72 14.83 3.72 1.13 0 2.24-.09 3.3-.28l-5.25-.8c-4.31-.35-8.75-1.81-12.05-3.37.3-.28.59-.58.87-.91 3.83 1.11 7.32 1.5 10.27 1.5z"/></svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

1
qml/js/emoji/1fabf.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#ffa800" d="M20.43 33.26a.736.736 0 0 0-.32-.31s-.36-.08-.42-.17c-.67-1.12 1.29-5.33 1.29-5.33h-2l-1.21 4.9-1.11-.03a.773.773 0 0 0-.79.76c-.01.43.33.78.76.79l.86.02-.17.09c-.38.19-.53.66-.34 1.04s.66.53 1.04.34l2.07-1.06c.38-.19.53-.66.34-1.04Z"/><path fill="#ffa800" d="M25.12 31.7a.743.743 0 0 0-.44-.06s-.33.15-.44.11c-1.21-.49-2.15-5.05-2.15-5.05l-1.6 1.19 1.96 4.65-.91.64c-.35.25-.43.73-.19 1.08.25.35.73.43 1.08.19l.7-.5-.09.18c-.19.38-.03.85.35 1.04s.85.03 1.04-.35l1.03-2.08a.78.78 0 0 0-.35-1.04Z"/><path fill="#dfe7ed" d="M9.23 11.39c-1.03 1.43-1.74 3.08-1.77 4.84-.08 5.16 4.11 7.45 6.01 8.71 1.33.88 2.85 2.35 4.13 3.72 1.47 1.56 3.98 1.41 5.24-.32 1.5-2.07 3.54-4.48 5.57-5.62 2.41-1.37 3.27-3.3 3.57-4.59.16-.66-.4-1.26-1.08-1.23-3.54.17-5.43-1.96-5.43-1.96-5.02-5.02-10.04-2.38-11.55-1.38-.25.17-.6.04-.68-.25-.38-1.35-.12-3.35.77-5.42.57-1.32.76-2.79.47-4.19-.12-.61-.34-1.22-.69-1.76-1.31-2-5.31-2-6.32 1.17-1.21 3.77 1.02 2.83 1.02 2.83s3.12 2.16.75 5.45Z"/><path fill="#fc8a00" d="M4.47 4.8c.91-.22 2.04-.55 2.94-.97.31-.14.66-.02.85.27.23.35.63.73 1.34.81.42.04.52.63.17.86-1.16.74-3.06 1.42-5.44.24-.53-.26-.43-1.07.14-1.21Z"/><circle cx="10.98" cy="3.44" r="1" fill="#282f33"/><path fill="#cad4db" d="M21.43 23.72c-1.02 0-1.99-.18-2.91-.55-4.06-1.62-5.47-6.22-5.52-6.41-.08-.26.07-.54.34-.62.27-.08.54.07.62.34.01.04 1.33 4.34 4.94 5.77 2.38.95 5.25.46 8.54-1.43a.35.35 0 0 0 .17-.3c0-.06-.02-.22-.19-.3l-2.65-1.33a.488.488 0 0 1-.22-.67c.12-.25.42-.34.67-.22l2.65 1.33c.44.22.73.67.74 1.16.01.49-.24.95-.67 1.2-2.35 1.36-4.53 2.04-6.51 2.04Z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

1
qml/js/emoji/1face.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#6c1a0d" d="M.75 8.93s.24-3.25 3.01-5.01c.67-.43 1.46.33 1.08 1.03-.82 1.47-1.64 3.51-1.12 5.2.1.32.54.34.65.03.25-.73.67-1.83 1.28-2.75.37-.56 1.26-.23 1.18.44-.12.96-.21 2.12-.11 2.99.07.64.84.93 1.31.49.3-.28.62-.66.83-1.13.23-.5.96-.44 1.13.08.12.37.19.84.15 1.41-.05.85.36 1.66 1.08 2.1l2.05 1.26-1.15.94-1.92-1.28c-.63-.42-1.42-.52-2.12-.23-2.31.93-7.59 2.22-7.31-5.56Zm34.5 0s-.24-3.25-3.01-5.01c-.67-.43-1.46.33-1.08 1.03.82 1.47 1.64 3.51 1.12 5.2-.1.32-.54.34-.65.03-.25-.73-.67-1.83-1.28-2.75-.37-.56-1.26-.23-1.18.44.12.96.21 2.12.11 2.99-.07.64-.84.93-1.31.49-.3-.28-.62-.66-.83-1.13-.23-.5-.96-.44-1.13.08-.12.37-.19.84-.15 1.41.05.85-.36 1.66-1.08 2.1l-2.05 1.26 1.15.94 1.92-1.28c.63-.42 1.42-.52 2.12-.23 2.31.93 7.59 2.22 7.31-5.56Zm-15.73.53-.7.63-.49-.87c-.16-.29-.49-.29-.65 0l-.49.87-.7-.63c-.22-.2-.52-.07-.61.27L15.01 13h6l-.87-3.27c-.09-.34-.39-.47-.61-.27Z"/><path fill="#282f33" d="M13.15 15.06s-2.37-3.81-.72-4.76 3.73 3.06 3.73 3.06z"/><path fill="#c86349" d="m12.97 15.73-.25-.41c-.18-.28-1.71-2.82-1.31-4.42.11-.46.38-.81.76-1.04.39-.22.83-.27 1.27-.14 1.58.46 3 3.1 3.15 3.4l.22.43-3.85 2.18Zm-.01-5.08c-.1 0-.2.03-.29.08-.15.09-.25.22-.3.42-.21.84.49 2.38.95 3.23l2.15-1.21c-.58-.98-1.54-2.26-2.31-2.48a.761.761 0 0 0-.21-.03Z"/><path fill="#282f33" d="M22.85 15.06s2.37-3.81.72-4.76-3.73 3.06-3.73 3.06z"/><path fill="#c86349" d="m23.03 15.73-3.85-2.18.22-.43c.16-.3 1.57-2.94 3.15-3.4.45-.13.89-.08 1.28.14s.65.58.76 1.04c.4 1.6-1.14 4.14-1.31 4.42l-.25.41Zm-2.5-2.56 2.15 1.21c.47-.86 1.16-2.39.95-3.23-.05-.19-.14-.33-.29-.41-.15-.09-.31-.1-.5-.05-.83.24-1.81 1.64-2.31 2.48Z"/><circle cx="18" cy="30" r="2.5" fill="#282f33"/><path fill="#c86349" d="M18 33c-1.65 0-3-1.35-3-3s1.35-3 3-3 3 1.35 3 3-1.35 3-3 3m0-5c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2"/><path fill="#c86349" d="M25 17.5c0-3.31-3.13-6-7-6s-7 2.69-7 6c0 1.2.41 2.32 1.13 3.26.78 1.02 1 2.37.76 3.64-.25 1.33-.39 2.46-.39 3.11 0 2.76 2.46 3 5.5 3s5.5-.24 5.5-3c0-.65-.14-1.77-.39-3.11-.24-1.26-.02-2.61.76-3.64.71-.94 1.13-2.05 1.13-3.26"/><path fill="#262b2b" d="M14.4 20.01s0-1.03 1.03-1.03 1.03 1.03 1.03 1.03v1.03s0 1.03-1.03 1.03-1.03-1.03-1.03-1.03v-1.03Zm5.15 0s0-1.03 1.03-1.03 1.03 1.03 1.03 1.03v1.03s0 1.03-1.03 1.03-1.03-1.03-1.03-1.03z"/><path fill="#6c1a0d" d="M16.06 28.03c.36-.09.51-.76.33-1.49s-.63-1.25-1-1.15-.51.76-.33 1.49.63 1.25 1 1.15Zm4.86-1.16c.19-.73.04-1.4-.33-1.49s-.81.42-1 1.15-.04 1.4.33 1.49.81-.42 1-1.15"/></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

1
qml/js/emoji/1facf.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#738695" d="m16.07 26.09 3.23 6.04c.1.18.35.22.49.07l1.49-1.49c.12-.12.12-.31 0-.43l-2.24-2.43a1.03 1.03 0 0 1-.27-.69v-3.72l-3.28-1.94-1 3 1.57 1.59Zm5.7-2.88S23 26 25 27l-1.33 4.07c-.08.2.07.43.29.43h1.52c.14 0 .26-.09.3-.22l1.72-5.78-3-5.38-3 2z"/><path fill="#bfccd5" d="M6.97 5s-1.86 3.92-.18 5.5h1.14S9.02 8.48 6.97 5"/><path fill="#738695" d="M7.02 8.82s.38-1.7 3.48-2.32c1.36-.27 2.42.05 3.11.39.48.24.57.89.16 1.25l-.76.67s1.72-.24 1.99.18c.22.33-.5 1.5-.5 1.5s1.08.02 1.5.5c.11.13.52.54.94.96.69.68 1.54 1.16 2.48 1.4l2.57.64-6.5-.5-4.04-2.91-4.44-1.77Z"/><path fill="#bfccd5" d="M31.4 12.92c-1.91-2.01-5.4-2.75-8.9-.42-4.85 3.23-10-2-10-2-3.36-2.69-5.37-2.21-6.32-1.61-.38.24-.67.59-.87.99l-2.2 4.36c-.84 1.66.82 3.47 2.55 2.79L9.01 16c.71 2.83 2.17 4.41 2.96 5.09.35.3.58.72.64 1.18l.9 7.23-.75 4.05c-.05.27.15.51.42.51h1.89c.24 0 .43-.19.43-.43V29.5s.63-3.81.89-5.36c.06-.34.4-.53.72-.42 3.39 1.12 5.78-.06 6.81-.77.28-.19.67-.08.78.25 1.15 3.42 3.79 4.3 3.79 4.3l-1.34 6.04c-.06.27.15.53.42.53h1.58c.2 0 .37-.13.42-.32l1.81-6.86c.06-.24.03-.51-.1-.73l-.78-1.36a.788.788 0 0 1-.06-.67l2.03-5.63c.06-.16.1-.32.15-.48.22 1.19.35 2.66.35 4.48 0 .28.22.5.5.5s.5-.22.5-.5c0-5.84-1.37-8.43-2.6-9.58Z"/><path fill="#dfe7ed" d="M5.82 15.37S6.5 11.5 2.5 13.5c-2 1-1.06 2.92.36 3.98 1.04.77 2.53.6 3.33-.41.5-.63.67-1.31-.38-1.7Z"/><path fill="#bfccd5" d="M10.38 4.3s-2.86 3.97-1.32 6.06l1.26.23s1.62-2.02.06-6.3Z"/><path fill="#738695" d="M9.76 8.82c.25.1.52.23.79.38.11-.74.08-1.78-.43-3.18 0 0-.88 1.26-1.08 2.59.24.04.48.11.71.2Zm-6 6.12c.13-.52.06-.98-.15-1.04s-.5.32-.63.84-.06.98.15 1.04.5-.32.63-.84"/><path d="M7.2 12.82c.44 0 .8-.36.8-.8s-.36-.8-.8-.8-.8.36-.8.8.36.8.8.8"/><path fill="#738695" d="M13.18 34.06h1.89c.24 0 .43-.19.43-.43V33h-2.65l-.1.55c-.05.27.15.51.42.51ZM27.28 33l-.12.54c-.06.27.15.53.42.53h1.58c.2 0 .37-.13.42-.32l.2-.74h-2.5Zm6.21-11.18c-.98 0-1.93 1.45 0 3.91 0 0 2.44-3.92 0-3.91"/></svg>

After

Width:  |  Height:  |  Size: 2 KiB

1
qml/js/emoji/1fada.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#f1b777" d="M16.4 26.75c.52.82 1.17 2 1.81 3.58.69 1.71 2.05 3.05 3.8 3.64.05.02.09.03.14.05 3.88 1.31 8.02-1.13 8.75-5.15.22-1.19-.05-2.42-.74-3.42l-1.32-1.9c-.3-.43-.25-1.01.13-1.37 1.35-1.29 4.18-4.55 3.34-8.56-.08-.4-2.81-4.11-6.72-1.05a1.77 1.77 0 0 0-.4 1.96c.36.84.71 2.15.48 3.73-.04.27-.41.32-.51.07-.42-.99-1.31-2.54-2.93-3.14-.42-.16-.71-.55-.71-1v-.98c0-.41.25-.78.63-.91 1.17-.42 3.61-1.55 4.37-3.8 0 0 .49-1.47-.59-2.35-.9-.74-2.29-.51-3.01.4-.38.48-.96.95-1.75.95 0 0 .08-.78.06-1.73a3.497 3.497 0 0 0-2.92-3.37h-.08c-2.49-.42-5.71.1-7.88 2.44-.51.55-1.01 1.1-1.2 1.83-.6 2.35-1.33 8.02 2.85 14.83 0 0-3.43-.77-4.95-4.31a2.197 2.197 0 0 0-2.46-1.3c-1.4.28-2.51 1.36-2.81 2.76-.41 1.87.14 3.82 1.47 5.2 1.9 1.96 5.27 3.9 10.54 2.02.98-.35 2.07 0 2.63.88Z"/><path fill="#ffd875" d="M29.06 27.58c-.69-1.6-3.11-2.09-5.41-1.1-2.3.99-3.6 3.09-2.91 4.69.69 1.6 3.11 2.09 5.41 1.1s3.6-3.09 2.91-4.69Z"/><path fill="#ea9e48" d="M20.21 24.17c-1.56 1.01-2.54 2.4-3.17 3.69.19.35.39.74.59 1.17.52-1.35 1.46-2.96 3.12-4.03 2.16-1.39 5.07-1.59 8.66-.63l-.58-.84c-.08-.11-.13-.24-.16-.36-3.43-.74-6.27-.42-8.46 1m-16.99-.32c.14.14.29.29.45.43-.14-1.9.23-3.4 1.09-4.48.83-1.04 1.96-1.47 2.77-1.65-.17-.28-.34-.59-.48-.92-.93.25-2.13.79-3.06 1.94-.83 1.03-1.27 2.37-1.33 3.98.17.25.36.48.57.7Zm6.8-3.21c-.51-.32-1.07-.74-1.59-1.3-.78.65-1.84 1.79-2.25 3.48-.24 1-.23 2.04.04 3.11.7.3 1.47.52 2.34.63-.53-1.17-.68-2.26-.44-3.24.34-1.42 1.4-2.32 1.9-2.68m6.88-1.34c1.98-2.42 5.18-3.07 6.72-3.24a4.27 4.27 0 0 0-1.27-.82c-1.83.36-4.42 1.23-6.21 3.42-1.49 1.82-2.12 4.2-1.92 7.1.34-.05.69-.02 1.01.08-.22-2.7.33-4.9 1.68-6.54Zm4.6-5.24v-.85c0-.41.25-.78.63-.91.06-.02.13-.05.2-.08-2.73-1.63-5.39-2.16-7.93-1.55-2.46.59-4.27 2.13-5.4 3.39.15.78.35 1.59.63 2.44.64-.94 2.46-3.22 5.25-3.89 2.06-.49 4.29 0 6.62 1.45ZM8.94 7.5c-.08.43-.16.91-.21 1.46 2.43-2.11 7.03-5.05 12.46-3.32-.02-.39-.1-.77-.25-1.12-5.04-1.36-9.34.91-12 2.98Zm14.98-1.67c-.36.13-.69.35-.95.65.76.34 1.62.87 2.09 1.71.35.62.43 1.34.27 2.16a5.2 5.2 0 0 0 1.02-1.48c-.08-.41-.21-.8-.42-1.17-.48-.86-1.25-1.45-2.01-1.86Zm8.51 8.7c-1.28-1.21-2.63-1.88-4.03-1.98-1.4-.09-2.54.41-3.3.87-.09.36-.07.74.08 1.09 0 .01.01.03.02.05.54-.41 1.7-1.12 3.14-1.01 1.39.1 2.76.91 4.06 2.39.05-.45.07-.92.03-1.41m-3.98.82c-.97-.3-1.96-.25-2.96.12.08.31.14.65.18 1.02.85-.36 1.68-.43 2.48-.18 1.46.44 2.56 1.77 3.21 2.75.18-.33.34-.68.49-1.05-.76-1.03-1.91-2.21-3.41-2.66Z"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

1
qml/js/emoji/1fadb.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#4e932b" d="M28 5C12 5 2.03 33.73 2.03 33.73c.01.24.21.42.45.4l8.78-5.46s.36-.61.83-1.4c.75.3 1.57.45 2.43.4a5.789 5.789 0 0 0 5.44-5.08 6.373 6.373 0 0 0 4.31-6.19c3.64-.44 6.18-4.23 4.61-8.08-.62-1.52.75-3.33-.89-3.33Z"/><circle cx="7.7" cy="30.6" r="3.3" fill="#9bd57f"/><path fill="#4e932b" d="M7 25s-2 4 1.7 6c.26.14 2.46-1.81 2.38-1.4S7.4 25.11 7.4 25.11z"/><circle cx="12.16" cy="26.74" r="5.16" fill="#9bd57f"/><path fill="#4e932b" d="M11 20s-4 2 1 8l4 1z"/><circle cx="15.5" cy="21.5" r="5.5" fill="#9bd57f"/><path fill="#4e932b" d="M13.8 15.12S12.48 21.99 19 24l1.42-1.14-6.63-7.74Z"/><circle cx="19.75" cy="16.33" r="5.58" fill="#9bd57f"/><path fill="#4e932b" d="M19.27 9.73s-1.49 5.78 2.9 8.38l5.69-2.55-8.6-5.83Z"/><circle cx="24.44" cy="11.46" r="4.96" fill="#9bd57f"/><path fill="#69b546" d="M2.35 34.03C4.2 33.16 19 22 29.25 8.47l1.71 1S21 38 2.39 34.23c-.1-.02-.13-.15-.04-.2Z"/><path fill="#4e932b" d="M32.64 3.18c2.46.88 2.24-1.04 1.69-1.62-.81-.84-2.22-.94-3.3-.07-.35.28-.83.89-1.32 1.59-.2.29-.57.38-.89.25-2.85-1.16-5.8-1.23-9.4.4 0 0 2.41 3.35 7.37 3.46 0 0-3.37 3.24-.99 8.24 0 0 3.35-2.41 3.42-5.71 0 0 1.6 2.51 4.91 2.58 0 0-.61-5.8-3.37-7.95-.18-.14-.16-.43.04-.56.74-.47 1.43-.77 1.87-.61Z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

1
qml/js/emoji/1fae8.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><defs><clipPath id="a"><path fill="none" d="M.997 18 17.996 1.001l16.999 17-17 16.998z"/></clipPath></defs><g clip-path="url(#a)"><path fill="#ffcb26" d="M26.5 26.5c-4.69 4.69-12.31 4.69-17 0-4.69-4.69-4.69-12.31 0-17s12.31-4.69 17 0c4.69 4.69 4.69 12.31 0 17"/><path fill="#b68600" d="M14.93 13.99c.65.65 1.92.44 2.83-.47.91-.91 1.12-2.18.47-2.83s-1.92-.44-2.83.47-1.12 2.18-.47 2.83Z"/><path fill="#b68600" d="M16.35 15.4c.65.65 1.92.44 2.83-.47.91-.91 1.12-2.18.47-2.83s-1.92-.44-2.83.47-1.12 2.18-.47 2.83m5.66 5.67c.65.65 1.92.44 2.83-.47s1.12-2.18.47-2.83-1.92-.44-2.83.47-1.12 2.18-.47 2.83Z"/><path fill="#b68600" d="M20.6 19.65c.65.65 1.92.44 2.83-.47s1.12-2.18.47-2.83-1.92-.44-2.83.47-1.12 2.18-.47 2.83"/><path fill="#694400" d="M12.33 23.67c1.04 1.04 2.95.83 4.25-.47s1.52-3.21.47-4.25-2.95-.83-4.25.47-1.52 3.21-.47 4.25Z"/></g><path fill="#4facf0" d="M21.89 33.66c-.46 0-.88-.32-.98-.79-.11-.54.23-1.07.77-1.19 8.4-1.78 9.92-9.63 9.99-9.96.1-.54.62-.9 1.16-.81s.9.62.81 1.16c-.02.09-1.79 9.5-11.54 11.57-.07.01-.14.02-.21.02Zm5.31 2.33c-.46 0-.88-.32-.98-.79-.11-.54.23-1.07.77-1.19 5.89-1.25 6.97-6.75 7.01-6.99.1-.54.62-.9 1.16-.8.54.1.9.61.81 1.16-.05.29-1.33 7.06-8.57 8.59-.07.01-.14.02-.21.02ZM3.34 15.1c-.07 0-.14 0-.21-.02a.998.998 0 0 1-.77-1.19C4.43 4.15 13.83 2.37 13.93 2.36a1 1 0 1 1 .35 1.97c-.35.06-8.18 1.6-9.96 9.99-.1.47-.52.79-.98.79ZM1.01 9.8c-.07 0-.14 0-.21-.02a.998.998 0 0 1-.77-1.19C1.56 1.36 8.34.08 8.62.03A1 1 0 1 1 8.97 2c-.26.05-5.74 1.14-6.99 7.01-.1.47-.51.79-.98.79Z"/><path fill="#694400" d="M15.64 14.69c.65.65 1.92.44 2.83-.47.91-.91 1.12-2.18.47-2.83s-1.92-.44-2.83.47-1.12 2.18-.47 2.83Zm5.67 5.67c.65.65 1.92.44 2.83-.47s1.12-2.18.47-2.83-1.92-.44-2.83.47-1.12 2.18-.47 2.83"/></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E0AA94" d="M16.5 31c-.64 0-1.28-.24-1.77-.73C9.28 24.82 6.79 5.49 6.52 3.3 6.35 1.93 7.33.68 8.7.52c1.38-.17 2.62.81 2.78 2.18.93 7.6 3.59 20.84 6.79 24.04a2.499 2.499 0 0 1-1.77 4.27Z"/><path fill="#F7DECE" d="m28.9 25.33-8.75 1.15c-2.15.51-3.94-1.06-5.01-2.8l-.17-.38c-1.3-2.86-2.22-5.87-2.73-8.97L10.32 2.64a2.392 2.392 0 0 0-4.77.31l.22 12.21c0 .07-.02.14-.05.21-.78 1.3-1.35 3.72.04 7.88-.15.77-.44 1.48-.92 2.69-1.28 4.82 2.35 9.55 7.34 9.55h16.51a2.76 2.76 0 0 0 2.76-2.76v-5.16c0-1.35-1.19-2.4-2.54-2.22Z"/><path fill="#E0AA94" d="M12.21 26.47c-.77-1.68-1.11-3.93-1.47-5.82-.22-1.26-.42-2.55-.6-3.81-.05-1.6-1.43-2.97-3.04-2.74-.47.01-.92.11-1.35.26l.02.86c1.24-.57 2.76-.43 3.19 1.06.44 3.71.43 7.3 1.67 10.9.47 1.09 2.09.36 1.59-.71ZM26.55 34H13.33c-1.55 0-3.09-.15-4.61-.46l-2.04-.41a7.564 7.564 0 0 0 5.49 2.35h16.51c1.25 0 2.3-.84 2.64-1.98-1.57.32-3.16.5-4.76.5Z"/></svg>

After

Width:  |  Height:  |  Size: 958 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D2A077" d="M16.5 31c-.64 0-1.28-.24-1.77-.73C9.28 24.82 6.79 5.49 6.52 3.3 6.35 1.93 7.33.68 8.7.52c1.38-.17 2.62.81 2.78 2.18.93 7.6 3.59 20.84 6.79 24.04a2.499 2.499 0 0 1-1.77 4.27Z"/><path fill="#F3D2A2" d="m28.9 25.33-8.75 1.15c-2.15.51-3.94-1.06-5.01-2.8l-.17-.38c-1.3-2.86-2.22-5.87-2.73-8.97L10.32 2.64a2.392 2.392 0 0 0-4.77.31l.22 12.21c0 .07-.02.14-.05.21-.78 1.3-1.35 3.72.04 7.88-.15.77-.44 1.48-.92 2.69-1.28 4.82 2.35 9.55 7.34 9.55h16.51a2.76 2.76 0 0 0 2.76-2.76v-5.16c0-1.35-1.19-2.4-2.54-2.22Z"/><path fill="#D2A077" d="M12.21 26.47c-.77-1.68-1.11-3.93-1.47-5.82-.22-1.26-.42-2.55-.6-3.81-.05-1.6-1.43-2.97-3.04-2.74-.47.01-.92.11-1.35.26l.02.86c1.24-.57 2.76-.43 3.19 1.06.44 3.71.43 7.3 1.67 10.9.47 1.09 2.09.36 1.59-.71ZM26.55 34H13.33c-1.55 0-3.09-.15-4.61-.46l-2.04-.41a7.564 7.564 0 0 0 5.49 2.35h16.51c1.25 0 2.3-.84 2.64-1.98-1.57.32-3.16.5-4.76.5Z"/></svg>

After

Width:  |  Height:  |  Size: 958 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#B78B60" d="M16.5 31c-.64 0-1.28-.24-1.77-.73C9.28 24.82 6.79 5.49 6.52 3.3 6.35 1.93 7.33.68 8.7.52c1.38-.17 2.62.81 2.78 2.18.93 7.6 3.59 20.84 6.79 24.04a2.499 2.499 0 0 1-1.77 4.27Z"/><path fill="#D5AB88" d="m28.9 25.33-8.75 1.15c-2.15.51-3.94-1.06-5.01-2.8l-.17-.38c-1.3-2.86-2.22-5.87-2.73-8.97L10.32 2.64a2.392 2.392 0 0 0-4.77.31l.22 12.21c0 .07-.02.14-.05.21-.78 1.3-1.35 3.72.04 7.88-.15.77-.44 1.48-.92 2.69-1.28 4.82 2.35 9.55 7.34 9.55h16.51a2.76 2.76 0 0 0 2.76-2.76v-5.16c0-1.35-1.19-2.4-2.54-2.22Z"/><path fill="#B78B60" d="M12.21 26.47c-.77-1.68-1.11-3.93-1.47-5.82-.22-1.26-.42-2.55-.6-3.81-.05-1.6-1.43-2.97-3.04-2.74-.47.01-.92.11-1.35.26l.02.86c1.24-.57 2.76-.43 3.19 1.06.44 3.71.43 7.3 1.67 10.9.47 1.09 2.09.36 1.59-.71ZM26.55 34H13.33c-1.55 0-3.09-.15-4.61-.46l-2.04-.41a7.564 7.564 0 0 0 5.49 2.35h16.51c1.25 0 2.3-.84 2.64-1.98-1.57.32-3.16.5-4.76.5Z"/></svg>

After

Width:  |  Height:  |  Size: 958 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path xmlns="http://www.w3.org/2000/svg" fill="#90603E" d="M16.5 31c-.64 0-1.28-.24-1.77-.73C9.28 24.82 6.79 5.49 6.52 3.3 6.35 1.93 7.33.68 8.7.52c1.38-.17 2.62.81 2.78 2.18.93 7.6 3.59 20.84 6.79 24.04a2.499 2.499 0 0 1-1.77 4.27Z"/><path fill="#AF7E57" d="m28.9 25.33-8.75 1.15c-2.15.51-3.94-1.06-5.01-2.8l-.17-.38c-1.3-2.86-2.22-5.87-2.73-8.97L10.32 2.64a2.392 2.392 0 0 0-4.77.31l.22 12.21c0 .07-.02.14-.05.21-.78 1.3-1.35 3.72.04 7.88-.15.77-.44 1.48-.92 2.69-1.28 4.82 2.35 9.55 7.34 9.55h16.51a2.76 2.76 0 0 0 2.76-2.76v-5.16c0-1.35-1.19-2.4-2.54-2.22Z"/><path xmlns="http://www.w3.org/2000/svg" fill="#90603E" d="M12.21 26.47c-.77-1.68-1.11-3.93-1.47-5.82-.22-1.26-.42-2.55-.6-3.81-.05-1.6-1.43-2.97-3.04-2.74-.47.01-.92.11-1.35.26l.02.86c1.24-.57 2.76-.43 3.19 1.06.44 3.71.43 7.3 1.67 10.9.47 1.09 2.09.36 1.59-.71ZM26.55 34H13.33c-1.55 0-3.09-.15-4.61-.46l-2.04-.41a7.564 7.564 0 0 0 5.49 2.35h16.51c1.25 0 2.3-.84 2.64-1.98-1.57.32-3.16.5-4.76.5Z"/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#583529" d="M16.5 31c-.64 0-1.28-.24-1.77-.73C9.28 24.82 6.79 5.49 6.52 3.3 6.35 1.93 7.33.68 8.7.52c1.38-.17 2.62.81 2.78 2.18.93 7.6 3.59 20.84 6.79 24.04a2.499 2.499 0 0 1-1.77 4.27Z"/><path fill="#7C533E" d="m28.9 25.33-8.75 1.15c-2.15.51-3.94-1.06-5.01-2.8l-.17-.38c-1.3-2.86-2.22-5.87-2.73-8.97L10.32 2.64a2.392 2.392 0 0 0-4.77.31l.22 12.21c0 .07-.02.14-.05.21-.78 1.3-1.35 3.72.04 7.88-.15.77-.44 1.48-.92 2.69-1.28 4.82 2.35 9.55 7.34 9.55h16.51a2.76 2.76 0 0 0 2.76-2.76v-5.16c0-1.35-1.19-2.4-2.54-2.22Z"/><path fill="#583529" d="M12.21 26.47c-.77-1.68-1.11-3.93-1.47-5.82-.22-1.26-.42-2.55-.6-3.81-.05-1.6-1.43-2.97-3.04-2.74-.47.01-.92.11-1.35.26l.02.86c1.24-.57 2.76-.43 3.19 1.06.44 3.71.43 7.3 1.67 10.9.47 1.09 2.09.36 1.59-.71ZM26.55 34H13.33c-1.55 0-3.09-.15-4.61-.46l-2.04-.41a7.564 7.564 0 0 0 5.49 2.35h16.51c1.25 0 2.3-.84 2.64-1.98-1.57.32-3.16.5-4.76.5Z"/></svg>

After

Width:  |  Height:  |  Size: 958 B

1
qml/js/emoji/1faf7.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EF9645" d="M16.5 31c-.64 0-1.28-.24-1.77-.73C9.28 24.82 6.79 5.49 6.52 3.3 6.35 1.93 7.33.68 8.7.52c1.38-.17 2.62.81 2.78 2.18.93 7.6 3.59 20.84 6.79 24.04a2.499 2.499 0 0 1-1.77 4.27Z"/><path fill="#FFDC5D" d="m28.9 25.33-8.75 1.15c-2.15.51-3.94-1.06-5.01-2.8l-.17-.38c-1.3-2.86-2.22-5.87-2.73-8.97L10.32 2.64a2.392 2.392 0 0 0-4.77.31l.22 12.21c0 .07-.02.14-.05.21-.78 1.3-1.35 3.72.04 7.88-.15.77-.44 1.48-.92 2.69-1.28 4.82 2.35 9.55 7.34 9.55h16.51a2.76 2.76 0 0 0 2.76-2.76v-5.16c0-1.35-1.19-2.4-2.54-2.22Z"/><path fill="#EF9645" d="M12.21 26.47c-.77-1.68-1.11-3.93-1.47-5.82-.22-1.26-.42-2.55-.6-3.81-.05-1.6-1.43-2.97-3.04-2.74-.47.01-.92.11-1.35.26l.02.86c1.24-.57 2.76-.43 3.19 1.06.44 3.71.43 7.3 1.67 10.9.47 1.09 2.09.36 1.59-.71ZM26.55 34H13.33c-1.55 0-3.09-.15-4.61-.46l-2.04-.41a7.564 7.564 0 0 0 5.49 2.35h16.51c1.25 0 2.3-.84 2.64-1.98-1.57.32-3.16.5-4.76.5Z"/></svg>

After

Width:  |  Height:  |  Size: 958 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#E0AA94" d="M19.5 31c.64 0 1.28-.24 1.77-.73 5.45-5.45 7.95-24.78 8.21-26.96.17-1.37-.81-2.62-2.18-2.78-1.38-.17-2.62.81-2.78 2.18-.93 7.6-3.59 20.84-6.79 24.04a2.499 2.499 0 0 0 1.77 4.27Z"/><path fill="#F7DECE" d="m7.1 25.33 8.75 1.15c2.15.51 3.94-1.06 5.01-2.8l.17-.38c1.3-2.86 2.22-5.87 2.73-8.97l1.92-11.69a2.392 2.392 0 0 1 4.77.31l-.22 12.21c0 .07.02.14.05.21.78 1.3 1.35 3.72-.04 7.88.15.77.44 1.48.92 2.69 1.28 4.82-2.35 9.55-7.34 9.55H7.33a2.76 2.76 0 0 1-2.76-2.76v-5.16c0-1.35 1.19-2.4 2.54-2.22Z"/><path fill="#E0AA94" d="M23.79 26.47c.77-1.68 1.11-3.93 1.47-5.82.22-1.26.42-2.55.6-3.81.05-1.6 1.43-2.97 3.04-2.74.47.01.92.11 1.35.26l-.02.86c-1.24-.57-2.76-.43-3.19 1.06-.44 3.71-.43 7.3-1.67 10.9-.47 1.09-2.09.36-1.59-.71ZM9.45 34h13.22c1.55 0 3.09-.15 4.61-.46l2.04-.41a7.564 7.564 0 0 1-5.49 2.35H7.33c-1.25 0-2.3-.84-2.64-1.98 1.57.32 3.16.5 4.76.5"/></svg>

After

Width:  |  Height:  |  Size: 947 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D2A077" d="M19.5 31c.64 0 1.28-.24 1.77-.73 5.45-5.45 7.95-24.78 8.21-26.96.17-1.37-.81-2.62-2.18-2.78-1.38-.17-2.62.81-2.78 2.18-.93 7.6-3.59 20.84-6.79 24.04a2.499 2.499 0 0 0 1.77 4.27Z"/><path fill="#F3D2A2" d="m7.1 25.33 8.75 1.15c2.15.51 3.94-1.06 5.01-2.8l.17-.38c1.3-2.86 2.22-5.87 2.73-8.97l1.92-11.69a2.392 2.392 0 0 1 4.77.31l-.22 12.21c0 .07.02.14.05.21.78 1.3 1.35 3.72-.04 7.88.15.77.44 1.48.92 2.69 1.28 4.82-2.35 9.55-7.34 9.55H7.33a2.76 2.76 0 0 1-2.76-2.76v-5.16c0-1.35 1.19-2.4 2.54-2.22Z"/><path fill="#D2A077" d="M23.79 26.47c.77-1.68 1.11-3.93 1.47-5.82.22-1.26.42-2.55.6-3.81.05-1.6 1.43-2.97 3.04-2.74.47.01.92.11 1.35.26l-.02.86c-1.24-.57-2.76-.43-3.19 1.06-.44 3.71-.43 7.3-1.67 10.9-.47 1.09-2.09.36-1.59-.71ZM9.45 34h13.22c1.55 0 3.09-.15 4.61-.46l2.04-.41a7.564 7.564 0 0 1-5.49 2.35H7.33c-1.25 0-2.3-.84-2.64-1.98 1.57.32 3.16.5 4.76.5Z"/></svg>

After

Width:  |  Height:  |  Size: 948 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#B78B60" d="M19.5 31c.64 0 1.28-.24 1.77-.73 5.45-5.45 7.95-24.78 8.21-26.96.17-1.37-.81-2.62-2.18-2.78-1.38-.17-2.62.81-2.78 2.18-.93 7.6-3.59 20.84-6.79 24.04a2.499 2.499 0 0 0 1.77 4.27Z"/><path fill="#D5AB88" d="m7.1 25.33 8.75 1.15c2.15.51 3.94-1.06 5.01-2.8l.17-.38c1.3-2.86 2.22-5.87 2.73-8.97l1.92-11.69a2.392 2.392 0 0 1 4.77.31l-.22 12.21c0 .07.02.14.05.21.78 1.3 1.35 3.72-.04 7.88.15.77.44 1.48.92 2.69 1.28 4.82-2.35 9.55-7.34 9.55H7.33a2.76 2.76 0 0 1-2.76-2.76v-5.16c0-1.35 1.19-2.4 2.54-2.22Z"/><path fill="#B78B60" d="M23.79 26.47c.77-1.68 1.11-3.93 1.47-5.82.22-1.26.42-2.55.6-3.81.05-1.6 1.43-2.97 3.04-2.74.47.01.92.11 1.35.26l-.02.86c-1.24-.57-2.76-.43-3.19 1.06-.44 3.71-.43 7.3-1.67 10.9-.47 1.09-2.09.36-1.59-.71ZM9.45 34h13.22c1.55 0 3.09-.15 4.61-.46l2.04-.41a7.564 7.564 0 0 1-5.49 2.35H7.33c-1.25 0-2.3-.84-2.64-1.98 1.57.32 3.16.5 4.76.5"/></svg>

After

Width:  |  Height:  |  Size: 947 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#90603E" d="M19.5 31c.64 0 1.28-.24 1.77-.73 5.45-5.45 7.95-24.78 8.21-26.96.17-1.37-.81-2.62-2.18-2.78-1.38-.17-2.62.81-2.78 2.18-.93 7.6-3.59 20.84-6.79 24.04a2.499 2.499 0 0 0 1.77 4.27Z"/><path fill="#AF7E57" d="m7.1 25.33 8.75 1.15c2.15.51 3.94-1.06 5.01-2.8l.17-.38c1.3-2.86 2.22-5.87 2.73-8.97l1.92-11.69a2.392 2.392 0 0 1 4.77.31l-.22 12.21c0 .07.02.14.05.21.78 1.3 1.35 3.72-.04 7.88.15.77.44 1.48.92 2.69 1.28 4.82-2.35 9.55-7.34 9.55H7.33a2.76 2.76 0 0 1-2.76-2.76v-5.16c0-1.35 1.19-2.4 2.54-2.22Z"/><path fill="#90603E" d="M23.79 26.47c.77-1.68 1.11-3.93 1.47-5.82.22-1.26.42-2.55.6-3.81.05-1.6 1.43-2.97 3.04-2.74.47.01.92.11 1.35.26l-.02.86c-1.24-.57-2.76-.43-3.19 1.06-.44 3.71-.43 7.3-1.67 10.9-.47 1.09-2.09.36-1.59-.71ZM9.45 34h13.22c1.55 0 3.09-.15 4.61-.46l2.04-.41a7.564 7.564 0 0 1-5.49 2.35H7.33c-1.25 0-2.3-.84-2.64-1.98 1.57.32 3.16.5 4.76.5"/></svg>

After

Width:  |  Height:  |  Size: 947 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#583529" d="M19.5 31c.64 0 1.28-.24 1.77-.73 5.45-5.45 7.95-24.78 8.21-26.96.17-1.37-.81-2.62-2.18-2.78-1.38-.17-2.62.81-2.78 2.18-.93 7.6-3.59 20.84-6.79 24.04a2.499 2.499 0 0 0 1.77 4.27Z"/><path fill="#7C533E" d="m7.1 25.33 8.75 1.15c2.15.51 3.94-1.06 5.01-2.8l.17-.38c1.3-2.86 2.22-5.87 2.73-8.97l1.92-11.69a2.392 2.392 0 0 1 4.77.31l-.22 12.21c0 .07.02.14.05.21.78 1.3 1.35 3.72-.04 7.88.15.77.44 1.48.92 2.69 1.28 4.82-2.35 9.55-7.34 9.55H7.33a2.76 2.76 0 0 1-2.76-2.76v-5.16c0-1.35 1.19-2.4 2.54-2.22Z"/><path fill="#583529" d="M23.79 26.47c.77-1.68 1.11-3.93 1.47-5.82.22-1.26.42-2.55.6-3.81.05-1.6 1.43-2.97 3.04-2.74.47.01.92.11 1.35.26l-.02.86c-1.24-.57-2.76-.43-3.19 1.06-.44 3.71-.43 7.3-1.67 10.9-.47 1.09-2.09.36-1.59-.71ZM9.45 34h13.22c1.55 0 3.09-.15 4.61-.46l2.04-.41a7.564 7.564 0 0 1-5.49 2.35H7.33c-1.25 0-2.3-.84-2.64-1.98 1.57.32 3.16.5 4.76.5"/></svg>

After

Width:  |  Height:  |  Size: 947 B

1
qml/js/emoji/1faf8.svg Normal file
View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#EF9645" d="M19.5 31c.64 0 1.28-.24 1.77-.73 5.45-5.45 7.95-24.78 8.21-26.96.17-1.37-.81-2.62-2.18-2.78-1.38-.17-2.62.81-2.78 2.18-.93 7.6-3.59 20.84-6.79 24.04a2.499 2.499 0 0 0 1.77 4.27Z"/><path fill="#FFDC5D" d="m7.1 25.33 8.75 1.15c2.15.51 3.94-1.06 5.01-2.8l.17-.38c1.3-2.86 2.22-5.87 2.73-8.97l1.92-11.69a2.392 2.392 0 0 1 4.77.31l-.22 12.21c0 .07.02.14.05.21.78 1.3 1.35 3.72-.04 7.88.15.77.44 1.48.92 2.69 1.28 4.82-2.35 9.55-7.34 9.55H7.33a2.76 2.76 0 0 1-2.76-2.76v-5.16c0-1.35 1.19-2.4 2.54-2.22Z"/><path fill="#EF9645" d="M23.79 26.47c.77-1.68 1.11-3.93 1.47-5.82.22-1.26.42-2.55.6-3.81.05-1.6 1.43-2.97 3.04-2.74.47.01.92.11 1.35.26l-.02.86c-1.24-.57-2.76-.43-3.19 1.06-.44 3.71-.43 7.3-1.67 10.9-.47 1.09-2.09.36-1.59-.71ZM9.45 34h13.22c1.55 0 3.09-.15 4.61-.46l2.04-.41a7.564 7.564 0 0 1-5.49 2.35H7.33c-1.25 0-2.3-.84-2.64-1.98 1.57.32 3.16.5 4.76.5"/></svg>

After

Width:  |  Height:  |  Size: 947 B

View file

@ -517,7 +517,7 @@ function handleErrorMessage(code, message) {
}
function getMessagesNeededForwardPermissions(messages) {
var neededPermissions = ["can_send_messages"]
var neededPermissions = ["can_send_basic_messages"]
var mediaMessageTypes = ["messageAudio", "messageDocument", "messagePhoto", "messageVideo", "messageVideoNote", "messageVoiceNote"]
var otherMessageTypes = ["messageAnimation", "messageGame", "messageSticker"]

File diff suppressed because one or more lines are too long

View file

@ -609,7 +609,8 @@ Page {
Connections {
target: chatModel
onMessagesReceived: {
Debug.log("[ChatPage] Messages received, view has ", chatView.count, " messages, last known message index ", modelIndex, ", own messages were read before index ", lastReadSentIndex);
var proxyIndex = chatProxyModel.mapRowFromSource(modelIndex, -1);
Debug.log("[ChatPage] Messages received, view has ", chatView.count, " messages, last known message index ", proxyIndex, "("+modelIndex+"), own messages were read before index ", lastReadSentIndex);
if (totalCount === 0) {
if (chatPage.iterativeInitialization) {
chatPage.iterativeInitialization = false;
@ -623,9 +624,9 @@ Page {
}
chatView.lastReadSentIndex = lastReadSentIndex;
chatView.scrollToIndex(modelIndex);
chatView.scrollToIndex(proxyIndex);
chatPage.loading = false;
if (chatOverviewItem.visible && modelIndex >= (chatView.count - 10)) {
if (chatOverviewItem.visible && proxyIndex >= (chatView.count - 10)) {
chatView.inCooldown = true;
chatModel.triggerLoadMoreFuture();
}
@ -669,10 +670,13 @@ Page {
chatView.lastReadSentIndex = lastReadSentIndex;
}
onMessagesIncrementalUpdate: {
Debug.log("Incremental update received. View now has ", chatView.count, " messages, view is on index ", modelIndex, ", own messages were read before index ", lastReadSentIndex);
var proxyIndex = chatProxyModel.mapRowFromSource(modelIndex, -1);
Debug.log("Incremental update received. View now has ", chatView.count, " messages, view is on index ", proxyIndex, "("+modelIndex+"), own messages were read before index ", lastReadSentIndex);
chatView.lastReadSentIndex = lastReadSentIndex;
if (!chatPage.isInitialized) {
chatView.scrollToIndex(modelIndex);
if (proxyIndex > -1) {
chatView.scrollToIndex(proxyIndex);
}
}
if (chatView.height > chatView.contentHeight) {
Debug.log("[ChatPage] Chat content quite small...");
@ -748,14 +752,26 @@ Page {
onTriggered: {
Debug.log("scroll position changed, message index: ", lastQueuedIndex);
Debug.log("unread count: ", chatInformation.unread_count);
var messageToRead = chatModel.getMessage(lastQueuedIndex);
var modelIndex = chatProxyModel.mapRowToSource(lastQueuedIndex);
var messageToRead = chatModel.getMessage(modelIndex);
if (messageToRead['@type'] === "sponsoredMessage") {
Debug.log("sponsored message to read: ", messageToRead.id);
tdLibWrapper.viewMessage(chatInformation.id, messageToRead.message_id, false);
} else if (chatInformation.unread_count > 0 && lastQueuedIndex > -1) {
if (messageToRead) {
Debug.log("message to read: ", messageToRead.id);
if (messageToRead && messageToRead.id) {
tdLibWrapper.viewMessage(chatInformation.id, messageToRead.id, false);
var messageId = messageToRead.id;
var type = messageToRead.content["@type"];
if (messageToRead.media_album_id !== '0') {
var albumIds = chatModel.getMessageIdsForAlbum(messageToRead.media_album_id);
if (albumIds.length > 0) {
messageId = albumIds[albumIds.length - 1];
Debug.log("message to read last album message id: ", messageId);
}
}
if (messageId) {
tdLibWrapper.viewMessage(chatInformation.id, messageId, false);
}
}
lastQueuedIndex = -1
}
@ -781,7 +797,7 @@ Page {
NamedAction {
visible: messageOptionsDrawer.showCopyMessageToClipboardMenuItem
name: qsTr("Copy Message to Clipboard")
action: messageOptionsDrawer.myMessage.copyMessageToClipboard
action: messageOptionsDrawer.sourceItem.copyMessageToClipboard
},
NamedAction {
visible: messageOptionsDrawer.showForwardMessageMenuItem && messageOptionsDrawer.myMessage.can_be_forwarded
@ -1223,7 +1239,6 @@ Page {
readonly property int messageInReplyToHeight: Theme.fontSizeExtraSmall * 2.571428571 + Theme.paddingSmall;
readonly property int webPagePreviewHeight: ( (textColumnWidth * 2 / 3) + (6 * Theme.fontSizeExtraSmall) + ( 7 * Theme.paddingSmall) )
readonly property bool pageIsSelecting: chatPage.isSelecting
}
function handleScrollPositionChanged() {
@ -1246,6 +1261,9 @@ Page {
positionViewAtIndex(index, (mode === undefined) ? ListView.Contain : mode)
if(index === chatView.count - 1) {
manuallyScrolledToBottom = true;
if(!chatView.atYEnd) {
chatView.positionViewAtEnd();
}
}
}
}
@ -1278,7 +1296,13 @@ Page {
}
}
model: chatModel
BoolFilterModel {
id: chatProxyModel
sourceModel: chatModel
filterRoleName: "album_entry_filter"
filterValue: false
}
model: chatProxyModel
header: Component {
Loader {
active: !!chatPage.botInformation
@ -1311,7 +1335,8 @@ Page {
}
}
function getContentComponentHeight(contentType, content, parentWidth) {
function getContentComponentHeight(contentType, content, parentWidth, albumEntries) {
var unit;
switch(contentType) {
case "messageAnimatedEmoji":
return content.animated_emoji.sticker.height;
@ -1327,6 +1352,10 @@ Page {
case "messageVenue":
return parentWidth * 0.66666666; // 2 / 3;
case "messagePhoto":
if(albumEntries > 0) {
unit = (parentWidth * 0.66666666)
return (albumEntries % 2 !== 0 ? unit * 0.75 : 0) + unit * albumEntries * 0.25
}
var biggest = content.photo.sizes[content.photo.sizes.length - 1];
var aspectRatio = biggest.width/biggest.height;
return Math.max(Theme.itemSizeExtraSmall, Math.min(parentWidth * 0.66666666, parentWidth / aspectRatio));
@ -1335,6 +1364,10 @@ Page {
case "messageSticker":
return content.sticker.height;
case "messageVideo":
if(albumEntries > 0) {
unit = (parentWidth * 0.66666666)
return (albumEntries % 2 !== 0 ? unit * 0.75 : 0) + unit * albumEntries * 0.25
}
return Functions.getVideoHeight(parentWidth, content.video);
case "messageVideoNote":
return parentWidth
@ -1390,10 +1423,11 @@ Page {
chatId: chatModel.chatId
myMessage: model.display
messageId: model.message_id
messageAlbumMessageIds: model.album_message_ids
messageViewCount: model.view_count
reactions: model.reactions
chatReactions: availableReactions
messageIndex: model.index
messageIndex: chatProxyModel.mapRowToSource(model.index)
hasContentComponent: !!myMessage.content && chatView.delegateMessagesContent.indexOf(model.content_type) > -1
canReplyToMessage: chatPage.canSendMessages
onReplyToMessage: {
@ -1414,9 +1448,21 @@ Page {
id: messageListViewItemSimpleComponent
MessageListViewItemSimple {}
}
sourceComponent: chatView.simpleDelegateMessages.indexOf(model.content_type) > -1 ? messageListViewItemSimpleComponent : messageListViewItemComponent
Component {
id: messageListViewItemHiddenComponent
Item {
property var myMessage: display
property bool senderIsUser: myMessage.sender_id["@type"] === "messageSenderUser"
property var userInformation: senderIsUser ? tdLibWrapper.getUserInformation(myMessage.sender_id.user_id) : null
property bool isOwnMessage: senderIsUser && chatPage.myUserId === myMessage.sender_id.user_id
height: 1
}
VerticalScrollDecorator {}
}
sourceComponent: chatView.simpleDelegateMessages.indexOf(model.content_type) > -1
? messageListViewItemSimpleComponent
: messageListViewItemComponent
}
VerticalScrollDecorator { flickable: chatView }
ViewPlaceholder {
id: chatViewPlaceholder
@ -1877,7 +1923,7 @@ Page {
Image {
id: emojiPicture
source: "../js/emoji/" + modelData.file_name
source: "../js/emoji/" + modelData.file_name +".svg"
width: Theme.fontSizeLarge
height: Theme.fontSizeLarge
}

View file

@ -0,0 +1,109 @@
/*
Copyright (C) 2020 Sebastian J. Wolf and other contributors
This file is part of Fernschreiber.
Fernschreiber 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 3 of the License, or
(at your option) any later version.
Fernschreiber 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.
You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/
// jolla-gallery/pages/FlickableImageView.qml
/*
FullscreenContentPage
- PagedView (jolla-gallery/FlickableImageView)
- delegate: Loader
- SilicaFlickable (Silica.private/ZoomableFlickable) (Sailfish.Gallery/ImageViewer)
- PinchArea
- dragDetector(?)
- image
- Item (Sailfish.Gallery/GalleryOverlay)
*/
import QtQuick 2.6
import Sailfish.Silica 1.0
import WerkWolf.Fernschreiber 1.0
import "../components"
import "../components/messageContent/mediaAlbumPage"
import "../js/twemoji.js" as Emoji
import "../js/functions.js" as Functions
Page {
// id
id: page
// property declarations
property alias index: pagedView.currentIndex
property alias overlayActive: overlay.active
property alias delegate: pagedView.delegate
property var messages: [];
// message.content.caption.text
palette.colorScheme: Theme.LightOnDark
clip: status !== PageStatus.Active || pageStack.dragInProgress
navigationStyle: PageNavigation.Vertical
backgroundColor: 'black'
allowedOrientations: Orientation.All
// signal declarations
// JavaScript functions
// object (parent) properties
// large property bindings
// child objects
// states
// transitions
// content
PagedView {
id: pagedView
anchors.fill: parent
model: messages
delegate: Component {
Loader {
id: loader
asynchronous: true
visible: status == Loader.Ready
width: PagedView.contentWidth
height: PagedView.contentHeight
states: [
State {
when: model.modelData.content['@type'] === 'messagePhoto'
PropertyChanges {
target: loader
source: "../components/messageContent/mediaAlbumPage/PhotoComponent.qml"
}
},
State {
when: model.modelData.content['@type'] === 'messageVideo'
PropertyChanges {
target: loader
source: "../components/messageContent/mediaAlbumPage/VideoComponent.qml"
}
}
]
}
}
}
// overlay
FullscreenOverlay {
id: overlay
pageCount: messages.length
currentIndex: page.index
message: messages[currentIndex]
//
}
}

153
src/boolfiltermodel.cpp Normal file
View file

@ -0,0 +1,153 @@
/*
This file is part of Fernschreiber.
Fernschreiber 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 3 of the License, or
(at your option) any later version.
Fernschreiber 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.
You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/
#include "boolfiltermodel.h"
#define DEBUG_MODULE BoolFilterModel
#include "debuglog.h"
BoolFilterModel::BoolFilterModel(QObject *parent) : QSortFilterProxyModel(parent)
{
setDynamicSortFilter(true);
// setFilterCaseSensitivity(Qt::CaseInsensitive);
// setFilterFixedString(QString());
filterValue = true;
}
void BoolFilterModel::setSource(QObject *model)
{
setSourceModel(qobject_cast<QAbstractItemModel*>(model));
}
void BoolFilterModel::setSourceModel(QAbstractItemModel *model)
{
if (sourceModel() != model) {
LOG(model);
QSortFilterProxyModel::setSourceModel(model);
updateFilterRole();
emit sourceChanged();
}
}
QString BoolFilterModel::getFilterRoleName() const
{
return filterRoleName;
}
void BoolFilterModel::setFilterRoleName(QString role)
{
if (filterRoleName != role) {
filterRoleName = role;
LOG(role);
updateFilterRole();
emit filterRoleNameChanged();
}
}
bool BoolFilterModel::getFilterValue() const
{
return filterValue;
}
void BoolFilterModel::setFilterValue(bool value)
{
if(value != filterValue) {
filterValue = value;
invalidateFilter();
}
}
int BoolFilterModel::mapRowFromSource(int i, int fallbackDirection)
{
QModelIndex myIndex = mapFromSource(sourceModel()->index(i, 0));
LOG("mapping index" << i << "to source model:" << myIndex.row() << "valid?" << myIndex.isValid());
if(myIndex.isValid()) {
return myIndex.row();
}
if(fallbackDirection > 0) {
int max = sourceModel()->rowCount();
i += 1;
while (i < max) {
myIndex = mapFromSource(sourceModel()->index(i, 0));
LOG("fallback ++ " << i << "to source model:" << myIndex.row() << "valid?" << myIndex.isValid());
if(myIndex.isValid()) {
return myIndex.row();
}
i += 1;
}
} else if(fallbackDirection < 0) {
i -= 1;
while (i > -1) {
myIndex = mapFromSource(sourceModel()->index(i, 0));
LOG("fallback -- " << i << "to source model:" << myIndex.row() << "valid?" << myIndex.isValid());
if(myIndex.isValid()) {
return myIndex.row();
}
i -= 1;
}
}
return myIndex.row(); // may still be -1
}
int BoolFilterModel::mapRowToSource(int i)
{
QModelIndex sourceIndex = mapToSource(index(i, 0));
return sourceIndex.row();
}
bool BoolFilterModel::filterAcceptsRow(int sourceRow,
const QModelIndex &sourceParent) const
{
// sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(); //.toString().contains( /*string for column 0*/ ))
// LOG("Filter Role " << filterRole());
// QModelIndex index = this->sourceModel()->index(sourceRow,1,sourceParent);
// sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole()).toBool();
// LOG("Filter index DATA"<< sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole())); //<< index << index.isValid());
// LOG("Filter parent " << sourceParent << sourceParent.isValid());
// LOG("Filter Model Value" << sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole()).toBool());
// LOG("Filter Model filterValue" << filterValue);
// LOG("Filter Model result" << (sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole()).toBool() == filterValue));
// LOG("Filter Model MESSAGE" << sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data());
return sourceModel()->index(sourceRow, 0, sourceParent.child(sourceRow, 0)).data(filterRole()).toBool() == filterValue;
}
int BoolFilterModel::findRole(QAbstractItemModel *model, QString role)
{
if (model && !role.isEmpty()) {
const QByteArray roleName(role.toUtf8());
const QHash<int,QByteArray> roleMap(model->roleNames());
const QList<int> roles(roleMap.keys());
const int n = roles.count();
for (int i = 0; i < n; i++) {
const QByteArray name(roleMap.value(roles.at(i)));
if (name == roleName) {
LOG(role << roles.at(i));
return roles.at(i);
}
}
LOG("Unknown role" << role);
}
return -1;
}
void BoolFilterModel::updateFilterRole()
{
const int role = findRole(sourceModel(), filterRoleName);
setFilterRole((role >= 0) ? role : Qt::DisplayRole);
}

63
src/boolfiltermodel.h Normal file
View file

@ -0,0 +1,63 @@
/*
This file is part of Fernschreiber.
Fernschreiber 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 3 of the License, or
(at your option) any later version.
Fernschreiber 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.
You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef BOOLFILTERMODEL_H
#define BOOLFILTERMODEL_H
#include <QSortFilterProxyModel>
class BoolFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(QString filterRoleName READ getFilterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged)
Q_PROPERTY(bool filterValue READ getFilterValue WRITE setFilterValue NOTIFY filterValueChanged)
Q_PROPERTY(QObject* sourceModel READ sourceModel WRITE setSource NOTIFY sourceChanged)
public:
BoolFilterModel(QObject *parent = Q_NULLPTR);
void setSource(QObject* model);
void setSourceModel(QAbstractItemModel *model) Q_DECL_OVERRIDE;
QString getFilterRoleName() const;
void setFilterRoleName(QString role);
bool getFilterValue() const;
void setFilterValue(bool value);
Q_INVOKABLE int mapRowFromSource(int i, int fallbackDirection);
Q_INVOKABLE int mapRowToSource(int i);
signals:
void sourceChanged();
void filterRoleNameChanged();
void filterValueChanged();
private slots:
void updateFilterRole();
private:
static int findRole(QAbstractItemModel *model, QString role);
private:
QString filterRoleName;
bool filterValue;
protected:
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
};
#endif // BOOLFILTERMODEL_H

View file

@ -30,6 +30,7 @@ namespace {
const QString ID("id");
const QString CONTENT("content");
const QString CHAT_ID("chat_id");
const QString DATE("date");
const QString PHOTO("photo");
const QString SMALL("small");
const QString UNREAD_COUNT("unread_count");
@ -48,6 +49,7 @@ namespace {
// "view_count": 47
// }
const QString TYPE_MESSAGE_INTERACTION_INFO("messageInteractionInfo");
const QString MEDIA_ALBUM_ID("media_album_id");
const QString INTERACTION_INFO("interaction_info");
const QString VIEW_COUNT("view_count");
const QString REACTIONS("reactions");
@ -63,7 +65,9 @@ public:
RoleMessageId,
RoleMessageContentType,
RoleMessageViewCount,
RoleMessageReactions
RoleMessageReactions,
RoleMessageAlbumEntryFilter,
RoleMessageAlbumMessageIds,
};
enum RoleFlag {
@ -71,7 +75,9 @@ public:
RoleFlagMessageId = 0x02,
RoleFlagMessageContentType = 0x04,
RoleFlagMessageViewCount = 0x08,
RoleFlagMessageReactions = 0x16
RoleFlagMessageReactions = 0x16,
RoleFlagMessageAlbumEntryFilter = 0x32,
RoleFlagMessageAlbumMessageIds = 0x64
};
MessageData(const QVariantMap &data, qlonglong msgid);
@ -86,12 +92,16 @@ public:
uint updateViewCount(const QVariantMap &interactionInfo);
uint updateInteractionInfo(const QVariantMap &interactionInfo);
uint updateReactions(const QVariantMap &interactionInfo);
uint updateAlbumEntryFilter(const bool isAlbumChild);
uint updateAlbumEntryMessageIds(const QVariantList &newAlbumMessageIds);
QVector<int> diff(const MessageData *message) const;
QVector<int> setMessageData(const QVariantMap &data);
QVector<int> setContent(const QVariantMap &content);
QVector<int> setReplyMarkup(const QVariantMap &replyMarkup);
QVector<int> setInteractionInfo(const QVariantMap &interactionInfo);
QVector<int> setAlbumEntryFilter(bool isAlbumChild);
QVector<int> setAlbumEntryMessageIds(const QVariantList &newAlbumMessageIds);
int senderUserId() const;
qlonglong senderChatId() const;
@ -104,6 +114,8 @@ public:
QString messageContentType;
int viewCount;
QVariantList reactions;
bool albumEntryFilter;
QVariantList albumMessageIds;
};
ChatModel::MessageData::MessageData(const QVariantMap &data, qlonglong msgid) :
@ -112,7 +124,9 @@ ChatModel::MessageData::MessageData(const QVariantMap &data, qlonglong msgid) :
messageType(data.value(_TYPE).toString()),
messageContentType(data.value(CONTENT).toMap().value(_TYPE).toString()),
viewCount(data.value(INTERACTION_INFO).toMap().value(VIEW_COUNT).toInt()),
reactions(data.value(INTERACTION_INFO).toMap().value(REACTIONS).toList())
reactions(data.value(INTERACTION_INFO).toMap().value(REACTIONS).toList()),
albumEntryFilter(false),
albumMessageIds(QVariantList())
{
}
@ -134,6 +148,12 @@ QVector<int> ChatModel::MessageData::flagsToRoles(uint flags)
if (flags & RoleFlagMessageReactions) {
roles.append(RoleMessageReactions);
}
if (flags & RoleFlagMessageAlbumEntryFilter) {
roles.append(RoleMessageAlbumEntryFilter);
}
if (flags & RoleFlagMessageAlbumMessageIds) {
roles.append(RoleMessageAlbumMessageIds);
}
return roles;
}
@ -169,6 +189,12 @@ QVector<int> ChatModel::MessageData::diff(const MessageData *message) const
if (message->reactions != reactions) {
roles.append(RoleMessageReactions);
}
if (message->albumEntryFilter != albumEntryFilter) {
roles.append(RoleMessageAlbumEntryFilter);
}
if (message->albumMessageIds != albumMessageIds) {
roles.append(RoleMessageAlbumMessageIds);
}
}
return roles;
}
@ -237,6 +263,37 @@ uint ChatModel::MessageData::updateReactions(const QVariantMap &interactionInfo)
return (reactions == oldReactions) ? 0 : RoleFlagMessageReactions;
}
uint ChatModel::MessageData::updateAlbumEntryFilter(const bool isAlbumChild)
{
LOG("Updating album filter... for id " << messageId << " value:" << isAlbumChild << "previously" << albumEntryFilter);
const bool oldAlbumFiltered = albumEntryFilter;
albumEntryFilter = isAlbumChild;
return (isAlbumChild == oldAlbumFiltered) ? 0 : RoleFlagMessageAlbumEntryFilter;
}
QVector<int> ChatModel::MessageData::setAlbumEntryFilter(bool isAlbumChild)
{
LOG("setAlbumEntryFilter");
return flagsToRoles(updateAlbumEntryFilter(isAlbumChild));
}
uint ChatModel::MessageData::updateAlbumEntryMessageIds(const QVariantList &newAlbumMessageIds)
{
LOG("Updating albumMessageIds... id" << messageId);
LOG(" Updating albumMessageIds..." << newAlbumMessageIds << "previously" << albumMessageIds << "same?" << (newAlbumMessageIds == albumMessageIds));
const QVariantList oldAlbumMessageIds = albumMessageIds;
albumMessageIds = newAlbumMessageIds;
LOG(" Updating albumMessageIds... same again?" << (newAlbumMessageIds == oldAlbumMessageIds));
return (newAlbumMessageIds == oldAlbumMessageIds) ? 0 : RoleFlagMessageAlbumMessageIds;
}
QVector<int> ChatModel::MessageData::setAlbumEntryMessageIds(const QVariantList &newAlbumMessageIds)
{
return flagsToRoles(updateAlbumEntryMessageIds(newAlbumMessageIds));
}
QVector<int> ChatModel::MessageData::setInteractionInfo(const QVariantMap &info)
{
return flagsToRoles(updateInteractionInfo(info));
@ -295,6 +352,8 @@ QHash<int,QByteArray> ChatModel::roleNames() const
roles.insert(MessageData::RoleMessageContentType, "content_type");
roles.insert(MessageData::RoleMessageViewCount, "view_count");
roles.insert(MessageData::RoleMessageReactions, "reactions");
roles.insert(MessageData::RoleMessageAlbumEntryFilter, "album_entry_filter");
roles.insert(MessageData::RoleMessageAlbumMessageIds, "album_message_ids");
return roles;
}
@ -314,6 +373,8 @@ QVariant ChatModel::data(const QModelIndex &index, int role) const
case MessageData::RoleMessageContentType: return message->messageContentType;
case MessageData::RoleMessageViewCount: return message->viewCount;
case MessageData::RoleMessageReactions: return message->reactions;
case MessageData::RoleMessageAlbumEntryFilter: return message->albumEntryFilter;
case MessageData::RoleMessageAlbumMessageIds: return message->albumMessageIds;
}
}
return QVariant();
@ -331,6 +392,7 @@ void ChatModel::clear(bool contentOnly)
qDeleteAll(messages);
messages.clear();
messageIndexMap.clear();
albumMessageMap.clear();
endResetModel();
}
@ -356,6 +418,7 @@ void ChatModel::initialize(const QVariantMap &chatInformation)
this->chatId = chatId;
this->messages.clear();
this->messageIndexMap.clear();
this->albumMessageMap.clear();
this->searchQuery.clear();
endResetModel();
emit chatIdChanged();
@ -420,6 +483,36 @@ int ChatModel::getMessageIndex(qlonglong messageId)
return -1;
}
QVariantList ChatModel::getMessageIdsForAlbum(qlonglong albumId)
{
QVariantList foundMessages;
if(albumMessageMap.contains(albumId)) { // there should be only one in here
QHash< qlonglong, QVariantList >::iterator i = albumMessageMap.find(albumId);
return i.value();
}
return foundMessages;
}
QVariantList ChatModel::getMessagesForAlbum(qlonglong albumId, int startAt)
{
LOG("getMessagesForAlbumId" << albumId);
QVariantList messageIds = getMessageIdsForAlbum(albumId);
int count = messageIds.size();
if ( count == 0) {
return messageIds;
}
QVariantList foundMessages;
for (int messageNum = startAt; messageNum < count; ++messageNum) {
const int position = messageIndexMap.value(messageIds.at(messageNum).toLongLong(), -1);
if(position >= 0 && position < messages.size()) {
foundMessages.append(messages.at(position)->messageData);
} else {
LOG("Not found in messages: #"<< messageNum);
}
}
return foundMessages;
}
int ChatModel::getLastReadMessageIndex()
{
LOG("Obtaining last read message index");
@ -477,7 +570,8 @@ void ChatModel::handleMessagesReceived(const QVariantList &messages, int totalCo
const qlonglong messageId = messageData.value(ID).toLongLong();
if (messageId && messageData.value(CHAT_ID).toLongLong() == chatId && !messageIndexMap.contains(messageId)) {
LOG("New message will be added:" << messageId);
messagesToBeAdded.append(new MessageData(messageData, messageId));
MessageData* message = new MessageData(messageData, messageId);
messagesToBeAdded.append(message);
}
}
@ -485,6 +579,7 @@ void ChatModel::handleMessagesReceived(const QVariantList &messages, int totalCo
if (!messagesToBeAdded.isEmpty()) {
insertMessages(messagesToBeAdded);
setMessagesAlbum(messagesToBeAdded);
}
// First call only returns a few messages, we need to get a little more than that...
@ -540,6 +635,7 @@ void ChatModel::handleNewMessageReceived(qlonglong chatId, const QVariantMap &me
QList<MessageData*> messagesToBeAdded;
messagesToBeAdded.append(new MessageData(message, messageId));
insertMessages(messagesToBeAdded);
setMessagesAlbum(messagesToBeAdded);
emit newMessageReceived(message);
} else {
LOG("New message in this chat, but not relevant as less recent messages need to be loaded first!");
@ -591,6 +687,7 @@ void ChatModel::handleMessageSendSucceeded(qlonglong messageId, qlonglong oldMes
messages.replace(pos, newMessage);
messageIndexMap.remove(oldMessageId);
messageIndexMap.insert(messageId, pos);
// TODO when we support sending album messages, handle ID change in albumMessageMap
const QVector<int> changedRoles(newMessage->diff(oldMessage));
delete oldMessage;
LOG("Message was replaced at index" << pos);
@ -635,7 +732,8 @@ void ChatModel::handleMessageContentUpdated(qlonglong chatId, qlonglong messageI
LOG("We know the message that was updated" << messageId);
const int pos = messageIndexMap.value(messageId, -1);
if (pos >= 0) {
const QVector<int> changedRoles(messages.at(pos)->setContent(newContent));
MessageData* messageData = messages.at(pos);
const QVector<int> changedRoles(messageData->setContent(newContent));
LOG("Message was updated at index" << pos);
const QModelIndex messageIndex(index(pos));
emit dataChanged(messageIndex, messageIndex, changedRoles);
@ -664,7 +762,8 @@ void ChatModel::handleMessageEditedUpdated(qlonglong chatId, qlonglong messageId
LOG("We know the message that was updated" << messageId);
const int pos = messageIndexMap.value(messageId, -1);
if (pos >= 0) {
const QVector<int> changedRoles(messages.at(pos)->setReplyMarkup(replyMarkup));
MessageData* messageData = messages.at(pos);
const QVector<int> changedRoles(messageData->setReplyMarkup(replyMarkup));
LOG("Message was edited at index" << pos);
const QModelIndex messageIndex(index(pos));
emit dataChanged(messageIndex, messageIndex, changedRoles);
@ -709,18 +808,31 @@ void ChatModel::handleMessagesDeleted(qlonglong chatId, const QList<qlonglong> &
}
}
void ChatModel::removeRange(int firstDeleted, int lastDeleted)
{
if (firstDeleted >= 0 && firstDeleted <= lastDeleted) {
LOG("Removing range" << firstDeleted << "..." << lastDeleted << "| current messages size" << messages.size());
beginRemoveRows(QModelIndex(), firstDeleted, lastDeleted);
QList<qlonglong> rescanAlbumIds;
for (int i = firstDeleted; i <= lastDeleted; i++) {
MessageData *message = messages.at(i);
messageIndexMap.remove(message->messageId);
qlonglong albumId = message->messageData.value(MEDIA_ALBUM_ID).toLongLong();
if(albumId != 0 && albumMessageMap.contains(albumId)) {
rescanAlbumIds.append(albumId);
}
delete message;
}
messages.erase(messages.begin() + firstDeleted, messages.begin() + (lastDeleted + 1));
// rebuild following messageIndexMap
for(int i = firstDeleted; i < messages.size(); ++i) {
messageIndexMap.insert(messages.at(i)->messageId, i);
}
endRemoveRows();
updateAlbumMessages(rescanAlbumIds, true);
}
}
@ -757,7 +869,7 @@ void ChatModel::appendMessages(const QList<MessageData*> newMessages)
beginInsertRows(QModelIndex(), oldSize, oldSize + count - 1);
messages.append(newMessages);
for (int i = 0; i < count; i++) {
// Appens new indeces to the map
// Append new indices to the map
messageIndexMap.insert(newMessages.at(i)->messageId, oldSize + i);
}
endInsertRows();
@ -785,6 +897,90 @@ void ChatModel::prependMessages(const QList<MessageData*> newMessages)
endInsertRows();
}
void ChatModel::updateAlbumMessages(qlonglong albumId, bool checkDeleted)
{
if(albumMessageMap.contains(albumId)) {
const QVariantList empty;
QHash< qlonglong, QVariantList >::iterator album = albumMessageMap.find(albumId);
QVariantList messageIds = album.value();
std::sort(messageIds.begin(), messageIds.end());
int count;
// first: clear deleted messageIds:
if(checkDeleted) {
QVariantList::iterator it = messageIds.begin();
while (it != messageIds.end()) {
if (!messageIndexMap.contains(it->toLongLong())) {
it = messageIds.erase(it);
}
else {
++it;
}
}
}
// second: remaining ones still exist
count = messageIds.size();
if(count == 0) {
albumMessageMap.remove(albumId);
} else {
for (int i = 0; i < count; i++) {
const int position = messageIndexMap.value(messageIds.at(i).toLongLong(), -1);
if(position > -1) {
// set list for first entry, empty for all others
QVector<int> changedRolesFilter;
QVector<int> changedRolesIds;
QModelIndex messageIndex(index(position));
if(i == 0) {
changedRolesFilter = messages.at(position)->setAlbumEntryFilter(false);
changedRolesIds = messages.at(position)->setAlbumEntryMessageIds(messageIds);
} else {
changedRolesFilter = messages.at(position)->setAlbumEntryFilter(true);
changedRolesIds = messages.at(position)->setAlbumEntryMessageIds(empty);
}
emit dataChanged(messageIndex, messageIndex, changedRolesIds);
emit dataChanged(messageIndex, messageIndex, changedRolesFilter);
}
}
}
albumMessageMap.insert(albumId, messageIds);
}
}
void ChatModel::updateAlbumMessages(QList<qlonglong> albumIds, bool checkDeleted)
{
const int albumsCount = albumIds.size();
for (int i = 0; i < albumsCount; i++) {
updateAlbumMessages(albumIds.at(i), checkDeleted);
}
}
void ChatModel::setMessagesAlbum(const QList<MessageData *> newMessages)
{
const int count = newMessages.size();
for (int i = 0; i < count; i++) {
setMessagesAlbum(newMessages.at(i));
}
}
void ChatModel::setMessagesAlbum(MessageData *message)
{
qlonglong albumId = message->messageData.value(MEDIA_ALBUM_ID).toLongLong();
if (albumId > 0 && (message->messageContentType != "messagePhoto" || message->messageContentType != "messageVideo")) {
qlonglong messageId = message->messageId;
if(albumMessageMap.contains(albumId)) {
// find message id within album:
QHash< qlonglong, QVariantList >::iterator i = albumMessageMap.find(albumId);
if(!i.value().contains(messageId)) {
i.value().append(messageId);
}
} else { // new album id
albumMessageMap.insert(albumId, QVariantList() << messageId);
}
updateAlbumMessages(albumId, false);
}
}
QVariantMap ChatModel::enhanceMessage(const QVariantMap &message)
{
QVariantMap enhancedMessage = message;

View file

@ -44,6 +44,8 @@ public:
Q_INVOKABLE void triggerLoadMoreFuture();
Q_INVOKABLE QVariantMap getChatInformation();
Q_INVOKABLE QVariantMap getMessage(int index);
Q_INVOKABLE QVariantList getMessageIdsForAlbum(qlonglong albumId);
Q_INVOKABLE QVariantList getMessagesForAlbum(qlonglong albumId, int startAt);
Q_INVOKABLE int getLastReadMessageIndex();
Q_INVOKABLE void setSearchQuery(const QString newSearchQuery);
@ -85,6 +87,10 @@ private:
void insertMessages(const QList<MessageData*> newMessages);
void appendMessages(const QList<MessageData*> newMessages);
void prependMessages(const QList<MessageData*> newMessages);
void updateAlbumMessages(qlonglong albumId, bool checkDeleted);
void updateAlbumMessages(QList<qlonglong> albumIds, bool checkDeleted);
void setMessagesAlbum(const QList<MessageData*> newMessages);
void setMessagesAlbum(MessageData *message);
QVariantMap enhanceMessage(const QVariantMap &message);
int calculateLastKnownMessageId();
int calculateLastReadSentMessageId();
@ -95,6 +101,7 @@ private:
TDLibWrapper *tdLibWrapper;
QList<MessageData*> messages;
QHash<qlonglong,int> messageIndexMap;
QHash<qlonglong, QVariantList> albumMessageMap;
QVariantMap chatInformation;
qlonglong chatId;
bool inReload;

View file

@ -51,6 +51,7 @@
#include "processlauncher.h"
#include "stickermanager.h"
#include "textfiltermodel.h"
#include "boolfiltermodel.h"
#include "tgsplugin.h"
#include "fernschreiberutils.h"
#include "knownusersmodel.h"
@ -130,6 +131,7 @@ int main(int argc, char *argv[])
qmlRegisterType<TDLibFile>(uri, 1, 0, "TDLibFile");
qmlRegisterType<NamedAction>(uri, 1, 0, "NamedAction");
qmlRegisterType<TextFilterModel>(uri, 1, 0, "TextFilterModel");
qmlRegisterType<BoolFilterModel>(uri, 1, 0, "BoolFilterModel");
qmlRegisterType<ChatPermissionFilterModel>(uri, 1, 0, "ChatPermissionFilterModel");
qmlRegisterSingletonType<DebugLogJS>(uri, 1, 0, "DebugLog", DebugLogJS::createSingleton);

View file

@ -903,6 +903,17 @@
<translation>hat eine Videonachricht geschickt</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>%Ln Nachricht weiterleiten</numerusform>
<numerusform>%Ln Nachrichten weiterleiten</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -905,6 +905,17 @@ messages</numerusform>
<translation>sent a video note</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Forward %Ln message</numerusform>
<numerusform>Forward %Ln messages</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -903,6 +903,17 @@
<translation>envió nota de video</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Reenviar %Ln mensaje</numerusform>
<numerusform>Reenviar %Ln mensajes</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -904,6 +904,17 @@
<translation>lähetti videoviestin</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Välitä %Ln viesti</numerusform>
<numerusform>Välitä %Ln viestiä</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -903,6 +903,17 @@
<translation>a envoyé une note vidéo</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Transférer %Ln message</numerusform>
<numerusform>Transférer %Ln messages</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -889,6 +889,16 @@
<translation type="unfinished"></translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation type="unfinished">
<numerusform></numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -903,6 +903,17 @@
<translation>ha inviato un videomessaggio</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Inoltra %Ln messaggio</numerusform>
<numerusform>Inoltra %Ln messaggi</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -917,6 +917,18 @@
<translation>wysłał notatkę video</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Przekaż %Ln wiadomość</numerusform>
<numerusform>Przekaż %Ln wiadomości</numerusform>
<numerusform>Przekaż %Ln wiadomości</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -920,6 +920,18 @@
<translation>отправил(а) видео заметку</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Перенаправить %Ln сообщение</numerusform>
<numerusform>Перенаправить %Ln сообщения</numerusform>
<numerusform>Перенаправить %Ln сообщений</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -917,6 +917,18 @@
<translation>poslal video-poznámku</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Postúpená %Ln správa</numerusform>
<numerusform>Postúpené %Ln správy</numerusform>
<numerusform>Postúpených %Ln správ</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -903,6 +903,17 @@
<translation>skickade ett videomeddelande</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Vidarebefordra %Ln meddelande</numerusform>
<numerusform>Vidarebefordra %Ln meddelanden</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -890,6 +890,16 @@
<translation></translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation type="unfinished">
<numerusform> %Ln </numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>

View file

@ -903,6 +903,17 @@
<translation type="unfinished">sent a video note</translation>
</message>
</context>
<context>
<name>FullscreenOverlay</name>
<message numerus="yes">
<source>Forward %Ln messages</source>
<comment>dialog header</comment>
<translation>
<numerusform>Forward %Ln message</numerusform>
<numerusform>Forward %Ln messages</numerusform>
</translation>
</message>
</context>
<context>
<name>ImagePage</name>
<message>