feat(chat/messages): implement new messages marker
closes: #8572 iterates: #7488
This commit is contained in:
parent
27b8924c6d
commit
87674064d0
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -326,4 +326,4 @@ var NODE_CONFIG* = %* {
|
|||
"DataDir": DEFAULT_TORRENT_CONFIG_DATADIR,
|
||||
"TorrentDir": DEFAULT_TORRENT_CONFIG_TORRENTDIR
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
type
|
||||
ContentType* {.pure.} = enum
|
||||
NewMessagesMarker = -3
|
||||
FetchMoreMessagesButton = -2
|
||||
ChatIdentifier = -1
|
||||
Unknown = 0
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue