feat: introduce new chat input component

Closes #757
This commit is contained in:
Pascal Precht 2020-09-29 11:06:57 +02:00 committed by Pascal Precht
parent dc14bbe9ec
commit 12a7d7c067
27 changed files with 954 additions and 2270 deletions

View File

@ -2,9 +2,11 @@ import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../../../shared"
import "../../../shared/status"
import "../../../imports"
import "./components"
import "./ChatColumn"
import "./ChatColumn/ChatComponents"
import "./data"
StackLayout {
@ -26,7 +28,6 @@ StackLayout {
Component.onCompleted: {
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
}
Layout.fillHeight: true
Layout.fillWidth: true
@ -37,21 +38,33 @@ StackLayout {
function showReplyArea() {
isReply = true;
isImage = false;
replyAreaContainer.setup()
let replyMessageIndex = chatsModel.messageList.getMessageIndex(SelectedMessage.messageId);
if (replyMessageIndex === -1) return;
let userName = chatsModel.messageList.getMessageData(replyMessageIndex, "userName")
let message = chatsModel.messageList.getMessageData(replyMessageIndex, "message")
let identicon = chatsModel.messageList.getMessageData(replyMessageIndex, "identicon")
chatInput.showReplyArea(userName, message, identicon)
}
function showImageArea(imagePath) {
isImage = true;
isReply = false;
sendImageArea.image = imagePath[0];
function requestAddressForTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
amount = walletModel.eth2Wei(amount.toString(), tokenDecimals)
chatsModel.requestAddressForTransaction(chatsModel.activeChannel.id,
address,
amount,
tokenAddress)
chatCommandModal.close()
}
function requestTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
amount = walletModel.eth2Wei(amount.toString(), tokenDecimals)
chatsModel.requestTransaction(chatsModel.activeChannel.id,
address,
amount,
tokenAddress)
chatCommandModal.close()
}
function hideExtendedArea() {
isImage = false;
isReply = false;
replyAreaContainer.setup();
sendImageArea.image = "";
}
ColumnLayout {
spacing: 0
@ -164,14 +177,12 @@ StackLayout {
Rectangle {
id: inputArea
color: Style.current.background
border.width: 1
border.color: Style.current.border
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
Layout.fillWidth: true
Layout.preferredWidth: parent.width
height: (!isExtendedInput ? 70 : 140) * chatInput.extraHeightFactor
height: chatInput.height
Layout.preferredHeight: height
color: "transparent"
SuggestionBox {
id: suggestionsBox
@ -209,16 +220,6 @@ StackLayout {
}
}
ReplyArea {
id: replyAreaContainer
visible: isReply
}
SendImageArea {
id: sendImageArea
visible: isImage
}
Loader {
active: chatsModel.loadingMessages
sourceComponent: loadingIndicator
@ -247,31 +248,56 @@ StackLayout {
}
}
ChatInput {
StatusChatInput {
id: chatInput
height: 40
anchors.top: {
if(!isExtendedInput){
return inputArea.top;
}
if(isReply){
return replyAreaContainer.bottom;
}
if(isImage){
return sendImageArea.bottom;
}
}
anchors.topMargin: 4
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
recentStickers: chatsModel.recentStickers
stickerPackList: chatsModel.stickerPacks
chatType: chatsModel.activeChannel.chatType
onSendTransactionCommandButtonClicked: {
chatCommandModal.sendChatCommand = chatColumnLayout.requestAddressForTransaction
chatCommandModal.isRequested = false
//% "Send"
chatCommandModal.commandTitle = qsTrId("command-button-send")
chatCommandModal.title = chatCommandModal.commandTitle
//% "Request Address"
chatCommandModal.finalButtonLabel = qsTrId("request-address")
chatCommandModal.selectedRecipient = {
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
identicon: chatsModel.activeChannel.identicon,
name: chatsModel.activeChannel.name,
type: RecipientSelector.Type.Contact
}
chatCommandModal.open()
}
onReceiveTransactionCommandButtonClicked: {
chatCommandModal.sendChatCommand = root.requestTransaction
chatCommandModal.isRequested = true
//% "Request"
chatCommandModal.commandTitle = qsTrId("wallet-request")
chatCommandModal.title = chatCommandModal.commandTitle
//% "Request"
chatCommandModal.finalButtonLabel = qsTrId("wallet-request")
chatCommandModal.selectedRecipient = {
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
identicon: chatsModel.activeChannel.identicon,
name: chatsModel.activeChannel.name,
type: RecipientSelector.Type.Contact
}
chatCommandModal.open()
}
onStickerSelected: {
chatsModel.sendSticker(hashId, packId)
}
}
}
}
EmptyChat {}
ChatCommandModal {
id: chatCommandModal
}
}
/*##^##

View File

@ -1,127 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtGraphicalEffects 1.13
import "../../../../imports"
import "../../../../shared"
import "../components"
import "./ChatComponents"
Row {
property int iconPadding: 6
property var addToChat: function () {}
property var onSend: function () {}
id: chatButtonsContainer
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
spacing: 0
// ChildrenRect doesn't work with the width being able to change
width: chatSendBtn.width + emojiIconButton.width +
stickerIconButton.width + imageIconButton.width + commandIconButton.width
Button {
id: chatSendBtn
visible: txtData.length > 0 || chatColumn.isImage
width: this.visible ? 30 : 0
height: this.width
text: ""
anchors.verticalCenter: parent.verticalCenter
onClicked: {
onSend();
}
background: Rectangle {
color: parent.enabled ? Style.current.blue : Style.current.grey
radius: 50
}
SVGImage {
source: "../../../img/arrowUp.svg"
width: 13
height: 17
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
}
ChatInputButton {
id: emojiIconButton
source: "../../../img/emojiBtn.svg"
opened: emojiPopup.opened
close: function () {
emojiPopup.close()
}
open: function () {
emojiPopup.open()
}
}
ChatInputButton {
id: stickerIconButton
visible: !chatColumn.isExtendedInput && txtData.length == 0
source: "../../../img/stickers_icon.svg"
opened: stickersPopup.opened
close: function () {
stickersPopup.close()
}
open: function () {
stickersPopup.open()
}
}
ChatInputButton {
id: imageIconButton
visible: !chatColumn.isExtendedInput && (chatsModel.activeChannel.chatType === Constants.chatTypePrivateGroupChat || chatsModel.activeChannel.chatType === Constants.chatTypeOneToOne)
source: "../../../img/images_icon.svg"
opened: imageDialog.visible
close: function () {
imageDialog.close()
}
open: function () {
imageDialog.open()
}
}
ChatInputButton {
id: commandIconButton
visible: !chatColumn.isExtendedInput && chatsModel.activeChannel.chatType === Constants.chatTypeOneToOne
source: "../../../img/chat-commands.svg"
opened: chatCommandsPopup.opened
close: function () {
chatCommandsPopup.close()
}
open: function () {
chatCommandsPopup.open()
}
}
StickersPopup {
id: stickersPopup
width: 360
height: 440
x: parent.width - width - Style.current.halfPadding
y: parent.height - sendBtns.height - height - Style.current.halfPadding
recentStickers: chatsModel.recentStickers
stickerPackList: chatsModel.stickerPacks
}
EmojiPopup {
id: emojiPopup
width: 360
height: 440
x: parent.width - width - Style.current.halfPadding
y: parent.height - sendBtns.height - height - Style.current.halfPadding
addToChat: chatButtonsContainer.addToChat
}
ChatCommandsPopup {
id: chatCommandsPopup
x: parent.width - width - Style.current.halfPadding
y: parent.height - sendBtns.height - height - Style.current.halfPadding
}
}
/*##^##
Designer {
D{i:0;formeditorColor:"#ffffff";formeditorZoom:1.75}
}
##^##*/

View File

@ -1,453 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtMultimedia 5.13
import QtQuick.Dialogs 1.3
import "../components"
import "../../../../shared"
import "../../../../imports"
import "../components/emojiList.js" as EmojiJSON
Rectangle {
id: root
property alias textInput: txtData
border.width: 0
height: 52
color: Style.current.transparent
visible: chatsModel.activeChannel.chatType !== Constants.chatTypePrivateGroupChat || chatsModel.activeChannel.isMember
property bool emojiEvent: false;
property bool paste: false;
property bool isColonPressed: false;
property int extraHeightFactor: calculateExtraHeightFactor()
property int messageLimit: 2000
property int messageLimitVisible: 200
Audio {
id: sendMessageSound
source: "../../../../sounds/send_message.wav"
volume: appSettings.volume
}
function calculateExtraHeightFactor() {
const factor = (txtData.length / 500) + 1;
return (factor > 5) ? 5 : factor;
}
function insertInTextInput(start, text) {
// Repace new lines with entities because `insert` gets rid of them
txtData.insert(start, text.replace(/\n/g, "<br/>"));
}
function interpretMessage(msg) {
if (msg === "/shrug") {
return "¯\\\\\\_(ツ)\\_/¯"
}
if (msg === "/tableflip") {
return "(╯°□°)╯︵ ┻━┻"
}
return msg
}
function sendMsg(event){
if(chatColumn.isImage){
const error = chatsModel.sendImage(sendImageArea.image);
if (error) {
toastMessage.title = error
toastMessage.source = "../../../img/block-icon.svg"
toastMessage.iconColor = Style.current.danger
toastMessage.linkText = ""
toastMessage.open()
}
}
var msg = chatsModel.plainText(Emoji.deparse(txtData.text).trim()).trim()
if(msg.length > 0){
msg = interpretMessage(msg)
chatsModel.sendMessage(msg, chatColumn.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType);
txtData.text = "";
if(event) event.accepted = true
sendMessageSound.stop()
Qt.callLater(sendMessageSound.play);
}
chatColumn.hideExtendedArea();
}
function onEnter(event){
if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
if (emojiSuggestions.visible) {
emojiSuggestions.addEmoji();
event.accepted = true;
return
}
if (txtData.length < messageLimit) {
sendMsg(event);
return;
}
if(event) event.accepted = true
messageTooLongDialog.open()
}
if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier)) {
paste = true;
}
if (event.key === Qt.Key_Down) {
return emojiList.incrementCurrentIndex()
}
if (event.key === Qt.Key_Up) {
return emojiList.decrementCurrentIndex()
}
isColonPressed = (event.key === Qt.Key_Colon) && (event.modifiers & Qt.ShiftModifier);
}
function onRelease(event) {
// the text doesn't get registered to the textarea fast enough
// we can only get it in the `released` event
if (paste) {
paste = false;
interrogateMessage();
}
emojiEvent = emojiHandler(event);
if (!emojiEvent) {
emojiSuggestions.close()
}
}
function onMouseClicked() {
emojiEvent = emojiHandler({key: null});
}
function interrogateMessage() {
const text = chatsModel.plainText(Emoji.deparse(txtData.text));
var words = text.split(' ');
for (var i = 0; i < words.length; i++) {
var transform = true;
if (words[i].charAt(0) === ':') {
for (var j = 0; j < words[i].length; j++) {
if (Utils.isSpace(words[i].charAt(j)) === true || Utils.isPunct(words[i].charAt(j)) === true) {
transform = false;
}
}
if (transform) {
const codePoint = Emoji.getEmojiUnicode(words[i]);
words[i] = words[i].replace(words[i], (codePoint !== undefined) ? Emoji.fromCodePoint(codePoint) : words[i]);
}
}
}
txtData.remove(0, txtData.length);
insertInTextInput(0, Emoji.parse(words.join('&nbsp;'), '26x26'));
}
function replaceWithEmoji(message, shortname, codePoint) {
const encodedCodePoint = Emoji.getEmojiCodepoint(codePoint)
const newMessage = message.data
.replace(shortname, encodedCodePoint)
.replace(/ /g, "&nbsp;");
txtData.remove(0, txtData.cursorPosition);
insertInTextInput(0, Emoji.parse(newMessage, '26x26'));
emojiSuggestions.close()
emojiEvent = false
}
function emojiHandler(event) {
let message = extrapolateCursorPosition();
pollEmojiEvent(message);
// state machine to handle different forms of the emoji event state
if (!emojiEvent && isColonPressed) {
return (message.data.length <= 1 || Utils.isSpace(message.data.charAt(message.cursor - 1))) ? true : false;
} else if (emojiEvent && isColonPressed) {
const index = message.data.lastIndexOf(':', message.cursor - 2);
if (index >= 0 && message.cursor > 0) {
const shortname = message.data.substr(index, message.cursor);
const codePoint = Emoji.getEmojiUnicode(shortname);
if (codePoint !== undefined) {
replaceWithEmoji(message, shortname, codePoint);
}
return false;
}
return true;
} else if (emojiEvent && isKeyValid(event.key) && !isColonPressed) {
// popup
const index2 = message.data.lastIndexOf(':', message.cursor - 1);
if (index2 >= 0 && message.cursor > 0) {
const emojiPart = message.data.substr(index2, message.cursor);
if (emojiPart.length > 2) {
const emojis = EmojiJSON.emoji_json.filter(function (emoji) {
return emoji.name.includes(emojiPart) ||
emoji.shortname.includes(emojiPart) ||
emoji.aliases.some(a => a.includes(emojiPart))
})
emojiSuggestions.openPopup(emojis, emojiPart)
}
return true;
}
} else if (emojiEvent && !isKeyValid(event.key) && !isColonPressed) {
return false;
}
return false;
}
// since emoji length is not 1 we need to match that position that TextArea returns
// to the actual position in the string.
function extrapolateCursorPosition() {
// we need only the message part to be html
const text = chatsModel.plainText(Emoji.deparse(txtData.text));
const plainText = Emoji.parse(text, '26x26');
var bracketEvent = false;
var length = 0;
for (var i = 0; i < plainText.length;) {
if (length >= txtData.cursorPosition) break;
if (!bracketEvent && plainText.charAt(i) !== '<') {
i++;
length++;
} else if (!bracketEvent && plainText.charAt(i) === '<') {
bracketEvent = true;
i++;
} else if (bracketEvent && plainText.charAt(i) !== '>') {
i++;
} else if (bracketEvent && plainText.charAt(i) === '>') {
bracketEvent = false;
i++;
length++;
}
}
let textBeforeCursor = Emoji.deparseFromParse(plainText.substr(0, i));
return {
cursor: countEmojiLengths(plainText.substr(0, i)) + txtData.cursorPosition,
data: Emoji.deparseFromParse(textBeforeCursor),
};
}
function countEmojiLengths(value) {
const match = Emoji.getEmojis(value);
var length = 0;
if (match && match.length > 0) {
for (var i = 0; i < match.length; i++) {
length += Emoji.deparseFromParse(match[i]).length;
}
length = length - match.length;
}
return length;
}
// check if user has placed cursor near valid emoji colon token
function pollEmojiEvent(message) {
const index = message.data.lastIndexOf(':', message.cursor);
if (index >= 0) {
emojiEvent = validSubstr(message.data.substr(index, message.cursor - index));
}
}
function validSubstr(substr) {
for(var i = 0; i < substr.length; i++) {
var c = substr.charAt(i);
if (c !== '_' && (Utils.isSpace(c) === true || Utils.isPunct(c) === true)) {
return false;
}
}
return true;
}
function isKeyValid(key) {
if (key !== Qt.Key_Underscore &&
(key === Qt.Key_Space || key === Qt.Key_Tab ||
(key >= Qt.Key_Exclam && key <= Qt.Key_Slash) ||
(key >= Qt.Key_Semicolon && key <= Qt.Key_Question) ||
(key >= Qt.Key_BracketLeft && key <= Qt.Key_hyphen)))
return false;
return true;
}
FileDialog {
id: imageDialog
//% "Please choose an image"
title: qsTrId("please-choose-an-image")
folder: shortcuts.pictures
nameFilters: [
//% "Image files (*.jpg *.jpeg *.png)"
qsTrId("image-files----jpg---jpeg---png-")
]
onAccepted: {
chatColumn.showImageArea(imageDialog.fileUrls);
txtData.forceActiveFocus();
}
onRejected: {
chatColumn.hideExtendedArea();
}
}
Popup {
property var emojis
property string shortname
function openPopup(emojisParam, shortnameParam) {
emojis = emojisParam
shortname = shortnameParam
emojiSuggestions.open()
}
function addEmoji(index) {
if (index === undefined) {
index = emojiList.currentIndex
}
const message = extrapolateCursorPosition();
const unicode = emojiSuggestions.emojis[index].unicode_alternates || emojiSuggestions.emojis[index].unicode
replaceWithEmoji(message, emojiSuggestions.shortname, unicode)
}
id: emojiSuggestions
width: parent.width - Style.current.padding * 2
height: Math.min(400, emojiList.contentHeight + Style.current.smallPadding * 2)
x : Style.current.padding / 2
y: -height - Style.current.smallPadding
background: Rectangle {
visible: !!emojiSuggestions.emojis && emojiSuggestions.emojis.length > 0
color: Style.current.secondaryBackground
border.width: 1
border.color: Style.current.borderSecondary
radius: 8
}
ListView {
id: emojiList
model: emojiSuggestions.emojis || []
keyNavigationEnabled: true
anchors.fill: parent
clip: true
delegate: Rectangle {
id: rectangle
color: emojiList.currentIndex === index ? Style.current.inputBorderFocus : Style.current.transparent
border.width: 0
width: parent.width
height: 42
radius: 8
SVGImage {
id: emojiImage
source: `../../../../imports/twemoji/26x26/${modelData.unicode}.png`
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
}
StyledText {
text: modelData.shortname
color: emojiList.currentIndex === index ? Style.current.currentUserTextColor : Style.current.textColor
anchors.verticalCenter: parent.verticalCenter
anchors.left: emojiImage.right
anchors.leftMargin: Style.current.smallPadding
font.pixelSize: 15
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onEntered: {
emojiList.currentIndex = index
}
onClicked: {
emojiSuggestions.addEmoji(index)
}
}
}
}
}
ScrollView {
id: scrollView
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.top: parent.top
anchors.right: sendBtns.left
anchors.rightMargin: 0
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
topPadding: Style.current.padding
StyledTArea {
textFormat: Text.RichText
id: txtData
text: ""
selectByMouse: true
wrapMode: TextArea.Wrap
font.pixelSize: 15
//% "Type a message..."
placeholderText: qsTrId("type-a-message")
Keys.onPressed: onEnter(event)
Keys.onReleased: onRelease(event) // gives much more up to date cursorPosition
background: Rectangle {
color: Style.current.transparent
}
TapHandler {
id: mousearea
onTapped: onMouseClicked()
}
}
}
StyledText {
id: messageLengthLimit
property int remainingChars: messageLimit - txtData.length
text: remainingChars.toString()
visible: remainingChars <= root.messageLimitVisible
color: (remainingChars <= 0) ? Style.current.danger : Style.current.textColor
anchors.right: parent.right
anchors.bottom: sendBtns.top
anchors.rightMargin: Style.current.padding
leftPadding: Style.current.halfPadding
rightPadding: Style.current.halfPadding
}
ChatButtons {
id: sendBtns
height: 36
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.bottom: parent.bottom
addToChat: function (text, atCursor) {
insertInTextInput(atCursor ? txtData.cursorPosition :txtData.length, text)
}
onSend: function(){
if (txtData.length < messageLimit) {
sendMsg(false);
return;
}
messageTooLongDialog.open()
}
}
MessageDialog {
id: messageTooLongDialog
//% "Your message is too long."
title: qsTrId("your-message-is-too-long.")
icon: StandardIcon.Critical
//% "Please make your message shorter. We have set the limit to 2000 characters to be courteous of others."
text: qsTrId("please-make-your-message-shorter.-we-have-set-the-limit-to-2000-characters-to-be-courteous-of-others.")
standardButtons: StandardButton.Ok
}
}
/*##^##
Designer {
D{i:0;formeditorColor:"#ffffff"}
}
##^##*/

View File

@ -1,93 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
import "./"
Rectangle {
property string userName: "Joseph Joestar"
property string message: "Your next line is: this is a Jojo reference"
property string identicon: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII="
id: replyArea
height: 70
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: "#00000000"
function setup(){
let replyMessageIndex = chatsModel.messageList.getMessageIndex(SelectedMessage.messageId);
if (replyMessageIndex == -1) return;
userName = chatsModel.messageList.getMessageData(replyMessageIndex, "userName")
message = chatsModel.messageList.getMessageData(replyMessageIndex, "message")
identicon = chatsModel.messageList.getMessageData(replyMessageIndex, "identicon")
}
function reset(){
userName = "";
message= "";
identicon = "";
}
StatusIconButton {
id: closeButton
type: "secondary"
icon.name: "close"
anchors.top: parent.top
anchors.topMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
anchors.right: parent.right
onClicked: {
chatColumn.hideExtendedArea()
}
}
Image {
id: chatImage
width: 36
height: 36
anchors.topMargin: 20
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.top: parent.top
fillMode: Image.PreserveAspectFit
source: identicon
mipmap: true
smooth: false
antialiasing: true
}
StyledTextEdit {
id: replyToUsername
text: userName
font.bold: true
font.pixelSize: 14
anchors.leftMargin: 20
anchors.top: parent.top
anchors.topMargin: 0
anchors.left: chatImage.right
readOnly: true
wrapMode: Text.WordWrap
selectByMouse: true
}
StyledText {
id: replyText
text: Emoji.parse(message, "26x26")
anchors.left: replyToUsername.left
anchors.top: replyToUsername.bottom
anchors.topMargin: 8
anchors.right: parent.right
anchors.rightMargin: Style.current.padding * 2 + closeButton.width
elide: Text.ElideRight
wrapMode: Text.Wrap
font.pixelSize: 15
textFormat: Text.RichText
}
}

