mirror of
https://github.com/status-im/status-desktop.git
synced 2025-01-10 14:26:34 +00:00
0c2674e7cb
After this change there is not need to pass sendModal instance from AppMain to other parts of app. Then SendModal should be launched simply by calling Global.openSendModal(....)
468 lines
16 KiB
QML
468 lines
16 KiB
QML
import QtQuick 2.15
|
|
import QtQml 2.15
|
|
import QtQuick.Controls 2.15
|
|
import QtQuick.Layouts 1.15
|
|
import QtQuick.Dialogs 1.3
|
|
|
|
import StatusQ.Components 0.1
|
|
import StatusQ.Controls 0.1
|
|
import StatusQ.Core 0.1
|
|
import StatusQ.Core.Backpressure 0.1
|
|
import StatusQ.Core.Theme 0.1
|
|
|
|
import utils 1.0
|
|
import shared 1.0
|
|
import shared.stores 1.0 as SharedStores
|
|
import shared.views 1.0
|
|
import shared.panels 1.0
|
|
import shared.popups 1.0
|
|
import shared.status 1.0
|
|
import shared.controls 1.0
|
|
import shared.views.chat 1.0
|
|
|
|
import AppLayouts.Chat.stores 1.0
|
|
import AppLayouts.Profile.stores 1.0
|
|
|
|
import "../controls"
|
|
import "../panels"
|
|
|
|
Item {
|
|
id: root
|
|
|
|
property var chatContentModule
|
|
|
|
property SharedStores.RootStore sharedRootStore
|
|
property SharedStores.UtilsStore utilsStore
|
|
|
|
property RootStore rootStore
|
|
property MessageStore messageStore
|
|
property UsersStore usersStore
|
|
property ContactsStore contactsStore
|
|
property string channelEmoji
|
|
property var formatBalance
|
|
|
|
property var emojiPopup
|
|
property var stickersPopup
|
|
property bool areTestNetworksEnabled
|
|
|
|
property string chatId: ""
|
|
property bool stickersLoaded: false
|
|
property alias chatLogView: chatLogView
|
|
property bool isContactBlocked: false
|
|
property bool isChatBlocked: false
|
|
property bool isOneToOne: false
|
|
|
|
property bool sendViaPersonalChatEnabled
|
|
|
|
signal openStickerPackPopup(string stickerPackId)
|
|
signal showReplyArea(string messageId, string author)
|
|
signal editModeChanged(bool editModeOn)
|
|
|
|
QtObject {
|
|
id: d
|
|
|
|
readonly property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight
|
|
readonly property bool isMostRecentMessageInViewport: chatLogView.visibleArea.yPosition >= 0.999 - chatLogView.visibleArea.heightRatio
|
|
readonly property var chatDetails: chatContentModule && chatContentModule.chatDetails || null
|
|
readonly property bool keepUnread: messageStore.keepUnread
|
|
|
|
readonly property var loadMoreMessagesIfScrollBelowThreshold: Backpressure.oneInTimeQueued(root, 100, function() {
|
|
if(scrollY < 1000) messageStore.loadMoreMessages()
|
|
})
|
|
|
|
function setKeepUnread(flag: bool) {
|
|
root.messageStore.setKeepUnread(flag)
|
|
}
|
|
|
|
function markAllMessagesReadIfMostRecentMessageIsInViewport() {
|
|
if (Qt.application.state != Qt.ApplicationActive || !isMostRecentMessageInViewport || !chatLogView.visible || keepUnread) {
|
|
return
|
|
}
|
|
|
|
if (chatDetails && chatDetails.active && (chatDetails.hasUnreadMessages || chatDetails.highlight) && !messageStore.loading) {
|
|
chatContentModule.markAllMessagesRead()
|
|
}
|
|
}
|
|
|
|
function goToMessage(messageIndex) {
|
|
chatLogView.currentIndex = -1
|
|
chatLogView.currentIndex = messageIndex
|
|
}
|
|
|
|
onIsMostRecentMessageInViewportChanged: markAllMessagesReadIfMostRecentMessageIsInViewport()
|
|
}
|
|
|
|
Connections {
|
|
target: Qt.application
|
|
onStateChanged: {
|
|
if (Qt.application.state == Qt.ApplicationActive) {
|
|
d.markAllMessagesReadIfMostRecentMessageIsInViewport()
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.messageStore.messageModule
|
|
|
|
function onMessageSuccessfullySent() {
|
|
chatLogView.positionViewAtBeginning()
|
|
}
|
|
|
|
function onSendingMessageFailed(error) {
|
|
sendingMsgFailedPopup.error = error
|
|
sendingMsgFailedPopup.open()
|
|
}
|
|
|
|
function onScrollToMessage(messageIndex) {
|
|
d.goToMessage(messageIndex)
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.messageStore
|
|
|
|
function onMessageSearchOngoingChanged() {
|
|
d.markAllMessagesReadIfMostRecentMessageIsInViewport()
|
|
}
|
|
|
|
function onLoadingChanged() {
|
|
d.markAllMessagesReadIfMostRecentMessageIsInViewport()
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: !!d.chatDetails ? d.chatDetails : null
|
|
|
|
function onActiveChanged() {
|
|
d.setKeepUnread(false)
|
|
d.markAllMessagesReadIfMostRecentMessageIsInViewport()
|
|
d.loadMoreMessagesIfScrollBelowThreshold()
|
|
}
|
|
|
|
function onHasUnreadMessagesChanged() {
|
|
if (!d.chatDetails.hasUnreadMessages) {
|
|
return
|
|
}
|
|
|
|
// HACK: we call `addNewMessagesMarker` later because messages model
|
|
// may not be yet propagated with unread messages when this signal is emitted
|
|
if (chatLogView.visible && (Qt.application.state != Qt.ApplicationActive || !d.isMostRecentMessageInViewport)) {
|
|
Qt.callLater(() => messageStore.addNewMessagesMarker())
|
|
}
|
|
}
|
|
}
|
|
|
|
Connections {
|
|
target: root.rootStore
|
|
enabled: d.chatDetails && d.chatDetails.active
|
|
|
|
function onLoadingHistoryMessagesInProgressChanged() {
|
|
if(!root.rootStore.loadingHistoryMessagesInProgress) {
|
|
d.loadMoreMessagesIfScrollBelowThreshold()
|
|
}
|
|
}
|
|
}
|
|
|
|
Item {
|
|
id: loadingMessagesIndicator
|
|
visible: root.rootStore.loadingHistoryMessagesInProgress
|
|
anchors.top: parent.top
|
|
anchors.left: parent.left
|
|
height: visible? 20 : 0
|
|
width: parent.width
|
|
|
|
Loader {
|
|
active: root.rootStore.loadingHistoryMessagesInProgress
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
anchors.verticalCenter: parent.verticalCenter
|
|
sourceComponent: Component {
|
|
LoadingAnimation {
|
|
width: 18
|
|
height: 18
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Loader {
|
|
id: loadingMessagesView
|
|
|
|
anchors.top: loadingMessagesIndicator.bottom
|
|
anchors.bottom: parent.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
|
|
active: messageStore.loading
|
|
visible: active
|
|
sourceComponent: MessagesLoadingView {
|
|
anchors.margins: 16
|
|
anchors.fill: parent
|
|
}
|
|
}
|
|
|
|
StatusListView {
|
|
id: chatLogView
|
|
visible: !loadingMessagesView.visible
|
|
objectName: "chatLogView"
|
|
anchors.top: loadingMessagesIndicator.bottom
|
|
anchors.bottom: parent.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
spacing: 0
|
|
verticalLayoutDirection: ListView.BottomToTop
|
|
cacheBuffer: height > 0 ? height * 2 : 0 // cache 2 screens worth of items
|
|
|
|
highlightRangeMode: ListView.ApplyRange
|
|
highlightMoveDuration: 200
|
|
preferredHighlightBegin: 0
|
|
preferredHighlightEnd: chatLogView.height / 2
|
|
|
|
Binding on flickDeceleration {
|
|
when: localAppSettings.isCustomMouseScrollingEnabled
|
|
value: localAppSettings.scrollDeceleration
|
|
restoreMode: Binding.RestoreBindingOrValue
|
|
}
|
|
|
|
Binding on maximumFlickVelocity {
|
|
when: localAppSettings.isCustomMouseScrollingEnabled
|
|
value: localAppSettings.scrollVelocity
|
|
restoreMode: Binding.RestoreBindingOrValue
|
|
}
|
|
|
|
model: messageStore.messagesModel
|
|
|
|
onContentYChanged: d.loadMoreMessagesIfScrollBelowThreshold()
|
|
|
|
onCountChanged: {
|
|
d.markAllMessagesReadIfMostRecentMessageIsInViewport()
|
|
|
|
// after inilial messages are loaded
|
|
// load as much messages as the view requires
|
|
if (!messageStore.loading) {
|
|
d.loadMoreMessagesIfScrollBelowThreshold()
|
|
}
|
|
}
|
|
|
|
onVisibleChanged: d.markAllMessagesReadIfMostRecentMessageIsInViewport()
|
|
|
|
onCurrentItemChanged: {
|
|
if(currentItem && currentIndex > 0) {
|
|
currentItem.startMessageFoundAnimation()
|
|
}
|
|
}
|
|
|
|
ScrollBar.vertical: StatusScrollBar {
|
|
visible: chatLogView.visibleArea.heightRatio < 1
|
|
}
|
|
|
|
ChatAnchorButtonsPanel {
|
|
anchors.bottom: parent.bottom
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: Theme.padding
|
|
|
|
mentionsCount: d.chatDetails ? d.chatDetails.notificationCount : 0
|
|
recentMessagesButtonVisible: {
|
|
chatLogView.contentY // trigger binding on contentY change
|
|
return chatLogView.contentHeight - (d.scrollY + chatLogView.height) > 400
|
|
}
|
|
|
|
onRecentMessagesButtonClicked: chatLogView.positionViewAtBeginning()
|
|
onMentionsButtonClicked: {
|
|
let id = messageStore.firstUnseenMentionMessageId()
|
|
if (id !== "") {
|
|
messageStore.jumpToMessage(id)
|
|
chatContentModule.markMessageRead(id)
|
|
}
|
|
}
|
|
}
|
|
|
|
delegate: MessageView {
|
|
id: msgDelegate
|
|
|
|
width: ListView.view.width
|
|
|
|
objectName: "chatMessageViewDelegate"
|
|
|
|
sharedRootStore: root.sharedRootStore
|
|
utilsStore: root.utilsStore
|
|
rootStore: root.rootStore
|
|
messageStore: root.messageStore
|
|
usersStore: root.usersStore
|
|
contactsStore: root.contactsStore
|
|
channelEmoji: root.channelEmoji
|
|
emojiPopup: root.emojiPopup
|
|
stickersPopup: root.stickersPopup
|
|
chatLogView: ListView.view
|
|
chatContentModule: root.chatContentModule
|
|
formatBalance: root.formatBalance
|
|
|
|
isChatBlocked: root.isChatBlocked
|
|
|
|
sendViaPersonalChatEnabled: root.sendViaPersonalChatEnabled
|
|
areTestNetworksEnabled: root.areTestNetworksEnabled
|
|
|
|
chatId: root.chatId
|
|
messageId: model.id
|
|
communityId: model.communityId
|
|
responseToMessageWithId: model.responseToMessageWithId
|
|
senderId: model.senderId
|
|
senderDisplayName: model.senderDisplayName
|
|
senderOptionalName: model.senderOptionalName
|
|
senderIsEnsVerified: model.senderEnsVerified
|
|
senderIcon: model.senderIcon
|
|
senderColorHash: model.senderColorHash
|
|
senderIsAdded: model.senderIsAdded
|
|
senderTrustStatus: model.senderTrustStatus
|
|
amISender: model.amISender
|
|
messageText: model.messageText
|
|
unparsedText: model.unparsedText
|
|
messageImage: model.messageImage
|
|
album: model.albumMessageImages.split(" ")
|
|
albumCount: model.albumImagesCount
|
|
messageTimestamp: model.timestamp
|
|
messageOutgoingStatus: model.outgoingStatus
|
|
resendError: model.resendError
|
|
messageContentType: model.contentType
|
|
pinnedMessage: model.pinned
|
|
messagePinnedBy: model.pinnedBy
|
|
reactionsModel: model.reactions
|
|
emojiReactionsModel: model.emojiReactionsModel
|
|
sticker: model.sticker
|
|
stickerPack: model.stickerPack
|
|
editModeOn: model.editMode
|
|
onEditModeOnChanged: root.editModeChanged(editModeOn)
|
|
isEdited: model.isEdited
|
|
deleted: model.deleted
|
|
deletedBy: model.deletedBy
|
|
deletedByContactDisplayName: model.deletedByContactDisplayName
|
|
deletedByContactIcon: model.deletedByContactIcon
|
|
deletedByContactColorHash: model.deletedByContactColorHash
|
|
linkPreviewModel: model.linkPreviewModel
|
|
links: model.links
|
|
paymentRequestModel: model.paymentRequestModel
|
|
messageAttachments: model.messageAttachments
|
|
transactionParams: model.transactionParameters
|
|
hasMention: model.mentioned
|
|
quotedMessageText: model.quotedMessageParsedText
|
|
quotedMessageFrom: model.quotedMessageFrom
|
|
quotedMessageContentType: model.quotedMessageContentType
|
|
quotedMessageDeleted: model.quotedMessageDeleted
|
|
quotedMessageAuthorDetailsName: model.quotedMessageAuthorName
|
|
quotedMessageAuthorDetailsDisplayName: model.quotedMessageAuthorDisplayName
|
|
quotedMessageAuthorDetailsThumbnailImage: model.quotedMessageAuthorThumbnailImage
|
|
quotedMessageAuthorDetailsEnsVerified: model.quotedMessageAuthorEnsVerified
|
|
quotedMessageAuthorDetailsIsContact: model.quotedMessageAuthorIsContact
|
|
quotedMessageAuthorDetailsColorHash: model.quotedMessageAuthorColorHash
|
|
quotedMessageAlbumMessageImages: model.quotedMessageAlbumMessageImages.split(" ")
|
|
quotedMessageAlbumImagesCount: model.quotedMessageAlbumImagesCount
|
|
bridgeName: model.bridgeName
|
|
|
|
gapFrom: model.gapFrom
|
|
gapTo: model.gapTo
|
|
|
|
// This is possible since we have all data loaded before we load qml.
|
|
// When we fetch messages to fulfill a gap we have to set them at once.
|
|
// Also one important thing here is that messages are set in descending order
|
|
// in terms of `timestamp` of a message, that means a message with the most
|
|
// recent time is added at index 0.
|
|
prevMessageIndex: model.prevMsgIndex
|
|
prevMessageTimestamp: model.prevMsgTimestamp
|
|
prevMessageSenderId: model.prevMsgSenderId
|
|
prevMessageContentType: model.prevMsgContentType
|
|
prevMessageDeleted: model.prevMsgDeleted
|
|
nextMessageIndex: model.nextMsgIndex
|
|
nextMessageTimestamp: model.nextMsgTimestamp
|
|
|
|
onOpenStickerPackPopup: {
|
|
root.openStickerPackPopup(stickerPackId);
|
|
}
|
|
|
|
onShowReplyArea: {
|
|
root.showReplyArea(messageId, author)
|
|
}
|
|
|
|
stickersLoaded: root.stickersLoaded
|
|
|
|
onSendViaPersonalChatRequested: {
|
|
Global.sendToRecipientRequested(recipientAddress)
|
|
}
|
|
|
|
onVisibleChanged: {
|
|
if(!visible && model.editMode)
|
|
messageStore.setEditModeOff(model.id)
|
|
}
|
|
}
|
|
header: {
|
|
if (!root.isContactBlocked && root.isOneToOne && root.rootStore.oneToOneChatContact) {
|
|
switch (root.rootStore.oneToOneChatContact.contactRequestState) {
|
|
case Constants.ContactRequestState.None: // no break
|
|
case Constants.ContactRequestState.Dismissed:
|
|
return sendContactRequestComponent
|
|
case Constants.ContactRequestState.Received:
|
|
return acceptOrDeclineContactRequestComponent
|
|
case Constants.ContactRequestState.Sent:
|
|
return pendingContactRequestComponent
|
|
default:
|
|
break
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
onHeaderChanged: chatLogView.positionViewAtBeginning()
|
|
}
|
|
|
|
MessageDialog {
|
|
property string error
|
|
|
|
id: sendingMsgFailedPopup
|
|
standardButtons: StandardButton.Ok
|
|
text: qsTr("Failed to send message.\n" + error)
|
|
icon: StandardIcon.Critical
|
|
}
|
|
|
|
Component {
|
|
id: sendContactRequestComponent
|
|
|
|
StatusButton {
|
|
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
|
text: qsTr("Send Contact Request")
|
|
onClicked: {
|
|
Global.openContactRequestPopup(root.chatId, null)
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: acceptOrDeclineContactRequestComponent
|
|
|
|
RowLayout {
|
|
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
|
|
|
StatusButton {
|
|
text: qsTr("Reject Contact Request")
|
|
type: StatusBaseButton.Type.Danger
|
|
onClicked: {
|
|
root.contactsStore.dismissContactRequest(root.chatId, "")
|
|
}
|
|
}
|
|
|
|
StatusButton {
|
|
text: qsTr("Accept Contact Request")
|
|
onClicked: {
|
|
root.contactsStore.acceptContactRequest(root.chatId, "")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: pendingContactRequestComponent
|
|
|
|
StatusButton {
|
|
anchors.horizontalCenter: parent ? parent.horizontalCenter : undefined
|
|
enabled: false
|
|
text: qsTr("Contact Request Pending...")
|
|
}
|
|
}
|
|
}
|