import QtQuick 2.13 import QtQuick.Controls 2.13 import QtGraphicalEffects 1.13 import QtQml.Models 2.13 import QtQuick.Layouts 1.13 import utils 1.0 import shared 1.0 import shared.views 1.0 import shared.status 1.0 import shared.popups 1.0 import shared.controls 1.0 import shared.views.chat 1.0 import "../Chat/stores" import "../Chat/popups" import "stores" import "panels" ScrollView { id: root property RootStore store: RootStore { } Layout.fillWidth: true Layout.fillHeight: true contentHeight: chatLogView.contentHeight + 140 clip: true ScrollBar.horizontal.policy: ScrollBar.AlwaysOff property var rootStore property var messageStore property var onActivated: function () { store.setActiveChannelToTimeline() statusUpdateInput.textInput.forceActiveFocus(Qt.MouseFocusReason) } Component.onCompleted: { statusUpdateInput.textInput.forceActiveFocus(Qt.MouseFocusReason) } function openProfilePopup(userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam, parentPopup){ var popup = profilePopupComponent.createObject(root); if(parentPopup){ popup.parentPopup = parentPopup; } popup.openPopup(userProfile.pubKey !== fromAuthorParam, userNameParam, fromAuthorParam, identiconParam, textParam, nicknameParam); } StatusImageModal { id: imagePopup onClicked: { close() } } property Component profilePopupComponent: ProfilePopup { id: profilePopup store: root.store onClosed: { if(profilePopup.parentPopup){ profilePopup.parentPopup.close(); } destroy() } } Item { id: timelineContainer width: 624 anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom // TODO: Replace this with StatusQ component once it lives there. StatusChatInput { id: statusUpdateInput anchors.top: parent.top anchors.topMargin: 40 chatType: Constants.chatTypeStatusUpdate imageErrorMessageLocation: StatusChatInput.ImageErrorMessageLocation.Bottom z: 1 onSendMessage: { if (statusUpdateInput.fileUrls.length > 0){ statusUpdateInput.fileUrls.forEach(url => { root.store.sendImage(url); }) } var msg = root.store.getPlainTextFromRichText(Emoji.deparse(statusUpdateInput.textInput.text)) if (msg.length > 0){ msg = statusUpdateInput.interpretMessage(msg) root.store.sendMessage(msg, Utils.isOnlyEmoji(msg) ? Constants.emojiType : Constants.messageType); statusUpdateInput.textInput.text = ""; if(event) event.accepted = true sendMessageSound.stop() Qt.callLater(sendMessageSound.play); } } } EmptyTimelinePanel { id: emptyTimeline anchors.top: statusUpdateInput.bottom anchors.topMargin: 40 anchors.horizontalCenter: parent.horizontalCenter visible: chatLogView.count === 0 } ListView { id: chatLogView anchors.top: statusUpdateInput.bottom anchors.topMargin: Style.current.bigPadding anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom spacing: Style.current.halfPadding flickDeceleration: 10000 interactive: false model: messageListDelegate section.property: "sectionIdentifier" section.criteria: ViewSection.FullString Connections { target: root.store.chatsModelInst.messageView onMessagesLoaded: { Qt.callLater(chatLogView.positionViewAtBeginning) } } } Timer { id: ageUpdateTimer property int epoch: 0 running: true repeat: true interval: 60000 // 1 min onTriggered: epoch = epoch + 1 } DelegateModelGeneralized { id: messageListDelegate lessThan: [ function(left, right) { return left.clock > right.clock } ] model: root.store.chatsModelInst.messageView.messageList // TODO: Replace with StatusQ component once it lives there. delegate: MessageView { id: msgDelegate rootStore: root.rootStore messageStore: root.messageStore /////////TODO Remove fromAuthor: model.fromAuthor chatId: model.chatId userName: model.userName alias: model.alias localName: model.localName message: model.message plainText: model.plainText identicon: model.identicon isCurrentUser: model.isCurrentUser timestamp: model.timestamp sticker: model.sticker contentType: model.contentType outgoingStatus: model.outgoingStatus responseTo: model.responseTo authorCurrentMsg: msgDelegate.ListView.section authorPrevMsg: msgDelegate.ListView.previousSection imageClick: imagePopup.openPopup.bind(imagePopup) messageId: model.messageId emojiReactions: model.emojiReactions isStatusUpdate: true statusAgeEpoch: ageUpdateTimer.epoch // This is used in order to have access to the previous message and determine the timestamp // we can't rely on the index because the sequence of messages is not ordered on the nim side prevMessageIndex: { // This is used in order to have access to the previous message and determine the timestamp // we can't rely on the index because the sequence of messages is not ordered on the nim side if(msgDelegate.DelegateModel.itemsIndex > 0){ return messageListDelegate.items.get(msgDelegate.DelegateModel.itemsIndex - 1).model.index } return -1; } timeout: model.timeout messageContextMenu: msgCntxtMenu Component.onCompleted: { messageStore.fromAuthor = model.fromAuthor; messageStore.chatId = model.chatId; messageStore.userName = model.userName; messageStore.alias = model.alias; messageStore.localName = model.localName; messageStore.message = model.message; messageStore.plainText = model.plainText; messageStore.identicon = model.identicon; messageStore.isCurrentUser = model.isCurrentUser; messageStore.timestamp = model.timestamp; messageStore.sticker = model.sticker; messageStore.contentType = model.contentType; messageStore.outgoingStatus = model.outgoingStatus; messageStore.responseTo = model.responseTo; messageStore.authorCurrentMsg = msgDelegate.ListView.section; messageStore.authorPrevMsg = msgDelegate.ListView.previousSection; messageStore.imageClick = imagePopup.openPopup.bind(imagePopup); messageStore.messageId = model.messageId; messageStore.emojiReactions = model.emojiReactions; messageStore.isStatusUpdate = true; messageStore.statusAgeEpoch = ageUpdateTimer.epoch; // This is used in order to have access to the previous message and determine the timestamp // we can't rely on the index because the sequence of messages is not ordered on the nim side messageStore.prevMessageIndex = // This is used in order to have access to the previous message and determine the timestamp // we can't rely on the index because the sequence of messages is not ordered on the nim side (msgDelegate.DelegateModel.itemsIndex > 0) ? messageListDelegate.items.get(msgDelegate.DelegateModel.itemsIndex - 1).model.index : -1; messageStore.timeout = model.timeout; messageStore.messageContextMenu = msgCntxtMenu; } MessageContextMenuView { id: msgCntxtMenu store: root.store reactionModel: EmojiReactions { } } } } Loader { active: root.store.chatsModelInst.messageView.loadingMessages // TODO: replace with StatusLoadingIndicator sourceComponent: LoadingAnimation {} anchors.right: timelineContainer.right anchors.top: statusUpdateInput.bottom anchors.rightMargin: Style.current.padding anchors.topMargin: Style.current.padding } } }