View File

@ -1,69 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import "../../../../imports"
import "../../../../shared"
import "./"
Rectangle {
id: sendImageArea
height: 70
property string image: ""
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
color: "#00000000"
Rectangle {
id: closeButton
height: 32
width: 32
anchors.top: parent.top
anchors.topMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
anchors.right: parent.right
radius: 8
SVGImage {
id: closeModalImg
source: "../../../../shared/img/close.svg"
width: 11
height: 11
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
}
MouseArea {
id: closeImageArea
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onExited: {
closeButton.color = Style.current.white
}
onEntered: {
closeButton.color = Style.current.grey
}
onClicked: {
chatColumn.hideExtendedArea();
}
}
}
Image {
id: chatImage
width: 36
height: 36
anchors.topMargin: 20
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.top: parent.top
fillMode: Image.PreserveAspectFit
source: image
mipmap: true
smooth: false
antialiasing: true
}
}

View File

@ -1,55 +0,0 @@
import QtQuick 2.13
import QtGraphicalEffects 1.0
import "../../../../imports"
import "../../../../shared"
Rectangle {
property bool active: false
property var changeCategory: function () {}
property url source: "../../../img/emojiCategories/recent.svg"
id: categoryButton
width: 40
height: 40
SVGImage {
width: 20
height: 20
fillMode: Image.PreserveAspectFit
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
source: categoryButton.source
ColorOverlay {
anchors.fill: parent
source: parent
color: categoryButton.active ? Style.current.blue : Style.current.transparent
}
Rectangle {
visible: categoryButton.active
width: parent.width
height: 2
radius: 1
color: Style.current.blue
anchors.bottom: parent.bottom
anchors.bottomMargin: -Style.current.smallPadding
}
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: function () {
categoryButton.changeCategory()
}
}
}
/*##^##
Designer {
D{i:0;formeditorColor:"#ffffff";height:440;width:360}
}
##^##*/

View File

@ -1,228 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import "../../../../imports"
import "../../../../shared"
import "../ChatColumn/samples"
import "./emojiList.js" as EmojiJSON
Popup {
property var addToChat: function () {}
property var categories: []
property string searchString: searchBox.text
id: popup
modal: false
width: 360
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
background: Rectangle {
radius: Style.current.radius
color: Style.current.background
border.color: Style.current.border
layer.enabled: true
layer.effect: DropShadow{
verticalOffset: 3
radius: 8
samples: 15
fast: true
cached: true
color: "#22000000"
}
}
function addEmoji(emoji) {
const MAX_EMOJI_NUMBER = 36
const extenstionIndex = emoji.filename.lastIndexOf('.');
let iconCodePoint = emoji.filename
if (extenstionIndex > -1) {
iconCodePoint = iconCodePoint.substring(0, extenstionIndex)
}
const encodedIcon = Emoji.getEmojiCodepoint(iconCodePoint)
// Add at the start of the list
let recentEmojis = appSettings.recentEmojis
recentEmojis.unshift(emoji)
// Remove duplicates
recentEmojis = recentEmojis.filter(function (e, index) {
return !recentEmojis.some(function (e2, index2) {
return index2 < index && e2.filename === e.filename
})
})
if (recentEmojis.length > MAX_EMOJI_NUMBER) {
// remove last one
recentEmojis.splice(MAX_EMOJI_NUMBER - 1)
}
emojiSectionsRepeater.itemAt(0).allEmojis = recentEmojis
appSettings.recentEmojis = recentEmojis
popup.addToChat(Emoji.parse(encodedIcon, "26x26") + ' ', true) // Adding a space because otherwise, some emojis would fuse since emoji is just a string
popup.close()
chatInput.textInput.forceActiveFocus()
}
Component.onCompleted: {
var categoryNames = {"recent": 0}
var newCategories = [[]]
EmojiJSON.emoji_json.forEach(function (emoji) {
if (!categoryNames[emoji.category] && categoryNames[emoji.category] !== 0) {
categoryNames[emoji.category] = newCategories.length
newCategories.push([])
}
newCategories[categoryNames[emoji.category]].push(Object.assign({}, emoji, {filename: emoji.unicode + '.png'}))
})
if (newCategories[categoryNames.recent].length === 0) {
newCategories[categoryNames.recent].push({
category: "recent",
empty: true
})
}
categories = newCategories
}
Connections {
target: applicationWindow
onSettingsLoaded: {
// Add recent
if (!appSettings.recentEmojis || !appSettings.recentEmojis.length) {
return
}
emojiSectionsRepeater.itemAt(0).allEmojis = appSettings.recentEmojis
}
}
onOpened: {
searchBox.forceActiveFocus(Qt.MouseFocusReason)
}
contentItem: ColumnLayout {
anchors.fill: parent
spacing: 0
Item {
property int headerMargin: 8
id: emojiHeader
Layout.fillWidth: true
height: searchBox.height + emojiHeader.headerMargin
SearchBox {
id: searchBox
anchors.right: skinToneEmoji.left
anchors.rightMargin: emojiHeader.headerMargin
anchors.top: parent.top
anchors.topMargin: emojiHeader.headerMargin
anchors.left: parent.left
anchors.leftMargin: emojiHeader.headerMargin
}
SVGImage {
id: skinToneEmoji
width: 22
height: 22
anchors.verticalCenter: searchBox.verticalCenter
anchors.right: parent.right
anchors.rightMargin: emojiHeader.headerMargin
source: "../../../../imports/twemoji/26x26/1f590.png"
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onClicked: function () {
console.log('Change skin tone')
}
}
}
}
ScrollView {
property ScrollBar vScrollBar: ScrollBar.vertical
property var categrorySectionHeightRatios: []
property int activeCategory: 0
id: scrollView
topPadding: Style.current.smallPadding
leftPadding: Style.current.smallPadding
rightPadding: Style.current.smallPadding / 2
Layout.fillWidth: true
Layout.rightMargin: Style.current.smallPadding / 2
Layout.topMargin: Style.current.smallPadding
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.preferredHeight: 400 - Style.current.smallPadding - emojiHeader.height
clip: true
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
ScrollBar.vertical.onPositionChanged: function () {
if (vScrollBar.position < categrorySectionHeightRatios[scrollView.activeCategory - 1]) {
scrollView.activeCategory--
} else if (vScrollBar.position > categrorySectionHeightRatios[scrollView.activeCategory]) {
scrollView.activeCategory++
}
}
function scrollToCategory(category) {
if (category === 0) {
return vScrollBar.setPosition(0)
}
vScrollBar.setPosition(categrorySectionHeightRatios[category - 1])
}
contentHeight: {
var totalHeight = 0
var categoryHeights = []
for (let i = 0; i < emojiSectionsRepeater.count; i++) {
totalHeight += emojiSectionsRepeater.itemAt(i).height + Style.current.padding
categoryHeights.push(totalHeight)
}
var ratios = []
categoryHeights.forEach(function (catHeight) {
ratios.push(catHeight / totalHeight)
})
categrorySectionHeightRatios = ratios
return totalHeight + Style.current.padding
}
Repeater {
id: emojiSectionsRepeater
model: popup.categories
EmojiSection {
searchString: popup.searchString
addEmoji: popup.addEmoji
}
}
}
Row {
Layout.fillWidth: true
height: 40
leftPadding: Style.current.smallPadding / 2
rightPadding: Style.current.smallPadding / 2
spacing: 0
Repeater {
model: EmojiJSON.emojiCategories
EmojiCategoryButton {
source: `../../../img/emojiCategories/${modelData}.svg`
active: index === scrollView.activeCategory
changeCategory: function () {
scrollView.activeCategory = index
scrollView.scrollToCategory(index)
}
}
}
}
}
}
/*##^##
Designer {
D{i:0;formeditorColor:"#ffffff";height:440;width:360}
}
##^##*/

View File

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

View File

