feat(chat/messages): implement new messages marker

closes: #8572
iterates: #7488
This commit is contained in:
Patryk Osmaczko 2022-12-05 08:52:41 +01:00 committed by osmaczko
parent 27b8924c6d
commit 87674064d0
22 changed files with 407 additions and 130 deletions

View File

@ -20,6 +20,7 @@ QtObject:
position: int position: int
isUntrustworthy: bool isUntrustworthy: bool
isContact: bool isContact: bool
active: bool
proc delete*(self: ChatDetails) = proc delete*(self: ChatDetails) =
self.QObject.delete self.QObject.delete
@ -47,6 +48,7 @@ QtObject:
self.position = position self.position = position
self.isUntrustworthy = isUntrustworthy self.isUntrustworthy = isUntrustworthy
self.isContact = isContact self.isContact = isContact
self.active = false
proc getId(self: ChatDetails): string {.slot.} = proc getId(self: ChatDetails): string {.slot.} =
return self.id return self.id
@ -188,3 +190,14 @@ QtObject:
proc setIsUntrustworthy*(self: ChatDetails, value: bool) = # this is not a slot proc setIsUntrustworthy*(self: ChatDetails, value: bool) = # this is not a slot
self.isUntrustworthy = value self.isUntrustworthy = value
self.isUntrustworthyChanged() self.isUntrustworthyChanged()
proc activeChanged(self: ChatDetails) {.signal.}
proc isActive(self: ChatDetails): bool {.slot.} =
return self.active
QtProperty[bool] active:
read = isActive
notify = activeChanged
proc setActive*(self: ChatDetails, value: bool) =
self.active = value
self.activeChanged()

View File

@ -117,3 +117,9 @@ method downloadMessages*(self: AccessInterface, filePath: string) =
method onMutualContactChanged*(self: AccessInterface) {.base.} = method onMutualContactChanged*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onMadeActive*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onMadeInactive*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -121,6 +121,12 @@ proc init*(self: Controller) =
return return
self.delegate.toggleReactionFromOthers(args.messageId, args.emojiId, args.reactionId, args.reactionFrom) self.delegate.toggleReactionFromOthers(args.messageId, args.emojiId, args.reactionId, args.reactionFrom)
self.events.on(SIGNAL_MESSAGES_MARKED_AS_READ) do(e: Args):
let args = MessagesMarkedAsReadArgs(e)
if(self.chatId != args.chatId):
return
self.delegate.markAllMessagesRead()
self.events.on(SIGNAL_CONTACT_NICKNAME_CHANGED) do(e: Args): self.events.on(SIGNAL_CONTACT_NICKNAME_CHANGED) do(e: Args):
var args = ContactArgs(e) var args = ContactArgs(e)
self.delegate.updateContactDetails(args.contactId) self.delegate.updateContactDetails(args.contactId)
@ -265,6 +271,9 @@ proc setSearchedMessageId*(self: Controller, searchedMessageId: string) =
proc clearSearchedMessageId*(self: Controller) = proc clearSearchedMessageId*(self: Controller) =
self.setSearchedMessageId("") self.setSearchedMessageId("")
proc getFirstUnseenMessageId*(self: Controller): string =
self.messageService.getFirstUnseenMessageIdFor(self.chatId)
proc getLoadingMessagesPerPageFactor*(self: Controller): int = proc getLoadingMessagesPerPageFactor*(self: Controller): int =
return self.loadingMessagesPerPageFactor return self.loadingMessagesPerPageFactor

View File

@ -151,3 +151,13 @@ method onMailserverSynced*(self: AccessInterface, syncedFrom: int64) =
method resendChatMessage*(self: AccessInterface, messageId: string): string = method resendChatMessage*(self: AccessInterface, messageId: string): string =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method resetNewMessagesMarker*(self: AccessInterface) =
raise newException(ValueError, "No implementation available")
method scrollToNewMessagesMarker*(self: AccessInterface) =
raise newException(ValueError, "No implementation available")
method markAllMessagesRead*(self: AccessInterface) =
raise newException(ValueError, "No implementation available")

View File

