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
isUntrustworthy: bool
isContact: bool
active: bool
proc delete*(self: ChatDetails) =
self.QObject.delete
@ -47,6 +48,7 @@ QtObject:
self.position = position
self.isUntrustworthy = isUntrustworthy
self.isContact = isContact
self.active = false
proc getId(self: ChatDetails): string {.slot.} =
return self.id
@ -187,4 +189,15 @@ QtObject:
proc setIsUntrustworthy*(self: ChatDetails, value: bool) = # this is not a slot
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

@ -252,4 +252,4 @@ proc downloadMessages*(self: Controller, messages: seq[message_item.Item], fileP
"sender": message.senderDisplayName()
})
writeFile(url_toLocalFile(filePath), $data)
writeFile(url_toLocalFile(filePath), $data)

View File

@ -117,3 +117,9 @@ method downloadMessages*(self: AccessInterface, filePath: string) =
method onMutualContactChanged*(self: AccessInterface) {.base.} =
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
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):
var args = ContactArgs(e)
self.delegate.updateContactDetails(args.contactId)
@ -265,6 +271,9 @@ proc setSearchedMessageId*(self: Controller, searchedMessageId: string) =
proc clearSearchedMessageId*(self: Controller) =
self.setSearchedMessageId("")
proc getFirstUnseenMessageId*(self: Controller): string =
self.messageService.getFirstUnseenMessageIdFor(self.chatId)
proc getLoadingMessagesPerPageFactor*(self: Controller): int =
return self.loadingMessagesPerPageFactor

View File

@ -151,3 +151,13 @@ method onMailserverSynced*(self: AccessInterface, syncedFrom: int64) =
method resendChatMessage*(self: AccessInterface, messageId: string): string =
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 = ""
)
proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool =
proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module) =
let searchedMessageId = self.controller.getSearchedMessageId()
if(searchedMessageId.len > 0):
self.view.emitScrollMessagesUpSignal()
let index = self.view.model().findIndexForMessageId(searchedMessageId)
self.controller.increaseLoadingMessagesPerPageFactor()
if(index != -1):
self.controller.clearSearchedMessageId()
self.controller.resetLoadingMessagesPerPageFactor()
self.view.emitScrollToMessageSignal(index)
return true
return false
self.view.setMessageSearchOngoing(false)
else:
self.controller.increaseLoadingMessagesPerPageFactor()
self.loadMoreMessages()
method currentUserWalletContainsAddress(self: Module, address: string): bool =
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)
# Add new loaded messages
self.view.model().appendItems(viewItems)
self.view.model().resetNewMessagesMarker()
if(not self.view.getInitialMessagesLoaded()):
self.view.initialMessagesAreLoaded()
# check if this loading was caused by the click on a messages from the app search result
self.checkIfMessageLoadedAndScrollToItIfItIs()
# check if this loading was caused by the click on a messages from the app search result
discard self.checkIfMessageLoadedAndScrollToItIfItIs()
self.view.initialMessagesAreLoaded()
method messageAdded*(self: Module, message: MessageDto) =
let sender = self.controller.getContactDetails(message.`from`)
@ -282,12 +282,12 @@ method messageAdded*(self: Module, message: MessageDto) =
let index = self.view.model().findIndexForMessageId(message.replace)
if(index != -1):
self.view.model().removeItem(message.replace)
# https://github.com/status-im/status-desktop/issues/7632 will introduce deleteFroMe feature.
# Now we just skip deleted messages
if message.deleted or message.deletedForMe:
return
var item = initItem(
message.id,
message.communityId,
@ -328,9 +328,12 @@ method messageAdded*(self: Module, message: MessageDto) =
self.view.model().insertItemBasedOnClock(item)
method removeNewMessagesMarker*(self: Module)
method onSendingMessageSuccess*(self: Module, message: MessageDto) =
self.messageAdded(message)
self.view.emitSendingMessageSuccessSignal()
self.removeNewMessagesMarker()
method onSendingMessageError*(self: Module) =
self.view.emitSendingMessageErrorSignal()
@ -524,9 +527,16 @@ method switchToMessage*(self: Module, messageId: string) =
self.controller.setSearchedMessageId(messageId)
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)
if(not self.checkIfMessageLoadedAndScrollToItIfItIs()):
self.loadMoreMessages()
self.checkIfMessageLoadedAndScrollToItIfItIs()
method requestMoreMessages*(self: Module) =
self.controller.requestMoreMessages()
@ -611,3 +621,18 @@ method onMailserverSynced*(self: Module, syncedFrom: int64) =
method resendChatMessage*(self: Module, messageId: string): string =
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
initialMessagesLoaded: bool
loadingHistoryMessagesInProgress: bool
messageSearchOngoing: bool
proc delete*(self: View) =
self.model.delete
@ -24,6 +25,7 @@ QtObject:
result.model = newModel()
result.modelVariant = newQVariant(result.model)
result.initialMessagesLoaded = false
result.messageSearchOngoing = false
proc load*(self: View) =
self.delegate.viewDidLoad()
@ -64,7 +66,7 @@ QtObject:
let messageItem = self.delegate.getMessageById(messageId)
if messageItem == nil:
return ""
jsonObj = messageItem.toJsonNode();
if(jsonObj.isNil):
return ""
@ -75,7 +77,7 @@ QtObject:
proc getChatId*(self: View): string {.slot.} =
return self.delegate.getChatId()
proc getChatType*(self: View): int {.slot.} =
return self.delegate.getChatType()
@ -96,7 +98,7 @@ QtObject:
proc initialMessagesLoadedChanged*(self: View) {.signal.}
proc getInitialMessagesLoaded*(self: View): bool {.slot.} =
proc getInitialMessagesLoaded(self: View): bool {.slot.} =
return self.initialMessagesLoaded
QtProperty[bool] initialMessagesLoaded:
@ -164,10 +166,6 @@ QtObject:
proc emitScrollToMessageSignal*(self: View, messageIndex: int) =
self.scrollToMessage(messageIndex)
proc scrollMessagesUp(self: View) {.signal.}
proc emitScrollMessagesUpSignal*(self: View) =
self.scrollMessagesUp()
proc requestMoreMessages(self: View) {.slot.} =
self.delegate.requestMoreMessages()
@ -190,4 +188,20 @@ QtObject:
self.model.itemFailedResending(messageId, error)
return
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)
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) =
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) =
self.chatDetails.setName(name)
self.chatDetails.setIcon(icon)
@ -129,11 +135,9 @@ QtObject:
self.chatDetails.setEmoji(emoji)
self.chatDetails.setColor(color)
self.chatDetails.setIcon(icon)
self.chatDetailsChanged()
proc updateChatDetailsName*(self: View, name: string) =
self.chatDetails.setName(name)
self.chatDetailsChanged()
proc onMutualContactChanged*(self: View, value: bool) =
self.chatDetails.setIsMutualContact(value)