@ -1,269 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import "../../../../imports"
import "../../../../shared"
Item {
id: root
enum StyleType {
Default,
LargeNoIcon
}
property int style: StickerButton.StyleType.Default
property int packPrice: 0
property bool isBought: false
property bool isPending: false
property bool isInstalled: false
property bool hasUpdate: false
property bool isTimedOut: false
property bool hasInsufficientFunds: false
property bool enabled: true
property var icon: new Object({
path: "../../../img/status-logo-no-bg",
rotation: 0,
runAnimation: false
})
//% "Buy for %1 SNT"
property string text: root.style === StickerButton.StyleType.Default ? packPrice : qsTrId("buy-for--1-snt").arg(packPrice )
property color textColor: style === StickerButton.StyleType.Default ? Style.current.pillButtonTextColor : Style.current.buttonForegroundColor
property color bgColor: style === StickerButton.StyleType.Default ? Style.current.blue : Style.current.secondaryBackground
signal uninstallClicked()
signal installClicked()
signal cancelClicked()
signal updateClicked()
signal buyClicked()
width: pill.width
states: [
State {
name: "installed"
when: root.isInstalled
PropertyChanges {
target: root;
//% "Uninstall"
text: root.style === StickerButton.StyleType.Default ? "" : qsTrId("uninstall");
textColor: root.style === StickerButton.StyleType.Default ? Style.current.pillButtonTextColor : Style.current.red;
bgColor: root.style === StickerButton.StyleType.Default ? Style.current.green : Style.current.lightRed;
icon: new Object({
path: "../../../img/check.svg",
rotation: 0,
runAnimation: false
})
}
},
State {
name: "bought"
when: root.isBought;
PropertyChanges {
target: root;
//% "Install"
text: qsTrId("install");
icon: new Object({
path: "../../../img/arrowUp.svg",
rotation: 180,
runAnimation: false
})
}
},
State {
name: "free"
when: root.packPrice === 0;
extend: "bought"
PropertyChanges {
target: root;
//% "Free"
text: qsTrId("free");
}
},
State {
name: "insufficientFunds"
when: root.hasInsufficientFunds
PropertyChanges {
target: root;
text: root.style === StickerButton.StyleType.Default ? packPrice : packPrice + " SNT";
textColor: root.style === StickerButton.StyleType.Default ? Style.current.pillButtonTextColor : Style.current.darkGrey
bgColor: root.style === StickerButton.StyleType.Default ? Style.current.darkGrey : Style.current.buttonDisabledBackgroundColor;
enabled: false;
}
},
State {
name: "pending"
when: root.isPending
PropertyChanges {
target: root;
//% "Pending..."
text: qsTrId("pending---");
textColor: root.style === StickerButton.StyleType.Default ? Style.current.pillButtonTextColor : Style.current.darkGrey
bgColor: root.style === StickerButton.StyleType.Default ? Style.current.darkGrey : Style.current.grey;
enabled: false;
icon: new Object({
path: "../../../img/loading.png",
rotation: 0,
runAnimation: true
})
}
},
State {
name: "timedOut"
when: root.isTimedOut
extend: "pending"
PropertyChanges {
target: root;
//% "Cancel"
text: qsTrId("browsing-cancel");
textColor: root.style === StickerButton.StyleType.Default ? Style.current.pillButtonTextColor : Style.current.red;
bgColor: root.style === StickerButton.StyleType.Default ? Style.current.red : Style.current.lightRed;
}
},
State {
name: "hasUpdate"
when: root.hasUpdate
extend: "bought"
PropertyChanges {
target: root;
//% "Update"
text: qsTrId("update");
}
}
]
TextMetrics {
id: textMetrics
font.weight: Font.Medium
font.family: Style.current.fontBold.name
font.pixelSize: 15
text: root.text
}
Rectangle {
id: pill
anchors.right: parent.right
width: textMetrics.width + roundedIconImage.width + (Style.current.smallPadding * 2) + 6.7
height: 26
color: root.bgColor
radius: root.style === StickerButton.StyleType.Default ? (width / 2) : 8
states: [
State {
name: "installed"
when: root.isInstalled && root.style === StickerButton.StyleType.Default
PropertyChanges {
target: pill;
width: 28;
height: 28
}
},
State {
name: "large"
when: root.style === StickerButton.StyleType.LargeNoIcon
PropertyChanges {
target: pill;
width: textMetrics.width + (Style.current.padding * 4);
height: 44
}
}
]
SVGImage {
id: roundedIconImage
width: 12
height: 12
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
anchors.verticalCenter: parent.verticalCenter
fillMode: Image.PreserveAspectFit
source: icon.path
rotation: icon.rotation
RotationAnimator {
target: roundedIconImage;
from: 0;
to: 360;
duration: 1200
running: root.icon.runAnimation
loops: Animation.Infinite
}
ColorOverlay {
anchors.fill: roundedIconImage
source: roundedIconImage
color: Style.current.pillButtonTextColor
antialiasing: true
}
states: [
State {
name: "installed"
when: root.isInstalled && root.style === StickerButton.StyleType.Default
PropertyChanges {
target: roundedIconImage;
anchors.leftMargin: 9
width: 11;
height: 8
}
},
State {
name: "large"
when: root.style === StickerButton.StyleType.LargeNoIcon
PropertyChanges {
target: roundedIconImage;
visible: false;
}
}
]
}
Text {
id: content
color: root.textColor
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
anchors.verticalCenter: parent.verticalCenter
text: root.text
font.weight: Font.Medium
font.family: Style.current.fontBold.name
font.pixelSize: 15
states: [
State {
name: "installed"
when: root.isInstalled && root.style === StickerButton.StyleType.Default
PropertyChanges {
target: content;
anchors.rightMargin: 9;
}
},
State {
name: "large"
when: root.style === StickerButton.StyleType.LargeNoIcon
PropertyChanges {
target: content;
anchors.horizontalCenter: parent.horizontalCenter;
anchors.leftMargin: Style.current.padding * 2
anchors.rightMargin: Style.current.padding * 2
}
}
]
}
MouseArea {
id: mouseArea
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
enabled: !root.isPending
cursorShape: Qt.PointingHandCursor
onClicked: {
if (root.isPending) return;
if (root.isInstalled) return root.uninstallClicked();
if (root.packPrice === 0 || root.isBought) return root.installClicked()
if (root.isTimedOut) return root.cancelClicked()
if (root.hasUpdate) return root.updateClicked()
return root.buyClicked()
}
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -1,35 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import "../../../../imports"
import "../../../../shared"
GridView {
id: root
visible: count > 0
anchors.fill: parent
cellWidth: 88
cellHeight: 88
model: stickerList
focus: true
clip: true
signal stickerClicked(string hash, int packId)
delegate: Item {
width: stickerGrid.cellWidth
height: stickerGrid.cellHeight
Column {
anchors.fill: parent
anchors.topMargin: 4
anchors.leftMargin: 4
ImageLoader {
width: 80
height: 80
source: "https://ipfs.infura.io/ipfs/" + url
onClicked: {
root.stickerClicked(hash, packId)
}
}
}
}
}

View File

@ -1,164 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import "../../../../imports"
import "../../../../shared"
import "../ChatColumn/samples"
Item {
id: root
property var stickerPacks: StickerPackData {}
signal backClicked
signal uninstallClicked(int packId)
signal installClicked(var stickers, int packId, int index)
signal cancelClicked(int packId)
signal updateClicked(int packId)
signal buyClicked(int packId)
Component.onCompleted: {
walletModel.getGasPricePredictions()
}
GridView {
id: availableStickerPacks
width: parent.width
height: 380
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.top: parent.top
anchors.topMargin: Style.current.padding
cellWidth: parent.width - (Style.current.padding * 2)
cellHeight: height - 72
model: stickerPacks
focus: true
clip: true
delegate: Item {
width: availableStickerPacks.cellWidth
height: availableStickerPacks.cellHeight
RoundedImage {
id: imgPreview
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height: 220
width: parent.width
radius: 12
source: "https://ipfs.infura.io/ipfs/" + preview
onClicked: {
stickerPackDetailsPopup.open()
}
}
ModalPopup {
id: stickerPackDetailsPopup
height: 472
header: StickerPackDetails {
height: 46
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: Style.current.padding
width: parent.width - (Style.current.padding * 2)
packThumb: thumbnail
packName: name
packAuthor: author
packNameFontSize: 17
spacing: Style.current.padding / 2
}
footer: StickerButton {
height: 76
anchors.right: parent.right
style: StickerButton.StyleType.LargeNoIcon
packPrice: price
isInstalled: installed
isBought: bought
isPending: pending
onInstallClicked: root.installClicked(stickers, packId, index)
onUninstallClicked: root.uninstallClicked(packId)
onCancelClicked: root.cancelClicked(packId)
onUpdateClicked: root.updateClicked(packId)
onBuyClicked: {
stickerPackPurchaseModal.open()
root.buyClicked(packId)
}
}
contentWrapper.anchors.topMargin: 0
contentWrapper.anchors.bottomMargin: 0
StickerList {
id: stickerGridInPopup
model: stickers
height: 350
}
}
StickerPackPurchaseModal {
id: stickerPackPurchaseModal
stickerPackId: packId
packPrice: price
width: stickerPackDetailsPopup.width
height: stickerPackDetailsPopup.height
showBackBtn: stickerPackDetailsPopup.opened
}
StickerPackDetails {
id: stickerPackDetails
height: 64 - (Style.current.smallPadding * 2)
width: parent.width - (Style.current.padding * 2)
anchors.top: imgPreview.bottom
anchors.topMargin: Style.current.smallPadding
anchors.bottomMargin: Style.current.smallPadding
anchors.left: parent.left
anchors.right: parent.right
packThumb: thumbnail
packName: name
packAuthor: author
StickerButton {
anchors.right: parent.right
packPrice: price
width: 75 // only needed for Qt Creator
isInstalled: installed
isBought: bought
isPending: pending
onInstallClicked: root.installClicked(stickers, packId, index)
onUninstallClicked: root.uninstallClicked(packId)
onCancelClicked: root.cancelClicked(packId)
onUpdateClicked: root.updateClicked(packId)
onBuyClicked: {
stickerPackPurchaseModal.open()
root.buyClicked(packId)
}
}
}
}
}
Item {
id: footer
height: 44 - Style.current.padding
anchors.top: availableStickerPacks.bottom
RoundedIcon {
id: btnBack
anchors.top: parent.top
anchors.topMargin: Style.current.padding / 2
anchors.left: parent.left
anchors.leftMargin: Style.current.padding / 2
width: 28
height: 28
iconWidth: 17.5
iconHeight: 13.5
iconColor: Style.current.pillButtonTextColor
source: "../../../img/arrowUp.svg"
rotation: 270
onClicked: {
root.backClicked()
}
}
}
}
/*##^##
Designer {
D{i:0;height:440;width:360}
}
##^##*/

View File

@ -1,47 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import "../../../../imports"
import "../../../../shared"
Item {
id: root
default property alias content: rest.children
property string packThumb: "QmfZrHmLR5VvkXSDbArDR3TX6j4FgpDcrvNz2fHSJk1VvG"
property string packName: "Status Cat"
property string packAuthor: "cryptoworld1373"
property int packNameFontSize: 15
property int spacing: Style.current.padding
RoundedImage {
id: imgThumb
anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter
width: 40
height: 40
source: "https://ipfs.infura.io/ipfs/" + packThumb
}
Column {
anchors.left: imgThumb.right
anchors.leftMargin: root.spacing
anchors.verticalCenter: parent.verticalCenter
Text {
id: txtPackName
text: packName
color: Style.current.textColor
font.family: Style.current.fontBold.name
font.weight: Font.Bold
font.pixelSize: packNameFontSize
}
Text {
color: Style.current.darkGrey
text: packAuthor
font.family: Style.current.fontRegular.name
font.pixelSize: 15
}
}
Item {
anchors.right: parent.right
id: rest
}
}

View File

@ -1,47 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import "../../../../imports"
import "../../../../shared"
Item {
id: root
property bool selected: false
property bool useIconInsteadOfImage: false
property url source: "../../../img/history_icon.svg"
signal clicked
height: 24
width: 24
RoundedImage {
visible: !useIconInsteadOfImage
id: iconImage
width: parent.width
height: parent.height
source: root.source
onClicked: {
root.clicked()
}
}
RoundedIcon {
id: iconIcon
visible: useIconInsteadOfImage
width: parent.width
height: parent.height
iconWidth: 6
color: Style.current.darkGrey
source: root.source
onClicked: {
root.clicked()
}
}
Rectangle {
id: packIndicator
visible: root.selected
border.color: Style.current.blue
border.width: 1
height: 2
width: 16
x: 4
y: root.y + root.height + 6
}
}

View File

@ -1,266 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
ModalPopup {
id: root
readonly property var asset: JSON.parse(walletModel.getStatusToken())
property int stickerPackId: -1
property string packPrice
property bool showBackBtn: false
//% "Authorize %1 %2"
title: qsTrId("authorize--1--2").arg(Utils.stripTrailingZeros(packPrice)).arg(asset.symbol)
property MessageDialog sendingError: MessageDialog {
id: sendingError
//% "Error sending the transaction"
title: qsTrId("error-sending-the-transaction")
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
onClosed: {
stack.reset()
}
function sendTransaction() {
let responseStr = chatsModel.buyStickerPack(root.stickerPackId,
selectFromAccount.selectedAccount.address,
root.packPrice,
gasSelector.selectedGasLimit,
gasSelector.selectedGasPrice,
transactionSigner.enteredPassword)
let response = JSON.parse(responseStr)
if (!response.success) {
if (response.result.includes("could not decrypt key with given password")){
//% "Wrong password"
transactionSigner.validationError = qsTrId("wrong-password")
return
}
sendingError.text = response.result
return sendingError.open()
}
root.close()
}
TransactionStackView {
id: stack
height: parent.height
anchors.fill: parent
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
onGroupActivated: {
root.title = group.headerText
btnNext.text = group.footerText
}
TransactionFormGroup {
id: group1
//% "Authorize %1 %2"
headerText: qsTrId("authorize--1--2").arg(Utils.stripTrailingZeros(root.packPrice)).arg(root.asset.symbol)
//% "Continue"
footerText: qsTrId("continue")
StackView.onActivated: {
btnBack.visible = root.showBackBtn
}
AccountSelector {
id: selectFromAccount
accounts: walletModel.accounts
selectedAccount: walletModel.currentAccount
currency: walletModel.defaultCurrency
width: stack.width
//% "Choose account"
label: qsTrId("choose-account")
showBalanceForAssetSymbol: root.asset.symbol
minRequiredAssetBalance: root.packPrice
reset: function() {
accounts = Qt.binding(function() { return walletModel.accounts })
selectedAccount = Qt.binding(function() { return walletModel.currentAccount })
showBalanceForAssetSymbol = Qt.binding(function() { return root.asset.symbol })
minRequiredAssetBalance = Qt.binding(function() { return root.packPrice })
}
onSelectedAccountChanged: gasSelector.estimateGas()
}
RecipientSelector {
id: selectRecipient
visible: false
accounts: walletModel.accounts
contacts: profileModel.addedContacts
selectedRecipient: { "address": utilsModel.stickerMarketAddress, "type": RecipientSelector.Type.Address }
readOnly: true
onSelectedRecipientChanged: gasSelector.estimateGas()
}
GasSelector {
id: gasSelector
visible: false
slowestGasPrice: parseFloat(walletModel.safeLowGasPrice)
fastestGasPrice: parseFloat(walletModel.fastestGasPrice)
getGasEthValue: walletModel.getGasEthValue
getFiatValue: walletModel.getFiatValue
defaultCurrency: walletModel.defaultCurrency
reset: function() {
slowestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.safeLowGasPrice) })
fastestGasPrice = Qt.binding(function(){ return parseFloat(walletModel.fastestGasPrice) })
}
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
if (!(root.stickerPackId > -1 && selectFromAccount.selectedAccount && root.packPrice && parseFloat(root.packPrice) > 0)) {
selectedGasLimit = 325000
return
}
selectedGasLimit = chatsModel.buyPackGasEstimate(root.stickerPackId, selectFromAccount.selectedAccount.address, root.packPrice)
})
}
GasValidator {
id: gasValidator
anchors.bottom: parent.bottom
anchors.bottomMargin: 8
selectedAccount: selectFromAccount.selectedAccount
selectedAsset: root.asset
selectedAmount: parseFloat(packPrice)
selectedGasEthValue: gasSelector.selectedGasEthValue
reset: function() {
selectedAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
selectedAsset = Qt.binding(function() { return root.asset })
selectedAmount = Qt.binding(function() { return parseFloat(packPrice) })
selectedGasEthValue = Qt.binding(function() { return gasSelector.selectedGasEthValue })
}
}
}
TransactionFormGroup {
id: group3
//% "Authorize %1 %2"
headerText: qsTrId("authorize--1--2").arg(Utils.stripTrailingZeros(root.packPrice)).arg(root.asset.symbol)
//% "Sign with password"
footerText: qsTrId("sign-with-password")
StackView.onActivated: {
btnBack.visible = true
}
TransactionPreview {
id: pvwTransaction
width: stack.width
fromAccount: selectFromAccount.selectedAccount
gas: {
"value": gasSelector.selectedGasEthValue,
"symbol": "ETH",
"fiatValue": gasSelector.selectedGasFiatValue
}
toAccount: selectRecipient.selectedRecipient
asset: root.asset
currency: walletModel.defaultCurrency
amount: {
const fiatValue = walletModel.getFiatValue(root.packPrice || 0, root.asset.symbol, currency)
return { "value": root.packPrice, "fiatValue": fiatValue }
}
reset: function() {
fromAccount = Qt.binding(function() { return selectFromAccount.selectedAccount })
toAccount = Qt.binding(function() { return selectRecipient.selectedRecipient })
asset = Qt.binding(function() { return root.asset })
amount = Qt.binding(function() { return { "value": root.packPrice, "fiatValue": walletModel.getFiatValue(root.packPrice, root.asset.symbol, currency) } })
gas = Qt.binding(function() {
return {
"value": gasSelector.selectedGasEthValue,
"symbol": "ETH",
"fiatValue": gasSelector.selectedGasFiatValue
}
})
}
}
}
TransactionFormGroup {
id: group4
//% "Send %1 %2"
headerText: qsTrId("send--1--2").arg(Utils.stripTrailingZeros(root.packPrice)).arg(root.asset.symbol)
//% "Sign with password"
footerText: qsTrId("sign-with-password")
TransactionSigner {
id: transactionSigner
width: stack.width
signingPhrase: walletModel.signingPhrase
reset: function() {
signingPhrase = Qt.binding(function() { return walletModel.signingPhrase })
}
}
}
}
footer: Item {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
StyledButton {
id: btnBack
anchors.left: parent.left
//% "Back"
label: qsTrId("back")
onClicked: {
if (stack.isFirstGroup) {
return root.close()
}
stack.back()
}
}
StatusButton {
id: btnNext
anchors.right: parent.right
//% "Next"
text: qsTrId("next")
enabled: stack.currentGroup.isValid && !stack.currentGroup.isPending
onClicked: {
const validity = stack.currentGroup.validate()
if (validity.isValid && !validity.isPending) {
if (stack.isLastGroup) {
return root.sendTransaction()
}
stack.next()
}
}
}
Connections {
target: chatsModel
onTransactionWasSent: {
//% "Transaction pending..."
toastMessage.title = qsTrId("ens-transaction-pending")
toastMessage.source = "../../../img/loading.svg"
toastMessage.iconColor = Style.current.primary
toastMessage.iconRotates = true
toastMessage.link = `${walletModel.etherscanLink}/${txResult}`
toastMessage.open()
}
onTransactionCompleted: {
toastMessage.title = !success ?
//% "Could not buy Stickerpack"
qsTrId("could-not-buy-stickerpack")
:
//% "Stickerpack bought successfully"
qsTrId("stickerpack-bought-successfully");
if (success) {
toastMessage.source = "../../../img/check-circle.svg"
toastMessage.iconColor = Style.current.success
} else {
toastMessage.source = "../../../img/block-icon.svg"
toastMessage.iconColor = Style.current.danger
}
toastMessage.link = `${walletModel.etherscanLink}/${txHash}`
toastMessage.open()
}
}
}
}
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -1,259 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import "../../../../imports"
import "../../../../shared"
import "../../../../shared/status"
import "../ChatColumn/samples"
Popup {
id: root
property var recentStickers: StickerData {}
property var stickerPackList: StickerPackData {}
property int installedPacksCount: chatsModel.numInstalledStickerPacks
property bool stickerPacksLoaded: false
modal: false
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
background: Rectangle {
radius: Style.current.radius
color: Style.current.background
border.color: Style.current.border
layer.enabled: true
layer.effect: DropShadow{
verticalOffset: 3
radius: 8
samples: 15
fast: true
cached: true
color: "#22000000"
}
}
onClosed: {
stickerMarket.visible = false
footerContent.visible = true
stickersContainer.visible = true
}
contentItem: ColumnLayout {
anchors.fill: parent
spacing: 0
StickerMarket {
id: stickerMarket
visible: false
Layout.fillWidth: true
Layout.fillHeight: true
stickerPacks: stickerPackList
onInstallClicked: {
chatsModel.installStickerPack(packId)
stickerGrid.model = stickers
stickerPackListView.itemAt(index).clicked()
}
onUninstallClicked: {
chatsModel.uninstallStickerPack(packId)
stickerGrid.model = recentStickers
btnHistory.clicked()
}
onBackClicked: {
stickerMarket.visible = false
footerContent.visible = true
stickersContainer.visible = true
}
}
Item {
id: stickersContainer
Layout.fillWidth: true
Layout.leftMargin: 4
Layout.rightMargin: 4
Layout.topMargin: 4
Layout.bottomMargin: 0
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
Layout.preferredHeight: 400 - 4
Item {
id: noStickerPacks
anchors.fill: parent
visible: false
Image {
id: imgNoStickers
visible: lblNoStickersYet.visible || lblNoRecentStickers.visible
width: 56
height: 56
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
anchors.topMargin: 134
source: "../../../img/stickers_sad_icon.svg"
}
Item {
id: noStickersContainer
width: parent.width
height: 22
anchors.top: imgNoStickers.bottom
anchors.topMargin: 8
StyledText {
id: lblNoStickersYet
visible: root.installedPacksCount === 0
anchors.fill: parent
font.pixelSize: 15
//% "You don't have any stickers yet"
text: qsTrId("you-don't-have-any-stickers-yet")
lineHeight: 22
horizontalAlignment: Text.AlignHCenter
}
StyledText {
id: lblNoRecentStickers
visible: stickerPackListView.selectedPackId === -1 && chatsModel.recentStickers.rowCount() === 0 && !lblNoStickersYet.visible
anchors.fill: parent
font.pixelSize: 15
//% "Recently used stickers will appear here"
text: qsTrId("recently-used-stickers")
lineHeight: 22
horizontalAlignment: Text.AlignHCenter
}
}
StyledButton {
visible: lblNoStickersYet.visible
//% "Get Stickers"
label: qsTrId("get-stickers")
anchors.top: noStickersContainer.bottom
anchors.topMargin: Style.current.padding
anchors.horizontalCenter: parent.horizontalCenter
onClicked: {
stickersContainer.visible = false
stickerMarket.visible = true
footerContent.visible = false
}
}
}
StickerList {
id: stickerGrid
model: recentStickers
onStickerClicked: {
chatsModel.sendSticker(hash, packId)
root.close()
}
}
StickerList {
id: loadingGrid
visible: chatsModel.recentStickers.rowCount() === 0
interactive: false
model: new Array(20)
delegate: Item {
width: stickerGrid.cellWidth
height: stickerGrid.cellHeight
Column {
anchors.fill: parent
anchors.topMargin: 4
anchors.leftMargin: 4
Rectangle {
width: 80
height: 80
radius: width / 2
color: Style.current.backgroundHover
}
}
}
}
}
Item {
id: footerContent
Layout.leftMargin: 8
Layout.fillWidth: true
Layout.preferredHeight: 40 - 8 * 2
Layout.topMargin: 8
Layout.rightMargin: 8
Layout.bottomMargin: 8
Layout.alignment: Qt.AlignTop | Qt.AlignLeft
StatusRoundButton {
id: btnAddStickerPack
size: "medium"
icon.name: "plusSign"
implicitWidth: 24
implicitHeight: 24
state: root.stickerPacksLoaded ? "default" : "pending"
onClicked: {
stickersContainer.visible = false
stickerMarket.visible = true
footerContent.visible = false
}
}
StickerPackIconWithIndicator {
id: btnHistory
width: 24
height: 24
selected: true
useIconInsteadOfImage: true
source: "../../../img/history_icon.svg"
anchors.left: btnAddStickerPack.right
anchors.leftMargin: Style.current.padding
onClicked: {
btnHistory.selected = true
stickerPackListView.selectedPackId = -1
stickerGrid.model = recentStickers
}
}
RowLayout {
spacing: Style.current.padding
anchors.top: parent.top
anchors.left: btnHistory.right
anchors.leftMargin: Style.current.padding
Repeater {
id: stickerPackListView
property int selectedPackId: -1
model: stickerPackList
delegate: StickerPackIconWithIndicator {
id: packIconWithIndicator
visible: installed
width: 24
height: 24
selected: stickerPackListView.selectedPackId === packId
source: "https://ipfs.infura.io/ipfs/" + thumbnail
Layout.preferredHeight: height
Layout.preferredWidth: width
onClicked: {
btnHistory.selected = false
stickerPackListView.selectedPackId = packId
stickerGrid.model = stickers
}
}
}
Repeater {
id: loadingStickerPackListView
model: new Array(7)
delegate: Rectangle {
width: 24
height: 24
Layout.preferredHeight: height
Layout.preferredWidth: width
radius: width / 2
color: Style.current.backgroundHover
}
}
}
}
}
Connections {
target: chatsModel
onStickerPacksLoaded: {
root.stickerPacksLoaded = true
stickerPackListView.visible = true
loadingGrid.visible = false
loadingStickerPackListView.model = []
noStickerPacks.visible = installedPacksCount === 0 || chatsModel.recentStickers.rowCount() === 0
}
}
}