@ -145,18 +145,18 @@ proc createChatIdentifierItem(self: Module): Item =
resendError = "" resendError = ""
) )
proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool = proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module) =
let searchedMessageId = self.controller.getSearchedMessageId() let searchedMessageId = self.controller.getSearchedMessageId()
if(searchedMessageId.len > 0): if(searchedMessageId.len > 0):
self.view.emitScrollMessagesUpSignal()
let index = self.view.model().findIndexForMessageId(searchedMessageId) let index = self.view.model().findIndexForMessageId(searchedMessageId)
self.controller.increaseLoadingMessagesPerPageFactor()
if(index != -1): if(index != -1):
self.controller.clearSearchedMessageId() self.controller.clearSearchedMessageId()
self.controller.resetLoadingMessagesPerPageFactor() self.controller.resetLoadingMessagesPerPageFactor()
self.view.emitScrollToMessageSignal(index) self.view.emitScrollToMessageSignal(index)
return true self.view.setMessageSearchOngoing(false)
return false else:
self.controller.increaseLoadingMessagesPerPageFactor()
self.loadMoreMessages()
method currentUserWalletContainsAddress(self: Module, address: string): bool = method currentUserWalletContainsAddress(self: Module, address: string): bool =
if (address.len == 0): if (address.len == 0):
@ -259,12 +259,12 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se
self.view.model().removeItem(CHAT_IDENTIFIER_MESSAGE_ID) self.view.model().removeItem(CHAT_IDENTIFIER_MESSAGE_ID)
# Add new loaded messages # Add new loaded messages
self.view.model().appendItems(viewItems) self.view.model().appendItems(viewItems)
self.view.model().resetNewMessagesMarker()
if(not self.view.getInitialMessagesLoaded()): # check if this loading was caused by the click on a messages from the app search result
self.view.initialMessagesAreLoaded() self.checkIfMessageLoadedAndScrollToItIfItIs()
# check if this loading was caused by the click on a messages from the app search result self.view.initialMessagesAreLoaded()
discard self.checkIfMessageLoadedAndScrollToItIfItIs()
method messageAdded*(self: Module, message: MessageDto) = method messageAdded*(self: Module, message: MessageDto) =
let sender = self.controller.getContactDetails(message.`from`) let sender = self.controller.getContactDetails(message.`from`)
@ -328,9 +328,12 @@ method messageAdded*(self: Module, message: MessageDto) =
self.view.model().insertItemBasedOnClock(item) self.view.model().insertItemBasedOnClock(item)
method removeNewMessagesMarker*(self: Module)
method onSendingMessageSuccess*(self: Module, message: MessageDto) = method onSendingMessageSuccess*(self: Module, message: MessageDto) =
self.messageAdded(message) self.messageAdded(message)
self.view.emitSendingMessageSuccessSignal() self.view.emitSendingMessageSuccessSignal()
self.removeNewMessagesMarker()
method onSendingMessageError*(self: Module) = method onSendingMessageError*(self: Module) =
self.view.emitSendingMessageErrorSignal() self.view.emitSendingMessageErrorSignal()
@ -524,9 +527,16 @@ method switchToMessage*(self: Module, messageId: string) =
self.controller.setSearchedMessageId(messageId) self.controller.setSearchedMessageId(messageId)
method scrollToMessage*(self: Module, messageId: string) = method scrollToMessage*(self: Module, messageId: string) =
if(messageId == ""):
return
let scrollAlreadyOngoing = len(self.controller.getSearchedMessageId()) > 0
if(scrollAlreadyOngoing):
return
self.view.setMessageSearchOngoing(true)
self.controller.setSearchedMessageId(messageId) self.controller.setSearchedMessageId(messageId)
if(not self.checkIfMessageLoadedAndScrollToItIfItIs()): self.checkIfMessageLoadedAndScrollToItIfItIs()
self.loadMoreMessages()
method requestMoreMessages*(self: Module) = method requestMoreMessages*(self: Module) =
self.controller.requestMoreMessages() self.controller.requestMoreMessages()
@ -611,3 +621,18 @@ method onMailserverSynced*(self: Module, syncedFrom: int64) =
method resendChatMessage*(self: Module, messageId: string): string = method resendChatMessage*(self: Module, messageId: string): string =
return self.controller.resendChatMessage(messageId) return self.controller.resendChatMessage(messageId)
method resetNewMessagesMarker*(self: Module) =
self.view.model().setFirstUnseenMessageId(self.controller.getFirstUnseenMessageId())
self.view.model().resetNewMessagesMarker()
method removeNewMessagesMarker*(self: Module) =
self.view.model().setFirstUnseenMessageId("")
self.view.model().resetNewMessagesMarker()
method scrollToNewMessagesMarker*(self: Module) =
self.scrollToMessage(self.view.model().getFirstUnseenMessageId())
method markAllMessagesRead*(self: Module) =
self.view.model().markAllAsSeen()

