status-desktop/ui/app/AppLayouts/Chat/ChatColumn.qml

612 lines
23 KiB
QML
Raw Normal View History

2020-06-17 19:18:31 +00:00
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.0
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import "../../../shared"
import "../../../shared/status"
import "../../../imports"
2020-06-25 16:27:46 +00:00
import "./components"
2020-05-27 21:59:34 +00:00
import "./ChatColumn"
import "./ChatColumn/ChatComponents"
import "./data"
import "../Wallet"
StackLayout {
id: chatColumnLayout
2021-05-25 19:38:18 +00:00
property alias pinnedMessagesPopupComponent: pinnedMessagesPopupComponent
property int chatGroupsListViewCount: 0
2020-07-09 17:47:36 +00:00
property bool isReply: false
property bool isImage: false
property bool isExtendedInput: isReply || isImage
property bool isConnected: false
property string contactToRemove: ""
2021-06-30 18:46:26 +00:00
property bool showUsers: false
property var doNotShowAddToContactBannerToThose: ([])
property var onActivated: function () {
2021-05-18 17:17:04 +00:00
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
}
property string activeChatId: chatsModel.channelView.activeChannel.id
property bool isBlocked: profileModel.contacts.isContactBlocked(activeChatId)
property bool isContact: profileModel.contacts.isAdded(activeChatId)
property bool contactRequestReceived: profileModel.contacts.contactRequestReceived(activeChatId)
2021-05-05 18:19:55 +00:00
property string currentNotificationChatId
property string currentNotificationCommunityId
2021-05-18 17:17:04 +00:00
property alias input: chatInput
2021-05-25 19:38:18 +00:00
property string hoveredMessage
property string activeMessage
function setHovered(messageId, hovered) {
if (hovered) {
hoveredMessage = messageId
} else if (hoveredMessage === messageId) {
hoveredMessage = ""
}
}
function setMessageActive(messageId, active) {
if (active) {
activeMessage = messageId
} else if (activeMessage === messageId) {
activeMessage = ""
}
}
Component.onCompleted: {
2021-05-18 17:17:04 +00:00
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
}
2021-06-30 18:46:26 +00:00
property var currentTime: 0
Timer {
interval: 60000; // 1 min
running: true;
repeat: true
triggeredOnStart: true
2021-06-30 18:46:26 +00:00
onTriggered: {
chatColumnLayout.currentTime = Date.now()
}
}
Layout.fillHeight: true
Layout.fillWidth: true
2020-05-28 12:56:43 +00:00
Layout.minimumWidth: 300
currentIndex: chatsModel.channelView.activeChannelIndex > -1 && chatGroupsListViewCount > 0 ? 0 : 1
2021-05-25 19:38:18 +00:00
Component {
id: pinnedMessagesPopupComponent
PinnedMessagesPopup {
id: pinnedMessagesPopup
onClosed: destroy()
}
}
MessageContextMenu {
id: messageContextMenu
}
2021-02-03 21:38:05 +00:00
2021-06-02 20:02:28 +00:00
StatusImageModal {
id: imagePopup
}
property var idMap: ({})
property var suggestionsObj: ([])
2021-02-03 21:38:05 +00:00
function addSuggestionFromMessageList(i){
const contactAddr = chatsModel.messageView.messageList.getMessageData(i, "publicKey");
2021-02-03 21:38:05 +00:00
if(idMap[contactAddr]) return;
suggestionsObj.push({
alias: chatsModel.messageView.messageList.getMessageData(i, "alias"),
ensName: chatsModel.messageView.messageList.getMessageData(i, "ensName"),
address: contactAddr,
identicon: chatsModel.messageView.messageList.getMessageData(i, "identicon"),
localNickname: chatsModel.messageView.messageList.getMessageData(i, "localName")
})
2021-05-18 17:17:04 +00:00
chatInput.suggestionsList.append(suggestionsObj[suggestionsObj.length - 1]);
2021-02-03 21:38:05 +00:00
idMap[contactAddr] = true;
}
function populateSuggestions() {
2021-05-18 17:17:04 +00:00
chatInput.suggestionsList.clear()
const len = chatsModel.suggestionList.rowCount()
2021-02-03 21:38:05 +00:00
idMap = {}
for (let i = 0; i < len; i++) {
const contactAddr = chatsModel.suggestionList.rowData(i, "address");
if(idMap[contactAddr]) continue;
suggestionsObj.push({
alias: chatsModel.suggestionList.rowData(i, "alias"),
ensName: chatsModel.suggestionList.rowData(i, "ensName"),
address: contactAddr,
identicon: getProfileImage(contactAddr, false, false) || chatsModel.suggestionList.rowData(i, "identicon"),
localNickname: chatsModel.suggestionList.rowData(i, "localNickname")
})
2021-05-18 17:17:04 +00:00
chatInput.suggestionsList.append(suggestionsObj[suggestionsObj.length - 1]);
idMap[contactAddr] = true;
}
const len2 = chatsModel.messageView.messageList.rowCount();
for (let f = 0; f < len2; f++) {
addSuggestionFromMessageList(f);
}
}
function showReplyArea() {
isReply = true;
isImage = false;
let replyMessageIndex = chatsModel.messageView.messageList.getMessageIndex(SelectedMessage.messageId);
if (replyMessageIndex === -1) return;
let userName = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "userName")
let message = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "message")
let identicon = chatsModel.messageView.messageList.getMessageData(replyMessageIndex, "identicon")
2021-05-18 17:17:04 +00:00
chatInput.showReplyArea(userName, message, identicon)
}
function requestAddressForTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
amount = utilsModel.eth2Wei(amount.toString(), tokenDecimals)
chatsModel.transactions.requestAddress(activeChatId,
address,
amount,
tokenAddress)
txModalLoader.close()
}
function requestTransaction(address, amount, tokenAddress, tokenDecimals = 18) {
amount = utilsModel.eth2Wei(amount.toString(), tokenDecimals)
chatsModel.transactions.request(activeChatId,
address,
amount,
tokenAddress)
txModalLoader.close()
}
Connections {
2020-12-06 22:15:51 +00:00
target: profileModel.contacts
onContactListChanged: {
isBlocked = profileModel.contacts.isContactBlocked(activeChatId);
}
2021-05-05 13:29:33 +00:00
onContactBlocked: {
chatsModel.messageView.removeMessagesByUserId(publicKey)
2021-05-05 13:29:33 +00:00
}
}
Connections {
target: chatsModel.channelView
onActiveChannelChanged: {
chatsModel.messageView.hideLoadingIndicator()
SelectedMessage.reset();
chatColumn.isReply = false;
}
}
function clickOnNotification() {
// So far we're just showing this app as the top most window. Once we decide about the way
// how to notify the app what channle should be displayed within the app when user clicks
// notificaiton bubble this part should be updated accordingly.
//
// I removed part of this function which caused app crash.
applicationWindow.show()
applicationWindow.raise()
applicationWindow.requestActivate()
}
Connections {
target: systemTray
onMessageClicked: function () {
clickOnNotification()
}
}
Timer {
id: timer
}
function positionAtMessage(messageId) {
stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].item.scrollToMessage(messageId)
}
ColumnLayout {
2020-07-09 17:47:36 +00:00
spacing: 0
StatusChatToolBar {
id: topBar
Layout.fillWidth: true
property string chatId: chatsModel.channelView.activeChannel.id
property string profileImage: appMain.getProfileImage(chatId) || ""
chatInfoButton.title: Utils.removeStatusEns(chatsModel.channelView.activeChannel.name)
chatInfoButton.subTitle: {
switch (chatsModel.channelView.activeChannel.chatType) {
case Constants.chatTypeOneToOne:
return (profileModel.contacts.isAdded(topBar.chatId) ?
profileModel.contacts.contactRequestReceived(topBar.chatId) ?
qsTr("Contact") :
qsTr("Contact request pending") :
qsTr("Not a contact"))
case Constants.chatTypePublic:
return qsTr("Public chat")
case Constants.chatTypePrivateGroupChat:
let cnt = chatsModel.channelView.activeChannel.members.rowCount();
if(cnt > 1) return qsTr("%1 members").arg(cnt);
return qsTr("1 member");
case Constants.chatTypeCommunity:
default:
return ""
}
}
chatInfoButton.image.source: profileImage || chatsModel.channelView.activeChannel.identicon
chatInfoButton.image.isIdenticon: !!!profileImage && chatsModel.channelView.activeChannel.identicon
chatInfoButton.icon.color: chatsModel.channelView.activeChannel.color
chatInfoButton.type: chatsModel.channelView.activeChannel.chatType
chatInfoButton.pinnedMessagesCount: chatsModel.messageView.pinnedMessagesList.count
chatInfoButton.muted: chatsModel.channelView.activeChannel.muted
chatInfoButton.onPinnedMessagesCountClicked: openPopup(pinnedMessagesPopupComponent)
chatInfoButton.onUnmute: chatsModel.channelView.unmuteChatItem(chatsModel.channelView.activeChannel.id)
chatInfoButton.sensor.enabled: chatsModel.channelView.activeChannel.chatType !== Constants.chatTypePublic &&
chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeCommunity
chatInfoButton.onClicked: {
switch (chatsModel.channelView.activeChannel.chatType) {
case Constants.chatTypePrivateGroupChat:
openPopup(groupInfoPopupComponent, {channelType: GroupInfoPopup.ChannelType.ActiveChannel})
break;
case Constants.chatTypeOneToOne:
openProfilePopup(chatsModel.userNameOrAlias(chatsModel.channelView.activeChannel.id),
chatsModel.channelView.activeChannel.id, profileImage || chatsModel.channelView.activeChannel.identicon,
"", chatsModel.channelView.activeChannel.nickname)
break;
}
}
membersButton.visible: appSettings.showOnlineUsers && chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeOneToOne
notificationButton.visible: appSettings.isActivityCenterEnabled
notificationCount: chatsModel.activityNotificationList.unreadCount
onSearchButtonClicked: searchPopup.open()
SearchPopup {
id: searchPopup
}
onMembersButtonClicked: showUsers = !showUsers
onNotificationButtonClicked: activityCenter.open()
popupMenu: ChatContextMenu {
openHandler: {
chatItem = chatsModel.channelView.activeChannel
}
}
}
Rectangle {
Component.onCompleted: {
isConnected = chatsModel.isOnline
if(!isConnected){
connectedStatusRect.visible = true
}
}
id: connectedStatusRect
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
z: 60
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")
}
Connections {
target: chatsModel
onOnlineStatusChanged: {
if (connected == isConnected) return;
isConnected = connected;
if(isConnected){
timer.setTimeout(function(){
connectedStatusRect.visible = false;
}, 5000);
} else {
connectedStatusRect.visible = true;
}
}
}
}
AddToContactBanner {
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true
}
StackLayout {
id: stackLayoutChatMessages
Layout.fillWidth: true
Layout.fillHeight: true
clip: true
Repeater {
model: chatsModel.messageView
Loader {
active: false
sourceComponent: ChatMessages {
id: chatMessages
messageList: model.messages
2021-06-30 18:46:26 +00:00
currentTime: chatColumnLayout.currentTime
}
}
}
Connections {
target: chatsModel.channelView
onActiveChannelChanged: {
stackLayoutChatMessages.currentIndex = chatsModel.messageView.getMessageListIndex(chatsModel.channelView.activeChannelIndex)
2021-02-28 15:33:25 +00:00
if(stackLayoutChatMessages.currentIndex > -1 && !stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active){
stackLayoutChatMessages.children[stackLayoutChatMessages.currentIndex].active = true;
}
}
}
}
EmojiReactions {
id: reactionModel
}
Connections {
target: chatsModel.channelView
onActiveChannelChanged: {
2021-05-18 17:17:04 +00:00
chatInput.suggestions.hide();
chatInput.textInput.forceActiveFocus(Qt.MouseFocusReason)
populateSuggestions();
}
}
Connections {
target: chatsModel.messageView
onMessagePushed: {
2021-02-03 21:38:05 +00:00
addSuggestionFromMessageList(messageIndex);
}
}
Connections {
target: profileModel
onContactsChanged: {
populateSuggestions();
}
}
ChatRequestMessage {
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
Layout.fillWidth: true
Layout.bottomMargin: Style.current.bigPadding
}
2021-05-18 17:17:04 +00:00
Item {
2020-07-09 17:47:36 +00:00
id: inputArea
Layout.alignment: Qt.AlignHCenter | Qt.AlignBottom
Layout.fillWidth: true
Layout.preferredWidth: parent.width
2021-05-18 17:17:04 +00:00
height: chatInput.height
Layout.preferredHeight: height
2021-05-18 17:17:04 +00:00
Connections {
target: chatsModel.messageView
2021-05-18 17:17:04 +00:00
onLoadingMessagesChanged:
if(value){
loadingMessagesIndicator.active = true
} else {
timer.setTimeout(function(){
loadingMessagesIndicator.active = false;
}, 5000);
}
}
Loader {
id: loadingMessagesIndicator
active: chatsModel.messageView.loadingMessages
2021-05-18 17:17:04 +00:00
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: {
if (chatsModel.channelView.activeChannel.chatType === Constants.chatTypePrivateGroupChat) {
return chatsModel.channelView.activeChannel.isMember
2021-05-18 17:17:04 +00:00
}
if (chatsModel.channelView.activeChannel.chatType === Constants.chatTypeOneToOne) {
return isContact && contactRequestReceived
}
const community = chatsModel.communities.activeCommunity
2021-05-18 17:17:04 +00:00
return !community.active ||
community.access === Constants.communityChatPublicAccess ||
community.admin ||
chatsModel.channelView.activeChannel.canPost
2021-05-18 17:17:04 +00:00
}
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.channelView.activeChannel.chatType
2021-05-18 17:17:04 +00:00
onSendTransactionCommandButtonClicked: {
if (chatsModel.channelView.activeChannel.ensVerified) {
2021-05-18 17:17:04 +00:00
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.messageView.sendMessage(msg, chatInput.isReply ? SelectedMessage.messageId : "", Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType, false, JSON.stringify(suggestionsObj));
2021-05-18 17:17:04 +00:00
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.channelView.activeChannel.alias,
identicon: chatsModel.channelView.activeChannel.identicon,
name: chatsModel.channelView.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.channelView.activeChannel.alias,
identicon: chatsModel.channelView.activeChannel.identicon,
name: chatsModel.channelView.activeChannel.name,
type: RecipientSelector.Type.Contact
}
}
selectRecipient.selectedType: RecipientSelector.Type.Contact
selectRecipient.readOnly: true
}
}
Component {
id: cmpSendTransactionWithEns
SendModal {
id: sendTransactionWithEns
onOpened: {
refactor wallet views add getSettings methods to src/status fix issue with calling getSettings; document issue remove most direct references to libstatus; document some common issues remove most references to libstatus wallet add mailserver layer to status lib; remove references to libstatus mailservers remove libstatus accounts references move types out of libstatus; remove libstatus types references remove libstatus browser references refactor libstatus utils references remove more references to libstatus stickers remove references to libstatus constants from src/app remove more libstatus references from src/app refactor token_list usage of libstatus refactor stickers usage of libstatus refactor chat usage of libstatus remove libstatus references from the wallet view remove logic from ens manager view fix issue with import & namespace conflict remove unnecessary imports refactor provider view to not depend on libstatus refactor provider view refactor: move accounts specific code to its own section fix account selection move collectibles to their own module update references to wallet transactions refactor: move gas methods to their own file refactor: extract tokens into their own file refactor: extract ens to its own file refactor: extract dappbrowser code to its own file refactor: extract history related code to its own file refactor: extract balance to its own file refactor: extract utils to its own file clean up wallet imports fix: identicon for transaction commands Fixes #2533
2021-06-08 12:48:31 +00:00
walletModel.gasView.getGasPricePredictions()
}
onClosed: {
txModalLoader.closed()
}
selectRecipient.readOnly: true
selectRecipient.selectedRecipient: {
return {
address: "",
alias: chatsModel.channelView.activeChannel.alias,
identicon: chatsModel.channelView.activeChannel.identicon,
name: chatsModel.channelView.activeChannel.name,
type: RecipientSelector.Type.Contact,
ensVerified: true
}
}
selectRecipient.selectedType: RecipientSelector.Type.Contact
}
}
ActivityCenter {
id: activityCenter
height: chatColumnLayout.height - (topBar.height * 2) // TODO get screen size
y: topBar.height
}
}
/*##^##
Designer {
D{i:0;formeditorColor:"#ffffff";height:770;width:800}
}
##^##*/