View File

@ -1,4 +1,4 @@
<svg width="20" height="20" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 5.25C10.4142 5.25 10.75 5.58579 10.75 6V8.75C10.75 9.02614 10.9739 9.25 11.25 9.25H14C14.4142 9.25 14.75 9.58579 14.75 10C14.75 10.4142 14.4142 10.75 14 10.75H11.25C10.9739 10.75 10.75 10.9739 10.75 11.25V14C10.75 14.4142 10.4142 14.75 10 14.75C9.58579 14.75 9.25 14.4142 9.25 14V11.25C9.25 10.9739 9.02614 10.75 8.75 10.75H6C5.58579 10.75 5.25 10.4142 5.25 10C5.25 9.58579 5.58579 9.25 6 9.25H8.75C9.02614 9.25 9.25 9.02614 9.25 8.75V6C9.25 5.58579 9.58579 5.25 10 5.25Z" fill="#4360DF"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10V16.6667C20 18.5076 18.5076 20 16.6667 20H10C4.47715 20 0 15.5228 0 10ZM18.5 10V16.6667C18.5 17.6792 17.6792 18.5 16.6667 18.5H10C5.30558 18.5 1.5 14.6944 1.5 10C1.5 5.30558 5.30558 1.5 10 1.5C14.6944 1.5 18.5 5.30558 18.5 10Z" fill="#4360DF"/>
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 5.25C10.4142 5.25 10.75 5.58579 10.75 6V8.75C10.75 9.02614 10.9739 9.25 11.25 9.25H14C14.4142 9.25 14.75 9.58579 14.75 10C14.75 10.4142 14.4142 10.75 14 10.75H11.25C10.9739 10.75 10.75 10.9739 10.75 11.25V14C10.75 14.4142 10.4142 14.75 10 14.75C9.58579 14.75 9.25 14.4142 9.25 14V11.25C9.25 10.9739 9.02614 10.75 8.75 10.75H6C5.58579 10.75 5.25 10.4142 5.25 10C5.25 9.58579 5.58579 9.25 6 9.25H8.75C9.02614 9.25 9.25 9.02614 9.25 8.75V6C9.25 5.58579 9.58579 5.25 10 5.25Z" fill="#939BA1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 10C0 4.47715 4.47715 0 10 0C15.5228 0 20 4.47715 20 10V16.6667C20 18.5076 18.5076 20 16.6667 20H10C4.47715 20 0 15.5228 0 10ZM18.5 10V16.6667C18.5 17.6792 17.6792 18.5 16.6667 18.5H10C5.30558 18.5 1.5 14.6944 1.5 10C1.5 5.30558 5.30558 1.5 10 1.5C14.6944 1.5 18.5 5.30558 18.5 10Z" fill="#939BA1"/>
</svg>