View File

@ -11,6 +11,7 @@ QtObject:
modelVariant: QVariant modelVariant: QVariant
initialMessagesLoaded: bool initialMessagesLoaded: bool
loadingHistoryMessagesInProgress: bool loadingHistoryMessagesInProgress: bool
messageSearchOngoing: bool
proc delete*(self: View) = proc delete*(self: View) =
self.model.delete self.model.delete
@ -24,6 +25,7 @@ QtObject:
result.model = newModel() result.model = newModel()
result.modelVariant = newQVariant(result.model) result.modelVariant = newQVariant(result.model)
result.initialMessagesLoaded = false result.initialMessagesLoaded = false
result.messageSearchOngoing = false
proc load*(self: View) = proc load*(self: View) =
self.delegate.viewDidLoad() self.delegate.viewDidLoad()
@ -96,7 +98,7 @@ QtObject:
proc initialMessagesLoadedChanged*(self: View) {.signal.} proc initialMessagesLoadedChanged*(self: View) {.signal.}
proc getInitialMessagesLoaded*(self: View): bool {.slot.} = proc getInitialMessagesLoaded(self: View): bool {.slot.} =
return self.initialMessagesLoaded return self.initialMessagesLoaded
QtProperty[bool] initialMessagesLoaded: QtProperty[bool] initialMessagesLoaded:
@ -164,10 +166,6 @@ QtObject:
proc emitScrollToMessageSignal*(self: View, messageIndex: int) = proc emitScrollToMessageSignal*(self: View, messageIndex: int) =
self.scrollToMessage(messageIndex) self.scrollToMessage(messageIndex)
proc scrollMessagesUp(self: View) {.signal.}
proc emitScrollMessagesUpSignal*(self: View) =
self.scrollMessagesUp()
proc requestMoreMessages(self: View) {.slot.} = proc requestMoreMessages(self: View) {.slot.} =
self.delegate.requestMoreMessages() self.delegate.requestMoreMessages()
@ -191,3 +189,19 @@ QtObject:
return return
self.model.itemSending(messageId) self.model.itemSending(messageId)
proc messageSearchOngoingChanged*(self: View) {.signal.}
proc getMessageSearchOngoing*(self: View): bool {.slot.} =
return self.messageSearchOngoing
QtProperty[bool] messageSearchOngoing:
read = getMessageSearchOngoing
notify = messageSearchOngoingChanged
proc setMessageSearchOngoing*(self: View, value: bool) =
self.messageSearchOngoing = value
self.messageSearchOngoingChanged()
proc addNewMessagesMarker*(self: View) {.slot.} =
if self.model.newMessagesMarkerIndex() == -1:
self.delegate.resetNewMessagesMarker()

View File

@ -365,4 +365,12 @@ method onMutualContactChanged*(self: Module) =
self.view.onMutualContactChanged(isContact) self.view.onMutualContactChanged(isContact)
method contactTrustStatusChanged*(self: Module, publicKey: string, isUntrustworthy: bool) = method contactTrustStatusChanged*(self: Module, publicKey: string, isUntrustworthy: bool) =
self.view.updateTrustStatus(isUntrustworthy) self.view.updateTrustStatus(isUntrustworthy)
method onMadeActive*(self: Module) =
self.messagesModule.resetNewMessagesMarker()
self.messagesModule.scrollToNewMessagesMarker()
self.view.setActive()
method onMadeInactive*(self: Module) =
self.view.setInactive()

View File

@ -95,6 +95,12 @@ QtObject:
proc setMuted*(self: View, muted: bool) = proc setMuted*(self: View, muted: bool) =
self.chatDetails.setMuted(muted) self.chatDetails.setMuted(muted)
proc setActive*(self: View) =
self.chatDetails.setActive(true)
proc setInactive*(self: View) =
self.chatDetails.setActive(false)
proc updateChatDetailsNameAndIcon*(self: View, name, icon: string) = proc updateChatDetailsNameAndIcon*(self: View, name, icon: string) =
self.chatDetails.setName(name) self.chatDetails.setName(name)
self.chatDetails.setIcon(icon) self.chatDetails.setIcon(icon)
@ -129,11 +135,9 @@ QtObject:
self.chatDetails.setEmoji(emoji) self.chatDetails.setEmoji(emoji)
self.chatDetails.setColor(color) self.chatDetails.setColor(color)
self.chatDetails.setIcon(icon) self.chatDetails.setIcon(icon)
self.chatDetailsChanged()
proc updateChatDetailsName*(self: View, name: string) = proc updateChatDetailsName*(self: View, name: string) =
self.chatDetails.setName(name) self.chatDetails.setName(name)
self.chatDetailsChanged()
proc onMutualContactChanged*(self: View, value: bool) = proc onMutualContactChanged*(self: View, value: bool) =
self.chatDetails.setIsMutualContact(value) self.chatDetails.setIsMutualContact(value)

