diff --git a/ui/app/AppLayouts/Chat/ChatColumn.qml b/ui/app/AppLayouts/Chat/ChatColumn.qml index 2d9cf8e720..384be27736 100644 --- a/ui/app/AppLayouts/Chat/ChatColumn.qml +++ b/ui/app/AppLayouts/Chat/ChatColumn.qml @@ -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 + } } /*##^## diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatButtons.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatButtons.qml deleted file mode 100644 index 9a037f7543..0000000000 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatButtons.qml +++ /dev/null @@ -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} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ChatInput.qml b/ui/app/AppLayouts/Chat/ChatColumn/ChatInput.qml deleted file mode 100644 index 79074bcd99..0000000000 --- a/ui/app/AppLayouts/Chat/ChatColumn/ChatInput.qml +++ /dev/null @@ -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, "
")); - } - - 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(' '), '26x26')); - } - - function replaceWithEmoji(message, shortname, codePoint) { - const encodedCodePoint = Emoji.getEmojiCodepoint(codePoint) - const newMessage = message.data - .replace(shortname, encodedCodePoint) - .replace(/ /g, " "); - 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"} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/ChatColumn/ReplyArea.qml b/ui/app/AppLayouts/Chat/ChatColumn/ReplyArea.qml deleted file mode 100644 index 7910a01783..0000000000 --- a/ui/app/AppLayouts/Chat/ChatColumn/ReplyArea.qml +++ /dev/null @@ -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 - } - -} diff --git a/ui/app/AppLayouts/Chat/ChatColumn/SendImageArea.qml b/ui/app/AppLayouts/Chat/ChatColumn/SendImageArea.qml deleted file mode 100644 index 45039fe0e8..0000000000 --- a/ui/app/AppLayouts/Chat/ChatColumn/SendImageArea.qml +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/components/EmojiCategoryButton.qml b/ui/app/AppLayouts/Chat/components/EmojiCategoryButton.qml deleted file mode 100644 index 056cfdf661..0000000000 --- a/ui/app/AppLayouts/Chat/components/EmojiCategoryButton.qml +++ /dev/null @@ -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} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/components/EmojiPopup.qml b/ui/app/AppLayouts/Chat/components/EmojiPopup.qml deleted file mode 100644 index 5b03e3d135..0000000000 --- a/ui/app/AppLayouts/Chat/components/EmojiPopup.qml +++ /dev/null @@ -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} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/components/EmojiSection.qml b/ui/app/AppLayouts/Chat/components/EmojiSection.qml deleted file mode 100644 index 20cef38677..0000000000 --- a/ui/app/AppLayouts/Chat/components/EmojiSection.qml +++ /dev/null @@ -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} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/components/StickerButton.qml b/ui/app/AppLayouts/Chat/components/StickerButton.qml deleted file mode 100644 index 3dc27ec031..0000000000 --- a/ui/app/AppLayouts/Chat/components/StickerButton.qml +++ /dev/null @@ -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} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/components/StickerList.qml b/ui/app/AppLayouts/Chat/components/StickerList.qml deleted file mode 100644 index 7eb7386451..0000000000 --- a/ui/app/AppLayouts/Chat/components/StickerList.qml +++ /dev/null @@ -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) - } - } - } - } -} \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/components/StickerMarket.qml b/ui/app/AppLayouts/Chat/components/StickerMarket.qml deleted file mode 100644 index 0dd24e4d58..0000000000 --- a/ui/app/AppLayouts/Chat/components/StickerMarket.qml +++ /dev/null @@ -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} -} -##^##*/ diff --git a/ui/app/AppLayouts/Chat/components/StickerPackDetails.qml b/ui/app/AppLayouts/Chat/components/StickerPackDetails.qml deleted file mode 100644 index 8f25acb5f1..0000000000 --- a/ui/app/AppLayouts/Chat/components/StickerPackDetails.qml +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/components/StickerPackIconWithIndicator.qml b/ui/app/AppLayouts/Chat/components/StickerPackIconWithIndicator.qml deleted file mode 100644 index 5f773a5473..0000000000 --- a/ui/app/AppLayouts/Chat/components/StickerPackIconWithIndicator.qml +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/ui/app/AppLayouts/Chat/components/StickerPackPurchaseModal.qml b/ui/app/AppLayouts/Chat/components/StickerPackPurchaseModal.qml deleted file mode 100644 index f593bd90c6..0000000000 --- a/ui/app/AppLayouts/Chat/components/StickerPackPurchaseModal.qml +++ /dev/null @@ -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} -} -##^##*/ - diff --git a/ui/app/AppLayouts/Chat/components/StickersPopup.qml b/ui/app/AppLayouts/Chat/components/StickersPopup.qml deleted file mode 100644 index a0e30d8f86..0000000000 --- a/ui/app/AppLayouts/Chat/components/StickersPopup.qml +++ /dev/null @@ -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 - } - } -} - diff --git a/ui/app/img/chat-commands.svg b/ui/app/img/chat-commands.svg index 563221ccbe..77121ba011 100644 --- a/ui/app/img/chat-commands.svg +++ b/ui/app/img/chat-commands.svg @@ -1,4 +1,4 @@ - - - + + + diff --git a/ui/app/img/close-filled-hovered.svg b/ui/app/img/close-filled-hovered.svg new file mode 100644 index 0000000000..743bc6f887 --- /dev/null +++ b/ui/app/img/close-filled-hovered.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/app/img/close-filled.svg b/ui/app/img/close-filled.svg new file mode 100644 index 0000000000..1d269adff4 --- /dev/null +++ b/ui/app/img/close-filled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/app/img/microphone.svg b/ui/app/img/microphone.svg new file mode 100644 index 0000000000..b5c3269b91 --- /dev/null +++ b/ui/app/img/microphone.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/imports/Emoji.qml b/ui/imports/Emoji.qml index d91487421d..3fd52c83db 100644 --- a/ui/imports/Emoji.qml +++ b/ui/imports/Emoji.qml @@ -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/") diff --git a/ui/imports/Themes/DarkTheme.qml b/ui/imports/Themes/DarkTheme.qml index d9e1e28f1d..a8ae129d80 100644 --- a/ui/imports/Themes/DarkTheme.qml +++ b/ui/imports/Themes/DarkTheme.qml @@ -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" diff --git a/ui/imports/Themes/LightTheme.qml b/ui/imports/Themes/LightTheme.qml index 989d6013de..27c26db877 100644 --- a/ui/imports/Themes/LightTheme.qml +++ b/ui/imports/Themes/LightTheme.qml @@ -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" diff --git a/ui/shared/status/StatusChatInput.qml b/ui/shared/status/StatusChatInput.qml new file mode 100644 index 0000000000..7fdf1995af --- /dev/null +++ b/ui/shared/status/StatusChatInput.qml @@ -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, "
")); + } + + 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(' '), '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, " "); + 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 + } + } + } + } + } +} diff --git a/ui/shared/status/StatusChatInputImageArea.qml b/ui/shared/status/StatusChatInputImageArea.qml new file mode 100644 index 0000000000..4c50de0bc9 --- /dev/null +++ b/ui/shared/status/StatusChatInputImageArea.qml @@ -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 + } + } + } + +} diff --git a/ui/shared/status/StatusChatInputReplyArea.qml b/ui/shared/status/StatusChatInputReplyArea.qml new file mode 100644 index 0000000000..9a1e0e9779 --- /dev/null +++ b/ui/shared/status/StatusChatInputReplyArea.qml @@ -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 + } + } + +} diff --git a/ui/shared/status/StatusIconButton.qml b/ui/shared/status/StatusIconButton.qml index 0fea02b4a1..9c7b426099 100644 --- a/ui/shared/status/StatusIconButton.qml +++ b/ui/shared/status/StatusIconButton.qml @@ -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 } diff --git a/ui/shared/status/qmldir b/ui/shared/status/qmldir index 7bf1f25dc6..f711c65fc2 100644 --- a/ui/shared/status/qmldir +++ b/ui/shared/status/qmldir @@ -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