Before

Width:  |  Height:  |  Size: 957 B

After

Width:  |  Height:  |  Size: 957 B

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="22" height="22" rx="11" fill="black"/>
<path d="M15.7728 8.22709C15.47 7.9243 14.9791 7.9243 14.6763 8.22709L12.3655 10.538C12.1636 10.7398 11.8364 10.7398 11.6345 10.538L9.32369 8.22709C9.02091 7.9243 8.53001 7.9243 8.22723 8.22709C7.92445 8.52987 7.92445 9.02078 8.22723 9.32357L10.5381 11.6344C10.7399 11.8363 10.7399 12.1636 10.5381 12.3654L8.22708 14.6764C7.92431 14.9792 7.92431 15.4701 8.22708 15.7729C8.52986 16.0757 9.02076 16.0757 9.32354 15.7729L11.6345 13.4619C11.8364 13.26 12.1636 13.26 12.3655 13.4619L14.6765 15.7729C14.9792 16.0757 15.4701 16.0757 15.7729 15.7729C16.0757 15.4701 16.0757 14.9792 15.7729 14.6764L13.4619 12.3654C13.2601 12.1636 13.2601 11.8363 13.4619 11.6344L15.7728 9.32357C16.0755 9.02078 16.0755 8.52987 15.7728 8.22709Z" fill="white"/>
<rect x="1" y="1" width="22" height="22" rx="11" stroke="#EEF2F5" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 987 B

View File

@ -0,0 +1,5 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="1" y="1" width="22" height="22" rx="11" fill="#939BA1"/>
<path d="M15.7728 8.22709C15.47 7.9243 14.9791 7.9243 14.6763 8.22709L12.3655 10.538C12.1636 10.7398 11.8364 10.7398 11.6345 10.538L9.32369 8.22709C9.02091 7.9243 8.53001 7.9243 8.22723 8.22709C7.92445 8.52987 7.92445 9.02078 8.22723 9.32357L10.5381 11.6344C10.7399 11.8363 10.7399 12.1636 10.5381 12.3654L8.22708 14.6764C7.92431 14.9792 7.92431 15.4701 8.22708 15.7729C8.52986 16.0757 9.02076 16.0757 9.32354 15.7729L11.6345 13.4619C11.8364 13.26 12.1636 13.26 12.3655 13.4619L14.6765 15.7729C14.9792 16.0757 15.4701 16.0757 15.7729 15.7729C16.0757 15.4701 16.0757 14.9792 15.7729 14.6764L13.4619 12.3654C13.2601 12.1636 13.2601 11.8363 13.4619 11.6344L15.7728 9.32357C16.0755 9.02078 16.0755 8.52987 15.7728 8.22709Z" fill="white"/>
<rect x="1" y="1" width="22" height="22" rx="11" stroke="#EEF2F5" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 989 B

View File

@ -0,0 +1,4 @@
<svg width="12" height="20" viewBox="0 0 12 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M4.7977 15.9103C5.05303 15.9488 5.25 16.1628 5.25 16.421V19C5.25 19.4142 5.58579 19.75 6 19.75C6.41421 19.75 6.75 19.4142 6.75 19V16.4209C6.75 16.1627 6.94696 15.9487 7.20227 15.9102C8.67126 15.6888 10.0094 15.0679 11.1024 14.1617C11.4213 13.8974 11.4192 13.4194 11.1263 13.1265C10.8334 12.8336 10.3609 12.8376 10.0364 13.095C8.92766 13.9746 7.52509 14.5 5.99981 14.5C4.47453 14.5 3.07195 13.9746 1.96317 13.095C1.63867 12.8376 1.16618 12.8336 0.873283 13.1265C0.58039 13.4194 0.578337 13.8974 0.897218 14.1617C1.99034 15.068 3.32857 15.6889 4.7977 15.9103Z" fill="#939BA1"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6 0C3.79086 0 2 1.79086 2 4V8C2 10.2091 3.79086 12 6 12C8.20914 12 10 10.2091 10 8V4C10 1.79086 8.20914 0 6 0ZM8.5 8V4C8.5 2.61929 7.38071 1.5 6 1.5C4.61929 1.5 3.5 2.61929 3.5 4V8C3.5 9.38071 4.61929 10.5 6 10.5C7.38071 10.5 8.5 9.38071 8.5 8Z" fill="#939BA1"/>
</svg>

After

Width:  |  Height:  |  Size: 1001 B

View File