View File

@ -350,11 +350,16 @@ method activeItemSubItemSet*(self: Module, itemId: string, subItemId: string) =
# update view maintained by this module # update view maintained by this module
self.view.chatsModel().setActiveItemSubItem(itemId, subItemId) self.view.chatsModel().setActiveItemSubItem(itemId, subItemId)
self.view.activeItemSubItemSet(item, subItem) self.view.activeItemSubItemSet(item, subItem)
# update child modules
for chatId, chatContentModule in self.chatContentModules:
if chatId == self.controller.getActiveChatId():
chatContentModule.onMadeActive()
else:
chatContentModule.onMadeInactive()
# notify parent module about active chat/channel # notify parent module about active chat/channel
self.delegate.onActiveChatChange(self.controller.getMySectionId(), self.controller.getActiveChatId()) self.delegate.onActiveChatChange(self.controller.getMySectionId(), self.controller.getActiveChatId())
# update notifications caused by setting active chat/channel
if singletonInstance.localAccountSensitiveSettings.getActiveSection() == self.controller.getMySectionId():
self.controller.markAllMessagesRead(self.controller.getActiveChatId())
method getModuleAsVariant*(self: Module): QVariant = method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant return self.viewVariant
@ -389,7 +394,6 @@ method onActiveSectionChange*(self: Module, sectionId: string) =
if(sectionId != self.controller.getMySectionId()): if(sectionId != self.controller.getMySectionId()):
return return
self.updateBadgeNotifications(self.controller.getActiveChatId(), hasUnreadMessages=false, unviewedMentionsCount=0)
self.delegate.onActiveChatChange(self.controller.getMySectionId(), self.controller.getActiveChatId()) self.delegate.onActiveChatChange(self.controller.getMySectionId(), self.controller.getActiveChatId())
method chatsModel*(self: Module): chats_model.Model = method chatsModel*(self: Module): chats_model.Model =
@ -729,12 +733,8 @@ method onNewMessagesReceived*(self: Module, sectionIdMsgBelongsTo: string, chatI
let chatDetails = self.controller.getChatDetails(chatIdMsgBelongsTo) let chatDetails = self.controller.getChatDetails(chatIdMsgBelongsTo)
# Badge notification # Badge notification
let messageBelongsToActiveSection = sectionIdMsgBelongsTo == self.controller.getMySectionId() and let showBadge = (not chatDetails.muted and unviewedMessagesCount > 0) or unviewedMentionsCount > 0
self.controller.getMySectionId() == self.delegate.getActiveSectionId() self.updateBadgeNotifications(chatIdMsgBelongsTo, showBadge, unviewedMentionsCount)
let messageBelongsToActiveChat = self.controller.getActiveChatId() == chatIdMsgBelongsTo
if(not messageBelongsToActiveSection or not messageBelongsToActiveChat):
let hasUnreadMessages = (not chatDetails.muted and unviewedMessagesCount > 0) or unviewedMentionsCount > 0
self.updateBadgeNotifications(chatIdMsgBelongsTo, hasUnreadMessages, unviewedMentionsCount)
if (chatDetails.muted): if (chatDetails.muted):
# No need to send a notification # No need to send a notification
@ -766,6 +766,10 @@ method onNewMessagesReceived*(self: Module, sectionIdMsgBelongsTo: string, chatI
else: else:
discard discard
let messageBelongsToActiveSection = sectionIdMsgBelongsTo == self.controller.getMySectionId() and
self.controller.getMySectionId() == self.delegate.getActiveSectionId()
let messageBelongsToActiveChat = self.controller.getActiveChatId() == chatIdMsgBelongsTo
singletonInstance.globalEvents.showMessageNotification(notificationTitle, plainText, sectionIdMsgBelongsTo, singletonInstance.globalEvents.showMessageNotification(notificationTitle, plainText, sectionIdMsgBelongsTo,
self.controller.isCommunity(), messageBelongsToActiveSection, chatIdMsgBelongsTo, messageBelongsToActiveChat, self.controller.isCommunity(), messageBelongsToActiveSection, chatIdMsgBelongsTo, messageBelongsToActiveChat,
message.id, notificationType.int, chatTypeMsgBelongsTo == ChatType.OneToOne, message.id, notificationType.int, chatTypeMsgBelongsTo == ChatType.OneToOne,

View File

@ -128,6 +128,38 @@ proc initItem*(
if attachment.contentType.contains("image"): if attachment.contentType.contains("image"):
result.messageAttachments.add(attachment.localUrl) result.messageAttachments.add(attachment.localUrl)
proc initNewMessagesMarkerItem*(timestamp: int64): Item =
return initItem(
id = "",
communityId = "",
responseToMessageWithId = "",
senderId = "",
senderDisplayName = "",
senderOptionalName = "",
senderIcon = "",
amISender = false,
senderIsAdded = false,
outgoingStatus = "",
text = "",
image = "",
messageContainsMentions = false,
seen = true,
timestamp = timestamp,
clock = 0,
ContentType.NewMessagesMarker,
messageType = -1,
contactRequestState = 0,
sticker = "",
stickerPack = -1,
links = @[],
transactionParameters = newTransactionParametersItem("","","","","","",-1,""),
mentionedUsersPks = @[],
senderTrustStatus = TrustStatus.Unknown,
senderEnsVerified = false,
discordMessage = DiscordMessage(),
resendError = ""
)
proc `$`*(self: Item): string = proc `$`*(self: Item): string =
result = fmt"""Item( result = fmt"""Item(
id: {$self.id}, id: {$self.id},
@ -246,6 +278,9 @@ proc sticker*(self: Item): string {.inline.} =
proc seen*(self: Item): bool {.inline.} = proc seen*(self: Item): bool {.inline.} =
self.seen self.seen
proc `seen=`*(self: Item, value: bool) {.inline.} =
self.seen = value
proc timestamp*(self: Item): int64 {.inline.} = proc timestamp*(self: Item): int64 {.inline.} =
self.timestamp self.timestamp

View File

@ -49,6 +49,7 @@ QtObject:
Model* = ref object of QAbstractListModel Model* = ref object of QAbstractListModel
items*: seq[Item] items*: seq[Item]
allKeys: seq[int] allKeys: seq[int]
firstUnseenMessageId: string
proc delete(self: Model) = proc delete(self: Model) =
self.items = @[] self.items = @[]
@ -66,6 +67,8 @@ QtObject:
for i in result.roleNames().keys: for i in result.roleNames().keys:
result.allKeys.add(i) result.allKeys.add(i)
result.firstUnseenMessageId = ""
proc `$`*(self: Model): string = proc `$`*(self: Model): string =
result = "MessageModel:\n" result = "MessageModel:\n"
for i in 0 ..< self.items.len: for i in 0 ..< self.items.len:
@ -529,3 +532,63 @@ QtObject:
if(ind == -1): if(ind == -1):
return return
self.updateItemAtIndex(ind) self.updateItemAtIndex(ind)
proc setFirstUnseenMessageId*(self: Model, messageId: string) =
self.firstUnseenMessageId = messageId
proc getFirstUnseenMessageId*(self: Model): string =
self.firstUnseenMessageId
proc newMessagesMarkerIndex*(self: Model): int =
result = -1
for i in countdown(self.items.len - 1, 0):
if self.items[i].contentType == ContentType.NewMessagesMarker:
return i
proc removeNewMessagesMarker(self: Model) =
let index = self.newMessagesMarkerIndex()
if index == -1:
return
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginRemoveRows(parentModelIndex, index, index)
self.items.delete(index)
self.endRemoveRows()
self.countChanged()
# TODO: handle messages removal
proc resetNewMessagesMarker*(self: Model) =
self.removeNewMessagesMarker()
let messageId = self.firstUnseenMessageId
if messageId == "":
return
let index = self.findIndexForMessageId(messageId)
if index == -1:
return
let position = index + 1
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, position, position)
self.items.insert(initNewMessagesMarkerItem(self.items[index].timestamp), position)
self.endInsertRows()
self.countChanged()
proc getNewMessagesCount*(self: Model): int {.slot.} =
max(0, self.newMessagesMarkerIndex())
QtProperty[int]newMessagesCount:
read = getNewMessagesCount
notify = countChanged
proc markAllAsSeen*(self: Model) =
for i in 0 ..< self.items.len:
let item = self.items[i]
if not item.seen:
item.seen = true
let index = self.createIndex(i, 0, nil)
self.dataChanged(index, index, @[ModelRole.Seen.int])

View File

@ -1,5 +1,6 @@
type type
ContentType* {.pure.} = enum ContentType* {.pure.} = enum
NewMessagesMarker = -3
FetchMoreMessagesButton = -2 FetchMoreMessagesButton = -2
ChatIdentifier = -1 ChatIdentifier = -1
Unknown = 0 Unknown = 0

View File

@ -657,6 +657,19 @@ QtObject:
self.threadpool.start(arg) self.threadpool.start(arg)
proc getFirstUnseenMessageIdFor*(self: Service, chatId: string): string =
try:
let response = status_go.firstUnseenMessageID(chatId)
if(not response.error.isNil):
error "error getFirstUnseenMessageIdFor: ", errDescription = response.error.message
result = response.result.getStr()
except Exception as e:
error "error: ", procName = "getFirstUnseenMessageIdFor", errName = e.name,
errDesription = e.msg
proc onAsyncGetLinkPreviewData*(self: Service, response: string) {.slot.} = proc onAsyncGetLinkPreviewData*(self: Service, response: string) {.slot.} =
self.events.emit(SIGNAL_MESSAGE_LINK_PREVIEW_DATA_LOADED, LinkPreviewDataArgs(response: response)) self.events.emit(SIGNAL_MESSAGE_LINK_PREVIEW_DATA_LOADED, LinkPreviewDataArgs(response: response))

View File

@ -67,3 +67,8 @@ proc editMessage*(messageId: string, contentType: int, msg: string): RpcResponse
proc resendChatMessage*(messageId: string): RpcResponse[JsonNode] {.raises: [Exception].} = proc resendChatMessage*(messageId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("reSendChatMessage".prefix, %* [messageId]) result = callPrivateRPC("reSendChatMessage".prefix, %* [messageId])
proc firstUnseenMessageID*(chatId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [chatId]
result = callPrivateRPC("firstUnseenMessageID".prefix, payload)

View File

@ -8,7 +8,9 @@ QtObject {
property var messagesModel property var messagesModel
property var chatSectionModule property var chatSectionModule
property var loadingHistoryMessagesInProgress: root.chatSectionModule? root.chatSectionModule.loadingHistoryMessagesInProgress : false readonly property bool loadingHistoryMessagesInProgress: root.chatSectionModule? root.chatSectionModule.loadingHistoryMessagesInProgress : false
readonly property int newMessagesCount: messagesModel ? messagesModel.newMessagesCount : 0
readonly property bool messageSearchOngoing: messageModule ? messageModule.messageSearchOngoing : false
onMessageModuleChanged: { onMessageModuleChanged: {
if(!messageModule) if(!messageModule)
@ -225,6 +227,12 @@ QtObject {
messageModule.leaveChat() messageModule.leaveChat()
} }
function addNewMessagesMarker() {
if(!messageModule)
return
messageModule.addNewMessagesMarker()
}
property bool playAnimation: { property bool playAnimation: {
if(!Global.applicationWindow.active) if(!Global.applicationWindow.active)
return false return false

View File

@ -42,39 +42,63 @@ Item {
property var messageContextMenu property var messageContextMenu
property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight
property int newMessages: 0
property int countOnStartUp: 0
signal openStickerPackPopup(string stickerPackId) signal openStickerPackPopup(string stickerPackId)
signal showReplyArea(string messageId, string author) signal showReplyArea(string messageId, string author)
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.chatDetails
function markAllMessagesReadIfMostRecentMessageIsInViewport() {
if (!isMostRecentMessageInViewport) {
return
}
if (chatDetails.active && chatDetails.hasUnreadMessages && !messageStore.messageSearchOngoing) {
chatContentModule.markAllMessagesRead()
}
}
onIsMostRecentMessageInViewportChanged: markAllMessagesReadIfMostRecentMessageIsInViewport()
}
Connections { Connections {
target: root.messageStore.messageModule target: root.messageStore.messageModule
function onMessageSuccessfullySent() { function onMessageSuccessfullySent() {
chatLogView.scrollToBottom(true) chatLogView.positionViewAtBeginning()
} }
function onSendingMessageFailed() { function onSendingMessageFailed() {
sendingMsgFailedPopup.open() sendingMsgFailedPopup.open()
} }
function onScrollMessagesUp() {
chatLogView.positionViewAtEnd()
}
function onScrollToMessage(messageIndex) { function onScrollToMessage(messageIndex) {
chatLogView.positionViewAtIndex(messageIndex, ListView.Center); chatLogView.positionViewAtIndex(messageIndex, ListView.Center)
chatLogView.itemAtIndex(messageIndex).startMessageFoundAnimation(); chatLogView.itemAtIndex(messageIndex).startMessageFoundAnimation()
} }
// Not Refactored Yet function onMessageSearchOngoingChanged() {
// onNewMessagePushed: { d.markAllMessagesReadIfMostRecentMessageIsInViewport()
// if (!chatLogView.scrollToBottom()) { }
// newMessages++ }
// }
// } Connections {
target: d.chatDetails
function onActiveChanged() {
d.markAllMessagesReadIfMostRecentMessageIsInViewport()
}
function onHasUnreadMessagesChanged() {
if (d.chatDetails.hasUnreadMessages && d.chatDetails.active && !d.isMostRecentMessageInViewport) {
// HACK: we call it later because messages model may not be yet propagated with unread messages when this signal is emitted
Qt.callLater(() => messageStore.addNewMessagesMarker())
}
}
} }
Item { Item {
@ -120,34 +144,15 @@ Item {
} }
} }
function scrollToBottom(force, caller) {
if (!force && !chatLogView.atYEnd) {
// User has scrolled up, we don't want to scroll back
return false
}
if (caller && caller !== chatLogView.itemAtIndex(chatLogView.count - 1)) {
// If we have a caller, only accept its request if it's the last message
return false
}
// Call this twice and with a timer since the first scroll to bottom might have happened before some stuff loads
// meaning that the scroll will not actually be at the bottom on switch
// Add a small delay because images, even though they say they say they are loaed, they aren't shown yet
Qt.callLater(chatLogView.positionViewAtBeginning)
timer.setTimeout(function() {
Qt.callLater(chatLogView.positionViewAtBeginning)
}, 100);
return true
}
model: messageStore.messagesModel model: messageStore.messagesModel
Component.onCompleted: chatLogView.scrollToBottom(true)
onContentYChanged: { onContentYChanged: {
scrollDownButton.visible = contentHeight - (scrollY + height) > 400 scrollDownButton.visible = contentHeight - (d.scrollY + height) > 400
if(scrollY < 500) messageStore.loadMoreMessages() if(d.scrollY < 500) messageStore.loadMoreMessages()
} }
onCountChanged: d.markAllMessagesReadIfMostRecentMessageIsInViewport()
ScrollBar.vertical: StatusScrollBar { ScrollBar.vertical: StatusScrollBar {
visible: chatLogView.visibleArea.heightRatio < 1 visible: chatLogView.visibleArea.heightRatio < 1
} }
@ -160,18 +165,6 @@ Item {
width: chatLogView.width width: chatLogView.width
} }
// Connections {
// id: contentHeightConnection
// enabled: true
// target: chatLogView
// onContentHeightChanged: {
// chatLogView.checkHeaderHeight()
// }
// onHeightChanged: {
// chatLogView.checkHeaderHeight()
// }
// }
Timer { Timer {
id: timer id: timer
} }
@ -181,12 +174,14 @@ Item {
readonly property int buttonPadding: 5 readonly property int buttonPadding: 5
visible: false
height: 32
width: nbMessages.width + arrowImage.width + 2 * Style.current.halfPadding + (nbMessages.visible ? scrollDownButton.buttonPadding : 0)
anchors.bottom: parent.bottom anchors.bottom: parent.bottom
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: Style.current.padding anchors.rightMargin: Style.current.padding
visible: false
height: 32
width: arrowImage.width + 2 * Style.current.halfPadding
background: Rectangle { background: Rectangle {
color: Style.current.buttonSecondaryColor color: Style.current.buttonSecondaryColor
border.width: 0 border.width: 0
@ -194,31 +189,16 @@ Item {
} }
onClicked: { onClicked: {
newMessages = 0
scrollDownButton.visible = false scrollDownButton.visible = false
chatLogView.scrollToBottom(true) chatLogView.positionViewAtBeginning()
}
StyledText {
id: nbMessages
visible: newMessages > 0
width: visible ? implicitWidth : 0
text: newMessages
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
color: Style.current.pillButtonTextColor
font.pixelSize: 15
anchors.leftMargin: Style.current.halfPadding
} }
StatusIcon { StatusIcon {
id: arrowImage id: arrowImage
anchors.centerIn: parent
width: 24 width: 24
height: 24 height: 24
anchors.verticalCenter: parent.verticalCenter
anchors.left: nbMessages.right
icon: "arrow-down" icon: "arrow-down"
anchors.leftMargin: nbMessages.visible ? scrollDownButton.buttonPadding : 0
color: Style.current.pillButtonTextColor color: Style.current.pillButtonTextColor
} }
@ -229,15 +209,6 @@ Item {
} }
} }
// Connections {
// Not Refactored Yet
// target: root.rootStore.chatsModelInst
// onAppReady: {
// chatLogView.scrollToBottom(true)
// }
// }
Connections { Connections {
target: chatLogView.model || null target: chatLogView.model || null
function onDataChanged(topLeft, bottomRight, roles) { function onDataChanged(topLeft, bottomRight, roles) {

View File

@ -205,6 +205,8 @@ Loader {
z: (typeof chatLogView === "undefined") ? 1 : (chatLogView.count - index) z: (typeof chatLogView === "undefined") ? 1 : (chatLogView.count - index)
asynchronous: true
sourceComponent: { sourceComponent: {
switch(messageContentType) { switch(messageContentType) {
case Constants.messageContentType.chatIdentifier: case Constants.messageContentType.chatIdentifier:
@ -215,6 +217,8 @@ Loader {
return privateGroupHeaderComponent return privateGroupHeaderComponent
case Constants.messageContentType.gapType: case Constants.messageContentType.gapType:
return gapComponent return gapComponent
case Constants.messageContentType.newMessagesMarker:
return newMessagesMarkerComponent
default: default:
return messageComponent return messageComponent
} }
@ -902,4 +906,13 @@ Loader {
} }
} }
} }
Component {
id: newMessagesMarkerComponent
NewMessagesMarker {
count: root.messageStore.newMessagesCount
timestamp: root.messageTimestamp
}
}
} }

View File

@ -0,0 +1,73 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
Item {
id: root
property double timestamp
property int count
implicitHeight: 28
RowLayout {
anchors {
left: parent.left
right: parent.right
leftMargin: 16
rightMargin: 16
verticalCenter: parent.verticalCenter
}
spacing: 8
Rectangle {
Layout.fillWidth: true
implicitHeight: 1
color: Theme.palette.primaryColor1
}
StatusBaseText {
text: qsTr("%n missed message(s) since %1", "", count).arg(new Date(timestamp).toLocaleDateString())
color: Theme.palette.primaryColor1
font.weight: Font.Bold
font.pixelSize: 13
}
Item {
Layout.fillWidth: true
Layout.fillHeight: true
RowLayout {
width: parent.width
anchors.verticalCenter: parent.verticalCenter
spacing: 0
Rectangle {
Layout.fillWidth: true
implicitHeight: 1
color: Theme.palette.primaryColor1
}
Rectangle {
implicitHeight: 16
implicitWidth: newLabel.width + 2*4
radius: 4
color: Theme.palette.primaryColor1
StatusBaseText {
id: newLabel
anchors.centerIn: parent
text: qsTr("NEW", "new message(s)")
color: Theme.palette.indirectColor1
font.weight: Font.DemiBold
font.pixelSize: 11
}
}
}
}
}
}

View File

@ -8,3 +8,4 @@ MessageView 1.0 MessageView.qml
NormalMessageView 1.0 NormalMessageView.qml NormalMessageView 1.0 NormalMessageView.qml
ProfileHeaderContextMenuView 1.0 ProfileHeaderContextMenuView.qml ProfileHeaderContextMenuView 1.0 ProfileHeaderContextMenuView.qml
TransactionBubbleView 1.0 TransactionBubbleView.qml TransactionBubbleView 1.0 TransactionBubbleView.qml
NewMessagesMarker 1.0 NewMessagesMarker.qml

View File

@ -348,6 +348,7 @@ QtObject {
} }
readonly property QtObject messageContentType: QtObject { readonly property QtObject messageContentType: QtObject {
readonly property int newMessagesMarker: -3
readonly property int fetchMoreMessagesButton: -2 readonly property int fetchMoreMessagesButton: -2
readonly property int chatIdentifier: -1 readonly property int chatIdentifier: -1
readonly property int unknownContentType: 0 readonly property int unknownContentType: 0