mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-12 23:35:32 +00:00
f1e83f74bc
Allow up to 5 images to be dragged and dropped in to one-on-one chats and in the timeline. Can be combined with the existing upload button. The upload file dialog has been changed to allow multiple selections. Drag and dropped images adhere to the following rules, with corresponding validations messages: - Max 5 image - Image size must be 0.5 MB or less - File extension must be one of [".png", ".jpg", ".jpeg", ".heif", "tif", ".tiff"] Drag and drop and uploaded images are now also deduplicated.
440 lines
15 KiB
QML
440 lines
15 KiB
QML
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"
|
|
import "../Wallet"
|
|
|
|
StackLayout {
|
|
id: chatColumnLayout
|
|
|
|
property int chatGroupsListViewCount: 0
|
|
|
|
property bool isReply: false
|
|
property bool isImage: false
|
|
|
|
property bool isExtendedInput: isReply || isImage
|
|
|
|
property bool isConnected: false
|
|
property string contactToRemove: ""
|
|
|
|
property var onActivated: function () {
|
|
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
|
|
}
|
|
|
|
property bool isBlocked: profileModel.contacts.isContactBlocked(chatsModel.activeChannel.id)
|
|
|
|
|
|
Component.onCompleted: {
|
|
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
|
|
}
|
|
|
|
Layout.fillHeight: true
|
|
Layout.fillWidth: true
|
|
Layout.minimumWidth: 300
|
|
|
|
currentIndex: chatsModel.activeChannelIndex > -1 && chatGroupsListViewCount > 0 ? 0 : 1
|
|
|
|
|
|
property var idMap: ({})
|
|
|
|
function addSuggestionFromMessageList(i){
|
|
const contactAddr = chatsModel.messageList.getMessageData(i, "publicKey");
|
|
if(idMap[contactAddr]) return;
|
|
chatInput.suggestionsList.append({
|
|
alias: chatsModel.messageList.getMessageData(i, "alias"),
|
|
ensName: chatsModel.messageList.getMessageData(i, "ensName"),
|
|
address: contactAddr,
|
|
identicon: chatsModel.messageList.getMessageData(i, "identicon"),
|
|
localNickname: chatsModel.messageList.getMessageData(i, "localName")
|
|
});
|
|
idMap[contactAddr] = true;
|
|
}
|
|
|
|
function populateSuggestions(){
|
|
chatInput.suggestionsList.clear()
|
|
const len = chatsModel.suggestionList.rowCount()
|
|
|
|
idMap = {}
|
|
|
|
for (let i = 0; i < len; i++) {
|
|
const contactAddr = chatsModel.suggestionList.rowData(i, "address");
|
|
if(idMap[contactAddr]) continue;
|
|
const contactIndex = profileModel.contacts.list.getContactIndexByPubkey(chatsModel.suggestionList.rowData(i, "address"));
|
|
chatInput.suggestionsList.append({
|
|
alias: chatsModel.suggestionList.rowData(i, "alias"),
|
|
ensName: chatsModel.suggestionList.rowData(i, "ensName"),
|
|
address: contactAddr,
|
|
identicon: profileModel.contacts.list.rowData(contactIndex, "thumbnailImage"),
|
|
localNickname: chatsModel.suggestionList.rowData(i, "localNickname")
|
|
});
|
|
idMap[contactAddr] = true;
|
|
}
|
|
const len2 = chatsModel.messageList.rowCount();
|
|
for (let f = 0; f < len2; f++) {
|
|
addSuggestionFromMessageList(f);
|
|
}
|
|
}
|
|
|
|
|
|
function showReplyArea() {
|
|
isReply = true;
|
|
isImage = false;
|
|
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 requestAddressForTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
|
|
amount = utilsModel.eth2Wei(amount.toString(), tokenDecimals)
|
|
chatsModel.transactions.requestAddress(chatsModel.activeChannel.id,
|
|
address,
|
|
amount,
|
|
tokenAddress)
|
|
txModalLoader.close()
|
|
}
|
|
function requestTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
|
|
amount = utilsModel.eth2Wei(amount.toString(), tokenDecimals)
|
|
chatsModel.transactions.request(chatsModel.activeChannel.id,
|
|
address,
|
|
amount,
|
|
tokenAddress)
|
|
txModalLoader.close()
|
|
}
|
|
|
|
Connections {
|
|
target: profileModel.contacts
|
|
onContactListChanged: {
|
|
isBlocked = profileModel.contacts.isContactBlocked(chatsModel.activeChannel.id);
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
spacing: 0
|
|
|
|
RowLayout {
|
|
id: chatTopBar
|
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignTop
|
|
Layout.fillWidth: true
|
|
z: 60
|
|
spacing: 0
|
|
TopBar {
|
|
id: topBar
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
Layout.alignment: Qt.AlignHCenter
|
|
Layout.fillWidth: true
|
|
z: 60
|
|
Rectangle {
|
|
Component.onCompleted: {
|
|
isConnected = chatsModel.isOnline
|
|
if(!isConnected){
|
|
connectedStatusRect.visible = true
|
|
}
|
|
}
|
|
|
|
id: connectedStatusRect
|
|
Layout.fillWidth: true
|
|
height: 40;
|
|
color: isConnected ? Style.current.green : Style.current.darkGrey
|
|
visible: false
|
|
Text {
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
color: Style.current.white
|
|
id: connectedStatusLbl
|
|
text: isConnected ?
|
|
//% "Connected"
|
|
qsTrId("connected") :
|
|
//% "Disconnected"
|
|
qsTrId("disconnected")
|
|
}
|
|
}
|
|
|
|
Timer {
|
|
id: timer
|
|
}
|
|
|
|
Connections {
|
|
target: chatsModel
|
|
onOnlineStatusChanged: {
|
|
if (connected == isConnected) return;
|
|
isConnected = connected;
|
|
if(isConnected){
|
|
timer.setTimeout(function(){
|
|
connectedStatusRect.visible = false;
|
|
}, 5000);
|
|
} else {
|
|
connectedStatusRect.visible = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RowLayout {
|
|
id: chatContainer
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
|
spacing: 0
|
|
|
|
Connections {
|
|
target: chatsModel
|
|
onActiveChannelChanged: {
|
|
stackLayoutChatMessages.currentIndex = chatsModel.getMessageListIndex(chatsModel.activeChannelIndex)
|
|
if(stackLayoutChatMessages.currentIndex > -1 && !stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active){
|
|
stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
StackLayout {
|
|
id: stackLayoutChatMessages
|
|
Repeater {
|
|
model: chatsModel
|
|
Loader {
|
|
active: false
|
|
sourceComponent: ChatMessages {
|
|
id: chatMessages
|
|
messageList: model.messages
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
StatusImageModal {
|
|
id: imagePopup
|
|
}
|
|
|
|
EmojiReactions {
|
|
id: reactionModel
|
|
}
|
|
|
|
MessageContextMenu {
|
|
id: messageContextMenu
|
|
}
|
|
|
|
Connections {
|
|
target: chatsModel
|
|
onActiveChannelChanged: {
|
|
chatInput.suggestions.hide();
|
|
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
|
|
populateSuggestions();
|
|
}
|
|
onMessagePushed: {
|
|
addSuggestionFromMessageList(messageIndex);
|
|
}
|
|
}
|
|
|
|
Rectangle {
|
|
id: inputArea
|
|
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
|
|
Layout.fillWidth: true
|
|
Layout.preferredWidth: parent.width
|
|
height: chatInput.height
|
|
Layout.preferredHeight: height
|
|
color: "transparent"
|
|
|
|
Connections {
|
|
target: chatsModel
|
|
onLoadingMessagesChanged:
|
|
if(value){
|
|
loadingMessagesIndicator.active = true
|
|
} else {
|
|
timer.setTimeout(function(){
|
|
loadingMessagesIndicator.active = false;
|
|
}, 5000);
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: loadingMessagesIndicator
|
|
active: chatsModel.loadingMessages
|
|
sourceComponent: loadingIndicator
|
|
anchors.right: parent.right
|
|
anchors.bottom: chatInput.top
|
|
anchors.rightMargin: Style.current.padding
|
|
anchors.bottomMargin: Style.current.padding
|
|
}
|
|
|
|
Component {
|
|
id: loadingIndicator
|
|
LoadingAnimation {}
|
|
}
|
|
|
|
StatusChatInput {
|
|
id: chatInput
|
|
visible: {
|
|
const community = chatsModel.communities.activeCommunity
|
|
if (chatsModel.activeChannel.chatType !== Constants.chatTypePrivateGroupChat &&
|
|
(!community.active ||
|
|
community.access === Constants.communityChatPublicAccess ||
|
|
community.admin || chatsModel.activeChannel.canPost)) {
|
|
return true
|
|
}
|
|
return chatsModel.activeChannel.isMember
|
|
}
|
|
enabled: !isBlocked
|
|
chatInputPlaceholder: isBlocked ?
|
|
//% "This user has been blocked."
|
|
qsTrId("this-user-has-been-blocked-") :
|
|
//% "Type a message."
|
|
qsTrId("type-a-message-")
|
|
anchors.bottom: parent.bottom
|
|
recentStickers: chatsModel.stickers.recent
|
|
stickerPackList: chatsModel.stickers.stickerPacks
|
|
chatType: chatsModel.activeChannel.chatType
|
|
onSendTransactionCommandButtonClicked: {
|
|
if (chatsModel.activeChannel.ensVerified) {
|
|
txModalLoader.sourceComponent = cmpSendTransactionWithEns
|
|
} else {
|
|
txModalLoader.sourceComponent = cmpSendTransactionNoEns
|
|
}
|
|
txModalLoader.item.open()
|
|
}
|
|
onReceiveTransactionCommandButtonClicked: {
|
|
txModalLoader.sourceComponent = cmpReceiveTransaction
|
|
txModalLoader.item.open()
|
|
}
|
|
onStickerSelected: {
|
|
chatsModel.stickers.send(hashId, packId)
|
|
}
|
|
onSendMessage: {
|
|
if (chatInput.fileUrls.length > 0){
|
|
chatsModel.sendImages(JSON.stringify(fileUrls));
|
|
}
|
|
let msg = chatsModel.plainText(Emoji.deparse(chatInput.textInput.text))
|
|
if (msg.length > 0){
|
|
msg = chatInput.interpretMessage(msg)
|
|
chatsModel.sendMessage(msg, chatInput.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, false);
|
|
if(event) event.accepted = true
|
|
sendMessageSound.stop();
|
|
Qt.callLater(sendMessageSound.play);
|
|
|
|
chatInput.textInput.clear();
|
|
chatInput.textInput.textFormat = TextEdit.PlainText;
|
|
chatInput.textInput.textFormat = TextEdit.RichText;
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
EmptyChat {}
|
|
|
|
Loader {
|
|
id: txModalLoader
|
|
function close() {
|
|
if (!this.item) {
|
|
return
|
|
}
|
|
this.item.close()
|
|
this.closed()
|
|
}
|
|
function closed() {
|
|
this.sourceComponent = undefined
|
|
}
|
|
}
|
|
Component {
|
|
id: cmpSendTransactionNoEns
|
|
ChatCommandModal {
|
|
id: sendTransactionNoEns
|
|
onClosed: {
|
|
txModalLoader.closed()
|
|
}
|
|
sendChatCommand: chatColumnLayout.requestAddressForTransaction
|
|
isRequested: false
|
|
//% "Send"
|
|
commandTitle: qsTrId("command-button-send")
|
|
title: commandTitle
|
|
//% "Request Address"
|
|
finalButtonLabel: qsTrId("request-address")
|
|
selectRecipient.selectedRecipient: {
|
|
return {
|
|
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
|
alias: chatsModel.activeChannel.alias,
|
|
identicon: chatsModel.activeChannel.identicon,
|
|
name: chatsModel.activeChannel.name,
|
|
type: RecipientSelector.Type.Contact
|
|
}
|
|
}
|
|
selectRecipient.selectedType: RecipientSelector.Type.Contact
|
|
selectRecipient.readOnly: true
|
|
}
|
|
}
|
|
Component {
|
|
id: cmpReceiveTransaction
|
|
ChatCommandModal {
|
|
id: receiveTransaction
|
|
onClosed: {
|
|
txModalLoader.closed()
|
|
}
|
|
sendChatCommand: chatColumnLayout.requestTransaction
|
|
isRequested: true
|
|
//% "Request"
|
|
commandTitle: qsTrId("wallet-request")
|
|
title: commandTitle
|
|
//% "Request"
|
|
finalButtonLabel: qsTrId("wallet-request")
|
|
selectRecipient.selectedRecipient: {
|
|
return {
|
|
address: Constants.zeroAddress, // Setting as zero address since we don't have the address yet
|
|
alias: chatsModel.activeChannel.alias,
|
|
identicon: chatsModel.activeChannel.identicon,
|
|
name: chatsModel.activeChannel.name,
|
|
type: RecipientSelector.Type.Contact
|
|
}
|
|
}
|
|
selectRecipient.selectedType: RecipientSelector.Type.Contact
|
|
selectRecipient.readOnly: true
|
|
}
|
|
}
|
|
Component {
|
|
id: cmpSendTransactionWithEns
|
|
SendModal {
|
|
id: sendTransactionWithEns
|
|
onOpened: {
|
|
walletModel.getGasPricePredictions()
|
|
}
|
|
onClosed: {
|
|
txModalLoader.closed()
|
|
}
|
|
selectRecipient.readOnly: true
|
|
selectRecipient.selectedRecipient: {
|
|
return {
|
|
address: "",
|
|
alias: chatsModel.activeChannel.alias,
|
|
identicon: chatsModel.activeChannel.identicon,
|
|
name: chatsModel.activeChannel.name,
|
|
type: RecipientSelector.Type.Contact,
|
|
ensVerified: true
|
|
}
|
|
}
|
|
selectRecipient.selectedType: RecipientSelector.Type.Contact
|
|
}
|
|
}
|
|
}
|
|
|
|
/*##^##
|
|
Designer {
|
|
D{i:0;formeditorColor:"#ffffff";height:770;width:800}
|
|
}
|
|
##^##*/
|