View File

@ -129,7 +129,7 @@ proc buildChatSectionUI(
mailserversService: mailservers_service.Service) =
var selectedItemId = ""
var selectedSubItemId = ""
# handle channels which don't belong to any category
for chatDto in channelGroup.chats:
if (chatDto.categoryId != ""):
@ -350,11 +350,16 @@ method activeItemSubItemSet*(self: Module, itemId: string, subItemId: string) =
# update view maintained by this module
self.view.chatsModel().setActiveItemSubItem(itemId, subItemId)
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
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 =
return self.viewVariant
@ -389,7 +394,6 @@ method onActiveSectionChange*(self: Module, sectionId: string) =
if(sectionId != self.controller.getMySectionId()):
return
self.updateBadgeNotifications(self.controller.getActiveChatId(), hasUnreadMessages=false, unviewedMentionsCount=0)
self.delegate.onActiveChatChange(self.controller.getMySectionId(), self.controller.getActiveChatId())
method chatsModel*(self: Module): chats_model.Model =
@ -498,7 +502,7 @@ method onCommunityCategoryCreated*(self: Module, cat: Category, chats: seq[ChatD
if (self.doesCatOrChatExist(cat.id)):
return
var categoryItem = initItem(cat.id, cat.name, icon="", color="", emoji="",
description="", ChatType.Unknown.int, amIChatAdmin=false, lastMessageTimestamp=(-1), hasUnreadMessages=false,
description="", ChatType.Unknown.int, amIChatAdmin=false, lastMessageTimestamp=(-1), hasUnreadMessages=false,
notificationsCount=0, muted=false, blocked=false, active=false, cat.position, cat.id)
var categoryChannels: seq[SubItem]
for chatDto in chats:
@ -611,7 +615,7 @@ method createOneToOneChat*(self: Module, communityID: string, chatId: string, en
self.controller.switchToOrCreateOneToOneChat(chatId, ensName)
return
# Adding this call here we have the same as we had before (didn't inspect what are all cases when this
# Adding this call here we have the same as we had before (didn't inspect what are all cases when this
# `createOneToOneChat` is called), but I am sure that after checking all cases and inspecting them, this can be improved.
self.switchToOrCreateOneToOneChat(chatId)
@ -715,7 +719,7 @@ method onContactDetailsUpdated*(self: Module, publicKey: string) =
let trustStatus = contactDetails.details.trustStatus
self.view.chatsModel().updateItemDetails(publicKey, chatName, chatImage, trustStatus)
method onNewMessagesReceived*(self: Module, sectionIdMsgBelongsTo: string, chatIdMsgBelongsTo: string,
method onNewMessagesReceived*(self: Module, sectionIdMsgBelongsTo: string, chatIdMsgBelongsTo: string,
chatTypeMsgBelongsTo: ChatType, lastMessageTimestamp: int, unviewedMessagesCount: int, unviewedMentionsCount: int,
message: MessageDto) =
self.updateLastMessageTimestamp(chatIdMsgBelongsTo, lastMessageTimestamp)
@ -729,12 +733,8 @@ method onNewMessagesReceived*(self: Module, sectionIdMsgBelongsTo: string, chatI
let chatDetails = self.controller.getChatDetails(chatIdMsgBelongsTo)
# Badge notification
let messageBelongsToActiveSection = sectionIdMsgBelongsTo == self.controller.getMySectionId() and
self.controller.getMySectionId() == self.delegate.getActiveSectionId()
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)
let showBadge = (not chatDetails.muted and unviewedMessagesCount > 0) or unviewedMentionsCount > 0
self.updateBadgeNotifications(chatIdMsgBelongsTo, showBadge, unviewedMentionsCount)
if (chatDetails.muted):
# No need to send a notification
@ -765,9 +765,13 @@ method onNewMessagesReceived*(self: Module, sectionIdMsgBelongsTo: string, chatI
notificationTitle.add(fmt" (#{chatDetails.name}, {categoryDetails.name})")
else:
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,
self.controller.isCommunity(), messageBelongsToActiveSection, chatIdMsgBelongsTo, messageBelongsToActiveChat,
self.controller.isCommunity(), messageBelongsToActiveSection, chatIdMsgBelongsTo, messageBelongsToActiveChat,
message.id, notificationType.int, chatTypeMsgBelongsTo == ChatType.OneToOne,
chatTypeMsgBelongsTo == ChatType.PrivateGroupChat)
@ -855,7 +859,7 @@ method editCommunity*(self: Module, name: string,
bannerJsonStr: string,
historyArchiveSupportEnabled: bool,
pinMessageAllMembersEnabled: bool) =
self.controller.editCommunity(name, description, introMessage, outroMessage, access, color, tags, logoJsonStr,
self.controller.editCommunity(name, description, introMessage, outroMessage, access, color, tags, logoJsonStr,
bannerJsonStr, historyArchiveSupportEnabled, pinMessageAllMembersEnabled)
method exportCommunity*(self: Module): string =
@ -919,7 +923,7 @@ method addChatIfDontExist*(self: Module,
self.onChatRenamed(chat.id, chat.name)
return
self.addNewChat(chat, belongsToCommunity, events, settingsService, nodeConfigurationService,
contactService, chatService, communityService, messageService, gifService, mailserversService,
contactService, chatService, communityService, messageService, gifService, mailserversService,
setChatAsActive)
method downloadMessages*(self: Module, chatId: string, filePath: string) =

View File

@ -128,6 +128,38 @@ proc initItem*(
if attachment.contentType.contains("image"):
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 =
result = fmt"""Item(
id: {$self.id},
@ -246,6 +278,9 @@ proc sticker*(self: Item): string {.inline.} =
proc seen*(self: Item): bool {.inline.} =
self.seen
proc `seen=`*(self: Item, value: bool) {.inline.} =
self.seen = value
proc timestamp*(self: Item): int64 {.inline.} =
self.timestamp

View File

@ -49,6 +49,7 @@ QtObject:
Model* = ref object of QAbstractListModel
items*: seq[Item]
allKeys: seq[int]
firstUnseenMessageId: string
proc delete(self: Model) =
self.items = @[]
@ -66,6 +67,8 @@ QtObject:
for i in result.roleNames().keys:
result.allKeys.add(i)
result.firstUnseenMessageId = ""
proc `$`*(self: Model): string =
result = "MessageModel:\n"
for i in 0 ..< self.items.len:
@ -335,7 +338,7 @@ QtObject:
if position + 1 < self.items.len:
self.updateItemAtIndex(position + 1)
self.countChanged()
proc replyDeleted*(self: Model, messageIndex: int) {.signal.}
proc updateMessagesWithResponseTo(self: Model, messageId: string) =
@ -440,13 +443,13 @@ QtObject:
if(index < 0 or index >= self.items.len):
return
self.items[index].toJsonNode()
proc updateContactInReplies(self: Model, messageId: string) =
for i in 0 ..< self.items.len:
if (self.items[i].responseToMessageWithId == messageId):
let index = self.createIndex(i, 0, nil)
self.dataChanged(index, index, @[ModelRole.ResponseToMessageWithId.int])
iterator modelContactUpdateIterator*(self: Model, contactId: string): Item =
for i in 0 ..< self.items.len:
yield self.items[i]
@ -529,3 +532,63 @@ QtObject:
if(ind == -1):
return
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

@ -326,4 +326,4 @@ var NODE_CONFIG* = %* {
"DataDir": DEFAULT_TORRENT_CONFIG_DATADIR,
"TorrentDir": DEFAULT_TORRENT_CONFIG_TORRENTDIR
}
}
}

View File

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

View File

@ -657,6 +657,19 @@ QtObject:
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.} =
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].} =
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 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: {
if(!messageModule)
@ -225,6 +227,12 @@ QtObject {
messageModule.leaveChat()
}
function addNewMessagesMarker() {
if(!messageModule)
return
messageModule.addNewMessagesMarker()
}
property bool playAnimation: {
if(!Global.applicationWindow.active)
return false

View File

@ -42,39 +42,63 @@ Item {
property var messageContextMenu
property real scrollY: chatLogView.visibleArea.yPosition * chatLogView.contentHeight
property int newMessages: 0
property int countOnStartUp: 0
signal openStickerPackPopup(string stickerPackId)
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 {
target: root.messageStore.messageModule
function onMessageSuccessfullySent() {
chatLogView.scrollToBottom(true)
chatLogView.positionViewAtBeginning()
}
function onSendingMessageFailed() {
sendingMsgFailedPopup.open()
}
function onScrollMessagesUp() {
chatLogView.positionViewAtEnd()
}
function onScrollToMessage(messageIndex) {
chatLogView.positionViewAtIndex(messageIndex, ListView.Center);
chatLogView.itemAtIndex(messageIndex).startMessageFoundAnimation();
chatLogView.positionViewAtIndex(messageIndex, ListView.Center)
chatLogView.itemAtIndex(messageIndex).startMessageFoundAnimation()
}
// Not Refactored Yet
// onNewMessagePushed: {
// if (!chatLogView.scrollToBottom()) {
// newMessages++
// }
// }
function onMessageSearchOngoingChanged() {
d.markAllMessagesReadIfMostRecentMessageIsInViewport()
}
}
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 {
@ -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
Component.onCompleted: chatLogView.scrollToBottom(true)
onContentYChanged: {
scrollDownButton.visible = contentHeight - (scrollY + height) > 400
if(scrollY < 500) messageStore.loadMoreMessages()
scrollDownButton.visible = contentHeight - (d.scrollY + height) > 400
if(d.scrollY < 500) messageStore.loadMoreMessages()
}
onCountChanged: d.markAllMessagesReadIfMostRecentMessageIsInViewport()
ScrollBar.vertical: StatusScrollBar {
visible: chatLogView.visibleArea.heightRatio < 1
}
@ -160,18 +165,6 @@ Item {
width: chatLogView.width
}
// Connections {
// id: contentHeightConnection
// enabled: true
// target: chatLogView
// onContentHeightChanged: {
// chatLogView.checkHeaderHeight()
// }
// onHeightChanged: {
// chatLogView.checkHeaderHeight()
// }
// }
Timer {
id: timer
}
@ -181,12 +174,14 @@ Item {
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.right: parent.right
anchors.rightMargin: Style.current.padding
visible: false
height: 32
width: arrowImage.width + 2 * Style.current.halfPadding
background: Rectangle {
color: Style.current.buttonSecondaryColor
border.width: 0
@ -194,31 +189,16 @@ Item {
}
onClicked: {
newMessages = 0
scrollDownButton.visible = false
chatLogView.scrollToBottom(true)
}
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
chatLogView.positionViewAtBeginning()
}
StatusIcon {
id: arrowImage
anchors.centerIn: parent
width: 24
height: 24
anchors.verticalCenter: parent.verticalCenter
anchors.left: nbMessages.right
icon: "arrow-down"
anchors.leftMargin: nbMessages.visible ? scrollDownButton.buttonPadding : 0
color: Style.current.pillButtonTextColor
}
@ -229,15 +209,6 @@ Item {
}
}
// Connections {
// Not Refactored Yet
// target: root.rootStore.chatsModelInst
// onAppReady: {
// chatLogView.scrollToBottom(true)
// }
// }
Connections {
target: chatLogView.model || null
function onDataChanged(topLeft, bottomRight, roles) {

View File

@ -205,6 +205,8 @@ Loader {
z: (typeof chatLogView === "undefined") ? 1 : (chatLogView.count - index)
asynchronous: true
sourceComponent: {
switch(messageContentType) {
case Constants.messageContentType.chatIdentifier:
@ -215,6 +217,8 @@ Loader {
return privateGroupHeaderComponent
case Constants.messageContentType.gapType:
return gapComponent
case Constants.messageContentType.newMessagesMarker:
return newMessagesMarkerComponent
default:
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
ProfileHeaderContextMenuView 1.0 ProfileHeaderContextMenuView.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 int newMessagesMarker: -3
readonly property int fetchMoreMessagesButton: -2
readonly property int chatIdentifier: -1
readonly property int unknownContentType: 0