@ -2,7 +2,7 @@ pragma Singleton
import QtQuick 2.13
import "./twemoji/twemoji.js" as Twemoji
import "../app/AppLayouts/Chat/components/emojiList.js" as EmojiJSON
import "../shared/status/emojiList.js" as EmojiJSON
QtObject {
property string base: Qt.resolvedUrl("twemoji/")

View File

@ -7,6 +7,7 @@ Theme {
property color black: "#000000"
property color almostBlack: "#141414"
property color grey: "#EEF2F5"
property color lightGrey: "#ccd0d4"
property color lightBlue: "#ECEFFC"
property color cyan: "#00FFFF"
property color blue: "#758EF0"

View File

@ -6,6 +6,7 @@ Theme {
property color white2: "#FCFCFC"
property color black: "#000000"
property color grey: "#EEF2F5"
property color lightGrey: "#ccd0d4"
property color lightBlue: "#ECEFFC"
property color cyan: "#00FFFF"
property color blue: "#4360DF"

View File

@ -0,0 +1,691 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtGraphicalEffects 1.12
import QtQuick.Layouts 1.13
import QtMultimedia 5.13
import QtQuick.Dialogs 1.3
import "../../imports"
import "../../shared"
import "../../app/AppLayouts/Chat/ChatColumn/samples"
import "./emojiList.js" as EmojiJSON
Rectangle {
id: control
signal sendTransactionCommandButtonClicked()
signal receiveTransactionCommandButtonClicked()
signal stickerSelected(string hashId, string packId)
property bool emojiEvent: false;
property bool paste: false;
property bool isColonPressed: false;
property bool isReply: false
property bool isImage: false
property var recentStickers: StickerData {}
property var stickerPackList: StickerPackData {}
property int extraHeightFactor: calculateExtraHeightFactor()
property int messageLimit: 2000
property int messageLimitVisible: 200
property int chatType
property alias textInput: messageInputField
height: {
if (extendedArea.visible) {
return messageInput.height + extendedArea.height + Style.current.bigPadding
}
if (messageInput.height > messageInput.defaultInputFieldHeight) {
if (messageInput.height >= messageInput.maxInputFieldHeight) {
return messageInput.maxInputFieldHeight + Style.current.bigPadding
}
return messageInput.height + Style.current.bigPadding
}
return 64
}
anchors.left: parent.left
anchors.right: parent.right
color: Style.current.background
Audio {
id: sendMessageSound
source: "../../sounds/send_message.wav"
volume: appSettings.volume
}
function calculateExtraHeightFactor() {
const factor = (messageInputField.length / 500) + 1;
return (factor > 5) ? 5 : factor;
}
function insertInTextInput(start, text) {
// Repace new lines with entities because `insert` gets rid of them
messageInputField.insert(start, text.replace(/\n/g, "<br/>"));
}
function interpretMessage(msg) {
if (msg === "/shrug") {
return "¯\\\\\\_(ツ)\\_/¯"
}
if (msg === "/tableflip") {
return "(╯°□°)╯︵ ┻━┻"
}
return msg
}
function onEnter(event){
if (event.modifiers === Qt.NoModifier && (event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {
if (emojiSuggestions.visible) {
emojiSuggestions.addEmoji();
event.accepted = true;
return
}
if (messageInputField.length < messageLimit) {
sendMsg(event);
return;
}
if(event) event.accepted = true
messageTooLongDialog.open()
}
if ((event.key === Qt.Key_V) && (event.modifiers & Qt.ControlModifier)) {
paste = true;
}
if (event.key === Qt.Key_Down) {
return emojiList.incrementCurrentIndex()
}
if (event.key === Qt.Key_Up) {
return emojiList.decrementCurrentIndex()
}
isColonPressed = (event.key === Qt.Key_Colon) && (event.modifiers & Qt.ShiftModifier);
}
function onRelease(event) {
// the text doesn't get registered to the textarea fast enough
// we can only get it in the `released` event
if (paste) {
paste = false;
interrogateMessage();
}
emojiEvent = emojiHandler(event);
if (!emojiEvent) {
emojiSuggestions.close()
}
}
function interrogateMessage() {
const text = chatsModel.plainText(Emoji.deparse(messageInputField.text));
var words = text.split(' ');
for (var i = 0; i < words.length; i++) {
var transform = true;
if (words[i].charAt(0) === ':') {
for (var j = 0; j < words[i].length; j++) {
if (Utils.isSpace(words[i].charAt(j)) === true || Utils.isPunct(words[i].charAt(j)) === true) {
transform = false;
}
}
if (transform) {
const codePoint = Emoji.getEmojiUnicode(words[i]);
words[i] = words[i].replace(words[i], (codePoint !== undefined) ? Emoji.fromCodePoint(codePoint) : words[i]);
}
}
}
messageInputField.remove(0, messageInputField.length);
insertInTextInput(0, Emoji.parse(words.join('&nbsp;'), '26x26'));
}
// since emoji length is not 1 we need to match that position that TextArea returns
// to the actual position in the string.
function extrapolateCursorPosition() {
// we need only the message part to be html
const text = chatsModel.plainText(Emoji.deparse(messageInputField.text));
const plainText = Emoji.parse(text, '26x26');
var bracketEvent = false;
var length = 0;
for (var i = 0; i < plainText.length;) {
if (length >= messageInputField.cursorPosition) break;
if (!bracketEvent && plainText.charAt(i) !== '<') {
i++;
length++;
} else if (!bracketEvent && plainText.charAt(i) === '<') {
bracketEvent = true;
i++;
} else if (bracketEvent && plainText.charAt(i) !== '>') {
i++;
} else if (bracketEvent && plainText.charAt(i) === '>') {
bracketEvent = false;
i++;
length++;
}
}
let textBeforeCursor = Emoji.deparseFromParse(plainText.substr(0, i));
return {
cursor: countEmojiLengths(plainText.substr(0, i)) + messageInputField.cursorPosition,
data: Emoji.deparseFromParse(textBeforeCursor),
};
}
function emojiHandler(event) {
let message = extrapolateCursorPosition();
pollEmojiEvent(message);
// state machine to handle different forms of the emoji event state
if (!emojiEvent && isColonPressed) {
return (message.data.length <= 1 || Utils.isSpace(message.data.charAt(message.cursor - 1))) ? true : false;
} else if (emojiEvent && isColonPressed) {
const index = message.data.lastIndexOf(':', message.cursor - 2);
if (index >= 0 && message.cursor > 0) {
const shortname = message.data.substr(index, message.cursor);
const codePoint = Emoji.getEmojiUnicode(shortname);
if (codePoint !== undefined) {
replaceWithEmoji(message, shortname, codePoint);
}
return false;
}
return true;
} else if (emojiEvent && isKeyValid(event.key) && !isColonPressed) {
// popup
const index2 = message.data.lastIndexOf(':', message.cursor - 1);
if (index2 >= 0 && message.cursor > 0) {
const emojiPart = message.data.substr(index2, message.cursor);
if (emojiPart.length > 2) {
const emojis = EmojiJSON.emoji_json.filter(function (emoji) {
return emoji.name.includes(emojiPart) ||
emoji.shortname.includes(emojiPart) ||
emoji.aliases.some(a => a.includes(emojiPart))
})
emojiSuggestions.openPopup(emojis, emojiPart)
}
return true;
}
} else if (emojiEvent && !isKeyValid(event.key) && !isColonPressed) {
return false;
}
return false;
}
function countEmojiLengths(value) {
const match = Emoji.getEmojis(value);
var length = 0;
if (match && match.length > 0) {
for (var i = 0; i < match.length; i++) {
length += Emoji.deparseFromParse(match[i]).length;
}
length = length - match.length;
}
return length;
}
function replaceWithEmoji(message, shortname, codePoint) {
const encodedCodePoint = Emoji.getEmojiCodepoint(codePoint)
const newMessage = message.data
.replace(shortname, encodedCodePoint)
.replace(/ /g, "&nbsp;");
messageInputField.remove(0, messageInputField.cursorPosition);
insertInTextInput(0, Emoji.parse(newMessage, '26x26'));
emojiSuggestions.close()
emojiEvent = false
}
// check if user has placed cursor near valid emoji colon token
function pollEmojiEvent(message) {
const index = message.data.lastIndexOf(':', message.cursor);
if (index >= 0) {
emojiEvent = validSubstr(message.data.substr(index, message.cursor - index));
}
}
function validSubstr(substr) {
for(var i = 0; i < substr.length; i++) {
var c = substr.charAt(i);
if (Utils.isSpace(c) === true || Utils.isPunct(c) === true)
return false;
}
return true;
}
function isKeyValid(key) {
if (key === Qt.Key_Space || key === Qt.Key_Tab ||
(key >= Qt.Key_Exclam && key <= Qt.Key_Slash) ||
(key >= Qt.Key_Semicolon && key <= Qt.Key_Question) ||
(key >= Qt.Key_BracketLeft && key <= Qt.Key_hyphen))
return false;
return true;
}
function sendMsg(event){
if(control.isImage){
chatsModel.sendImage(imageArea.imageSource);
}
var msg = chatsModel.plainText(Emoji.deparse(messageInputField.text).trim()).trim()
if(msg.length > 0){
msg = interpretMessage(msg)
chatsModel.sendMessage(msg, control.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType);
messageInputField.text = "";
if(event) event.accepted = true
sendMessageSound.stop()
Qt.callLater(sendMessageSound.play);
}
control.hideExtendedArea();
}
function hideExtendedArea() {
isImage = false;
isReply = false;
imageArea.imageSource = "";
replyArea.userName = ""
replyArea.identicon = ""
replyArea.message = ""
}
function showImageArea(imagePath) {
isImage = true;
isReply = false;
imageArea.imageSource = imageDialog.fileUrls[0]
}
function showReplyArea(userName, message, identicon) {
isReply = true
replyArea.userName = userName
replyArea.message = message
replyArea.identicon = identicon
}
FileDialog {
id: imageDialog
//% "Please choose an image"
title: qsTrId("please-choose-an-image")
folder: shortcuts.pictures
nameFilters: [
//% "Image files (*.jpg *.jpeg *.png)"
qsTrId("image-files----jpg---jpeg---png-")
]
onAccepted: {
imageBtn.highlighted = false
control.showImageArea()
messageInputField.forceActiveFocus();
}
onRejected: {
imageBtn.highlighted = false
}
}
MessageDialog {
id: messageTooLongDialog
//% "Your message is too long."
title: qsTrId("your-message-is-too-long.")
icon: StandardIcon.Critical
//% "Please make your message shorter. We have set the limit to 2000 characters to be courteous of others."
text: qsTrId("please-make-your-message-shorter.-we-have-set-the-limit-to-2000-characters-to-be-courteous-of-others.")
standardButtons: StandardButton.Ok
}
Popup {
property var emojis
property string shortname
function openPopup(emojisParam, shortnameParam) {
emojis = emojisParam
shortname = shortnameParam
emojiSuggestions.open()
}
function addEmoji(index) {
if (index === undefined) {
index = emojiList.currentIndex
}
const message = extrapolateCursorPosition();
const unicode = emojiSuggestions.emojis[index].unicode_alternates || emojiSuggestions.emojis[index].unicode
replaceWithEmoji(message, emojiSuggestions.shortname, unicode)
}
id: emojiSuggestions
width: parent.width - Style.current.padding * 2
height: Math.min(400, emojiList.contentHeight + Style.current.smallPadding * 2)
x : Style.current.padding / 2
y: -height - Style.current.smallPadding
background: Rectangle {
visible: !!emojiSuggestions.emojis && emojiSuggestions.emojis.length > 0
color: Style.current.secondaryBackground
border.width: 1
border.color: Style.current.borderSecondary
radius: Style.current.radius
}
ListView {
id: emojiList
model: emojiSuggestions.emojis || []
keyNavigationEnabled: true
anchors.fill: parent
clip: true
delegate: Rectangle {
id: rectangle
color: emojiList.currentIndex === index ? Style.current.inputBorderFocus : Style.current.transparent
border.width: 0
width: parent.width
height: 42
radius: Style.current.radius
SVGImage {
id: emojiImage
source: `../../imports/twemoji/26x26/${modelData.unicode}.png`
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
}
StyledText {
text: modelData.shortname
color: emojiList.currentIndex === index ? Style.current.currentUserTextColor : Style.current.textColor
anchors.verticalCenter: parent.verticalCenter
anchors.left: emojiImage.right
anchors.leftMargin: Style.current.smallPadding
font.pixelSize: 15
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onEntered: {
emojiList.currentIndex = index
}
onClicked: {
emojiSuggestions.addEmoji(index)
}
}
}
}
}
StatusChatCommandsPopup {
id: chatCommandsPopup
x: 8
y: -height
onSendTransactionCommandButtonClicked: {
control.sendTransactionCommandButtonClicked()
chatCommandsPopup.close()
}
onReceiveTransactionCommandButtonClicked: {
control.receiveTransactionCommandButtonClicked()
chatCommandsPopup.close()
}
onClosed: {
chatCommandsBtn.highlighted = false
}
}
StatusEmojiPopup {
id: emojiPopup
width: 360
height: 440
x: parent.width - width - Style.current.halfPadding
y: -height
emojiSelected: function (text, atCursor) {
insertInTextInput(atCursor ? messageInputField.cursorPosition : messageInputField.length, text)
emojiBtn.highlighted = false
messageInputField.forceActiveFocus();
}
onClosed: {
emojiBtn.highlighted = false
}
}
StatusStickersPopup {
id: stickersPopup
width: 360
height: 440
x: parent.width - width - Style.current.halfPadding
y: -height
recentStickers: control.recentStickers
stickerPackList: control.stickerPackList
onStickerSelected: {
control.stickerSelected(hashId, packId)
messageInputField.forceActiveFocus();
stickersPopup.close()
}
onClosed: {
stickersBtn.highlighted = false
}
}
StatusIconButton {
id: chatCommandsBtn
icon.name: "chat-commands"
anchors.left: parent.left
anchors.leftMargin: 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 16
visible: control.chatType === Constants.chatTypeOneToOne
onClicked: {
highlighted = true
chatCommandsPopup.open()
}
}
StatusIconButton {
id: imageBtn
icon.name: "images_icon"
icon.height: 18
icon.width: 20
anchors.left: chatCommandsBtn.visible ? chatCommandsBtn.right : parent.left
anchors.leftMargin: chatCommandsBtn.visible ? 2 : 4
anchors.bottom: parent.bottom
anchors.bottomMargin: 16
visible: control.chatType !== Constants.chatTypePublic
onClicked: {
highlighted = true
imageDialog.open()
}
}
Rectangle {
id: extendedArea
visible: isImage || isReply
height: {
if (visible) {
if (isImage) {
return imageArea.height
}
if (isReply) {
return replyArea.height + replyArea.anchors.topMargin
}
}
return 0
}
anchors.left: messageInput.left
anchors.right: messageInput.right
anchors.bottom: messageInput.top
color: Style.current.inputBackground
radius: 16
Rectangle {
height: 16
color: parent.color
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
}
StatusChatInputImageArea {
id: imageArea
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
visible: isImage
onImageRemoved: {
isImage = false
}
}
StatusChatInputReplyArea {
id: replyArea
visible: isReply
anchors.left: parent.left
anchors.leftMargin: 2
anchors.right: parent.right
anchors.rightMargin: 2
anchors.top: parent.top
anchors.topMargin: 2
onCloseButtonClicked: {
isReply = false
}
}
}
Rectangle {
id: messageInput
property int maxInputFieldHeight: 112
property int defaultInputFieldHeight: 40
anchors.left: imageBtn.visible ? imageBtn.right : parent.left
anchors.leftMargin: imageBtn.visible ? 5 : Style.current.smallPadding
anchors.bottom: parent.bottom
anchors.bottomMargin: 12
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
height: scrollView.height
color: Style.current.inputBackground
radius: height > defaultInputFieldHeight || extendedArea.visible ? 16 : 32
Rectangle {
color: parent.color
anchors.right: parent.right
anchors.left: parent.left
anchors.top: parent.top
height: 18
visible: extendedArea.visible
}
ScrollView {
id: scrollView
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
anchors.right: actions.left
anchors.rightMargin: 0
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
height: {
if (messageInputField.height <= messageInput.defaultInputFieldHeight) {
return messageInput.defaultInputFieldHeight
}
if (messageInputField.height >= messageInput.maxInputFieldHeight) {
return messageInput.maxInputFieldHeight
}
return messageInputField.height
}
TextArea {
id: messageInputField
textFormat: Text.RichText
verticalAlignment: TextEdit.AlignVCenter
font.pixelSize: 15
font.family: Style.current.fontRegular.name
wrapMode: TextArea.Wrap
anchors.bottom: parent.bottom
anchors.top: parent.top
placeholderText: qsTr("Type a message")
placeholderTextColor: Style.current.secondaryText
selectByMouse: true
color: Style.current.textColor
topPadding: Style.current.smallPadding
bottomPadding: 12
Keys.onPressed: onEnter(event)
Keys.onReleased: onRelease(event) // gives much more up to date cursorPosition
leftPadding: 0
background: Rectangle {
color: "transparent"
}
}
}
Rectangle {
color: parent.color
anchors.bottom: parent.bottom
anchors.right: parent.right
height: parent.height / 2
width: 32
radius: Style.current.radius
}
StyledText {
id: messageLengthLimit
property int remainingChars: messageLimit - messageInputField.length
text: remainingChars.toString()
visible: remainingChars <= control.messageLimitVisible
color: (remainingChars <= 0) ? Style.current.danger : Style.current.textColor
anchors.right: parent.right
anchors.bottom: actions.top
anchors.rightMargin: Style.current.radius
leftPadding: Style.current.halfPadding
rightPadding: Style.current.halfPadding
}
Item {
id: actions
width: childrenRect.width
anchors.bottom: parent.bottom
anchors.bottomMargin: 4
anchors.right: parent.right
anchors.rightMargin: Style.current.radius
height: emojiBtn.height
StatusIconButton {
id: emojiBtn
anchors.left: parent.left
anchors.bottom: parent.bottom
icon.name: "emojiBtn"
type: "secondary"
onClicked: {
stickersPopup.close()
if (emojiPopup.opened) {
emojiPopup.close()
highlighted = false
} else {
emojiPopup.open()
highlighted = true
}
}
}
StatusIconButton {
id: stickersBtn
anchors.left: emojiBtn.right
anchors.leftMargin: 2
anchors.bottom: parent.bottom
icon.name: "stickers_icon"
type: "secondary"
onClicked: {
emojiPopup.close()
if (stickersPopup.opened) {
stickersPopup.close()
highlighted = false
} else {
stickersPopup.open()
highlighted = true
}
}
}
}
}
}

View File

@ -0,0 +1,70 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import "../../imports"
import "../../shared"
Rectangle {
id: imageArea
height: 72
signal imageRemoved()
property url imageSource: ""
color: "transparent"
Image {
id: chatImage
property bool hovered: false
height: 64
anchors.left: parent.left
anchors.leftMargin: Style.current.halfPadding
anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
fillMode: Image.PreserveAspectFit
mipmap: true
smooth: false
antialiasing: true
source: parent.imageSource
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
hoverEnabled: true
onEntered: {
chatImage.hovered = true
}
onExited: {
chatImage.hovered = false
}
}
RoundButton {
id: closeBtn
implicitWidth: 24
implicitHeight: 24
padding: 0
anchors.top: chatImage.top
anchors.topMargin: -5
anchors.right: chatImage.right
anchors.rightMargin: -Style.current.halfPadding
visible: chatImage.hovered || hovered
contentItem: SVGImage {
source: !closeBtn.hovered ?
"../../app/img/close-filled.svg" : "../../app/img/close-filled-hovered.svg"
width: closeBtn.width
height: closeBtn.height
}
background: Rectangle {
color: "transparent"
}
onClicked: {
imageArea.imageRemoved()
chatImage.source = ""
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onPressed: mouse.accepted = false
}
}
}
}

View File

@ -0,0 +1,100 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtGraphicalEffects 1.13
import "../../imports"
import "../../shared"
Rectangle {
id: root
height: 50
color: Style.current.lightGrey
radius: 16
clip: true
property string userName: ""
property string message : ""
property string identicon: ""
signal closeButtonClicked()
Rectangle {
color: parent.color
anchors.bottom: parent.bottom
anchors.right: parent.right
height: parent.height / 2
width: 32
radius: Style.current.radius
}
StyledText {
id: replyToUsername
text: "↪ " + userName
color: Style.current.black
anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
font.pixelSize: 13
font.weight: Font.Medium
}
StyledText {
id: replyText
text: Emoji.parse(message, "26x26")
anchors.left: replyToUsername.left
anchors.top: replyToUsername.bottom
anchors.topMargin: 2
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
anchors.bottom: parent.bottom
elide: Text.ElideRight
font.pixelSize: 13
font.weight: Font.Normal
// Eliding only works for PlainText: https://bugreports.qt.io/browse/QTBUG-16567
textFormat: Text.PlainText
color: Style.current.black
}
RoundButton {
id: closeBtn
implicitWidth: 20
implicitHeight: 20
radius: 10
padding: 0
anchors.top: parent.top
anchors.topMargin: 4
anchors.right: parent.right
anchors.rightMargin: 4
contentItem: SVGImage {
id: iconImg
source: "../../app/img/close.svg"
width: closeBtn.width
height: closeBtn.height
ColorOverlay {
anchors.fill: iconImg
source: iconImg
color: Style.current.black
antialiasing: true
}
}
background: Rectangle {
color: "transparent"
width: closeBtn.width
height: closeBtn.height
radius: closeBtn.radius
}
onClicked: {
root.userName = ""
root.message = ""
root.identicon = ""
root.closeButtonClicked()
}
MouseArea {
cursorShape: Qt.PointingHandCursor
anchors.fill: parent
onPressed: mouse.accepted = false
}
}
}

View File

@ -27,7 +27,7 @@ RoundButton {
if (type === "secondary") {
return "transparent"
}
return hovered || highlighted ? Style.current.lightBlue : "transparent"
return hovered || highlighted ? Style.current.secondaryBackground : "transparent"
}
radius: control.radius
}

View File

@ -3,6 +3,7 @@ StatusChatCommandButton 1.0 StatusChatCommandButton.qml
StatusChatCommandPopup 1.0 StatusChatCommandPopup.qml
StatusChatInfo 1.0 StatusChatInfo.qml
StatusChatInfoButton 1.0 StatusChatInfoButton.qml
StatusChatInput 1.0 StatusChatInput.qml
StatusEmojiCategoryButton 1.0 StatusEmojiCategoryButton.qml
StatusEmojiPopup 1.0 StatusEmojiPopup.qml
StatusEmojiSection 1.0 StatusEmojiSection.qml
@ -19,5 +20,5 @@ StatusStickerList 1.0 StatusStickerList.qml
StatusStickerMarket 1.0 StatusStickerMarket.qml
StatusStickerPackDetails 1.0 StatusStickerPackDetails.qml
StatusStickerPackPurchaseModal 1.0 StatusStickerPackPurchaseModal.qml
StatusStickersPopup 1.0 StatusStickerPopup.qml
StatusStickersPopup 1.0 StatusStickersPopup.qml
StatusToolTip 1.0 StatusToolTip.qml