feat(@desktop/chats): Keep only last 5 chats/channels in the memory

This commit is contained in:
mprakhov 2023-03-23 10:38:09 +01:00 committed by Mykhailo Prakhov
parent c8ef6bcd9c
commit 21d2c00b40
13 changed files with 148 additions and 32 deletions

View File

@ -6,6 +6,7 @@ import local_app_settings
import user_profile
import utils
import global_events
import loader_deactivator
export local_account_settings
export local_account_sensitive_settings
@ -13,6 +14,7 @@ export local_app_settings
export user_profile
export utils
export global_events
export loader_deactivator
type
GlobalSingleton = object
@ -63,9 +65,16 @@ proc globalEvents*(self: GlobalSingleton): GlobalEvents =
globalEvents = newGlobalEvents()
return globalEvents
proc loaderDeactivator*(self: GlobalSingleton): LoaderDeactivator =
var loaderDeactivator {.global.}: LoaderDeactivator
if (loaderDeactivator.isNil):
loaderDeactivator = newLoaderDeactivator()
return loaderDeactivator
proc delete*(self: GlobalSingleton) =
self.engine.delete()
self.localAccountSettings.delete()
self.localAccountSensitiveSettings.delete()
self.localAppSettings.delete()
self.userProfile.delete()
self.loaderDeactivator.delete()

View File

@ -0,0 +1,45 @@
import NimQml
import std/deques
const MAX_CHATS_IN_MEMORY = 5
type
ChatInMemory = tuple
sectionId: string
chatId: string
QtObject:
type LoaderDeactivator* = ref object of QObject
keepInMemory: Deque[ChatInMemory]
proc setup(self: LoaderDeactivator) =
self.QObject.setup
self.keepInMemory = initDeque[ChatInMemory]()
proc delete*(self: LoaderDeactivator) =
self.QObject.delete
proc newLoaderDeactivator*():
LoaderDeactivator =
new(result, delete)
result.setup
proc newChatInMemory(sectionId, chatId: string): ChatInMemory =
(sectionId, chatId)
proc unloadSection*(self: LoaderDeactivator, searchSectionId: string): bool =
if searchSectionId.len == 0:
return false
for (sectionId, _) in self.keepInMemory.items:
if sectionId == searchSectionId:
return false
return true
proc addChatInMemory*(self: LoaderDeactivator, sectionId, chatId: string): ChatInMemory =
if self.keepInMemory.contains(newChatInMemory(sectionId, chatId)):
return
self.keepInMemory.addFirst(newChatInMemory(sectionId, chatId))
if self.keepInMemory.len > MAX_CHATS_IN_MEMORY:
result = self.keepInMemory.popLast()

View File

@ -374,3 +374,6 @@ method onUserAuthenticated*(self: AccessInterface, pin: string, password: string
method requestToJoinCommunity*(self: AccessInterface, communityId: string, ensName: string) =
raise newException(ValueError, "No implementation available")
method onDeactivateChatLoader*(self: AccessInterface, chatId: string) =
raise newException(ValueError, "No implementation available")

View File

@ -32,6 +32,7 @@ type
highlight: bool
trustStatus: TrustStatus
onlineStatus: OnlineStatus
loaderActive: bool
proc initItem*(
id,
@ -56,7 +57,8 @@ proc initItem*(
highlight: bool = false,
categoryOpened: bool = true,
trustStatus: TrustStatus = TrustStatus.Unknown,
onlineStatus = OnlineStatus.Inactive
onlineStatus = OnlineStatus.Inactive,
loaderActive = false
): Item =
result = Item()
result.id = id
@ -83,6 +85,7 @@ proc initItem*(
result.categoryOpened = categoryOpened
result.trustStatus = trustStatus
result.onlineStatus = onlineStatus
result.loaderActive = loaderActive
proc `$`*(self: Item): string =
result = fmt"""chat_section/Item(
@ -108,6 +111,7 @@ proc `$`*(self: Item): string =
categoryOpened: {$self.categoryOpened},
trustStatus: {$self.trustStatus},
onlineStatus: {$self.onlineStatus},
loaderActive: {$self.loaderActive},
]"""
proc toJsonNode*(self: Item): JsonNode =
@ -132,7 +136,8 @@ proc toJsonNode*(self: Item): JsonNode =
"highlight": self.highlight,
"categoryOpened": self.categoryOpened,
"trustStatus": self.trustStatus,
"onlineStatus": self.onlineStatus
"onlineStatus": self.onlineStatus,
"loaderActive": self.loaderActive
}
proc delete*(self: Item) =
@ -263,3 +268,9 @@ proc `onlineStatus=`*(self: var Item, value: OnlineStatus) =
proc setHasUnreadMessages*(self: Item, value: bool) =
self.hasUnreadMessages = value
proc loaderActive*(self: Item): bool =
self.loaderActive
proc `loaderActive=`*(self: var Item, value: bool) =
self.loaderActive = value

View File

@ -30,6 +30,7 @@ type
TrustStatus
OnlineStatus
IsCategory
LoaderActive
QtObject:
type
@ -97,6 +98,7 @@ QtObject:
ModelRole.TrustStatus.int:"trustStatus",
ModelRole.OnlineStatus.int:"onlineStatus",
ModelRole.IsCategory.int:"isCategory",
ModelRole.LoaderActive.int:"loaderActive",
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -158,6 +160,8 @@ QtObject:
result = newQVariant(item.onlineStatus.int)
of ModelRole.IsCategory:
result = newQVariant(item.`type` == CATEGORY_TYPE)
of ModelRole.LoaderActive:
result = newQVariant(item.loaderActive)
proc appendItem*(self: Model, item: Item) =
let parentModelIndex = newQModelIndex()
@ -236,7 +240,11 @@ QtObject:
let index = self.createIndex(i, 0, nil)
# Set active channel to true and others to false
self.items[i].active = isChannelToSetActive
self.dataChanged(index, index, @[ModelRole.Active.int])
if (isChannelToSetActive):
self.items[i].loaderActive = true
self.dataChanged(index, index, @[ModelRole.Active.int, ModelRole.LoaderActive.int])
else:
self.dataChanged(index, index, @[ModelRole.Active.int])
proc changeMutedOnItemById*(self: Model, id: string, muted: bool) =
let index = self.getItemIdxById(id)
@ -501,3 +509,13 @@ QtObject:
return
return self.items[index].toJsonNode()
proc disableChatLoader*(self: Model, chatId: string) =
let index = self.getItemIdxById(chatId)
if index == -1:
return
self.items[index].loaderActive = false
self.items[index].active = false
let modelIndex = self.createIndex(index, 0, nil)
self.dataChanged(modelIndex, modelIndex, @[ModelRole.Active.int, ModelRole.LoaderActive.int])

View File

@ -226,7 +226,7 @@ proc buildChatSectionUI(
var isActive = false
# restore on a startup last open channel for the section or
# make the first channel which doesn't belong to any category active
if selectedItemId.len == 0 or chatDto.id == sectionLastOpenChat:
if (selectedItemId.len == 0 and sectionLastOpenChat.len == 0) or chatDto.id == sectionLastOpenChat:
selectedItemId = chatDto.id
isActive = true
@ -258,6 +258,7 @@ proc buildChatSectionUI(
colorId,
colorHash,
onlineStatus = onlineStatus,
loaderActive = isActive
)
self.view.chatsModel().appendItem(newChatItem)
@ -477,9 +478,12 @@ method activeItemSet*(self: Module, itemId: string) =
# save last open chat in settings for restore on the next app launch
singletonInstance.localAccountSensitiveSettings.setSectionLastOpenChat(mySectionId, activeChatId)
let (deactivateSectionId, deactivateChatId) = singletonInstance.loaderDeactivator.addChatInMemory(mySectionId, activeChatId)
# notify parent module about active chat/channel
self.delegate.onActiveChatChange(mySectionId, activeChatId)
self.delegate.onDeactivateSectionAndChatLoader(deactivateSectionId, deactivateChatId)
method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant
@ -591,6 +595,7 @@ method addNewChat*(
colorHash,
chatDto.highlight,
onlineStatus = onlineStatus,
loaderActive = setChatAsActive
)
self.addSubmodule(
chatDto.id,
@ -667,8 +672,12 @@ method setFirstChannelAsActive*(self: Module) =
if(self.view.chatsModel().getCount() == 0):
self.setActiveItem("")
return
let chat_item = self.view.chatsModel().getItemAtIndex(0)
self.setActiveItem(chat_item.id)
let chat_items = self.view.chatsModel().items()
for chat_item in chat_items:
if chat_item.`type` != CATEGORY_TYPE:
self.setActiveItem(chat_item.id)
break
method onReorderChat*(self: Module, chatId: string, position: int, newCategoryIdForChat: string, prevCategoryId: string, prevCategoryDeleted: bool) =
var newCategoryName = ""
@ -1222,3 +1231,6 @@ proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPerm
return tokenPermissionItem
method onDeactivateChatLoader*(self: Module, chatId: string) =
self.view.chatsModel().disableChatLoader(chatId)

View File

@ -435,4 +435,4 @@ QtObject:
QtProperty[bool] allTokenRequirementsMet:
read = getAllTokenRequirementsMet
notify = allTokenRequirementsMetChanged
notify = allTokenRequirementsMetChanged

View File

@ -298,6 +298,9 @@ method onAcceptRequestToJoinLoading*(self: AccessInterface, communityId: string,
method onAcceptRequestToJoinSuccess*(self: AccessInterface, communityId: string, memberKey: string, requestId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onDeactivateSectionAndChatLoader*(self: AccessInterface, sectionId: string, chatId: string) {.base.} =
raise newException(ValueError, "No implementation available")
# This way (using concepts) is used only for the modules managed by AppController
type
DelegateInterface* = concept c

View File

@ -369,7 +369,8 @@ proc createChannelGroupItem[T](self: Module[T], c: ChannelGroupDto): SectionItem
)
) else: @[],
c.encrypted,
communityTokensItems
communityTokensItems,
loaderActive = active
)
method load*[T](
@ -733,8 +734,8 @@ method setActiveSection*[T](self: Module[T], item: SectionItem, skipSavingInSett
self.controller.setActiveSection(item.id, skipSavingInSettings)
method setActiveSectionById*[T](self: Module[T], id: string) =
let item = self.view.model().getItemById(id)
self.setActiveSection(item)
let item = self.view.model().getItemById(id)
self.setActiveSection(item)
proc notifySubModulesAboutChange[T](self: Module[T], sectionId: string) =
for cModule in self.channelGroupModules.values:
@ -1187,3 +1188,9 @@ method onDisplayKeycardSharedModuleFlow*[T](self: Module[T]) =
method activateStatusDeepLink*[T](self: Module[T], statusDeepLink: string) =
let linkToActivate = self.urlsManager.convertExternalLinkToInternal(statusDeepLink)
self.urlsManager.onUrlActivated(linkToActivate)
method onDeactivateSectionAndChatLoader*[T](self: Module[T], sectionId: string, chatId: string) =
if (sectionId.len > 0 and self.channelGroupModules.contains(sectionId)):
self.channelGroupModules[sectionId].onDeactivateChatLoader(chatId)
if (singletonInstance.loaderDeactivator.unloadSection(sectionId)):
self.view.model().disableSectionLoader(sectionId)

View File

@ -55,6 +55,7 @@ type
declinedMemberRequestsModel: member_model.Model
encrypted: bool
communityTokensModel: community_tokens_model.TokenModel
loaderActive: bool
proc initItem*(
id: string,
@ -91,6 +92,7 @@ proc initItem*(
declinedMemberRequests: seq[MemberItem] = @[],
encrypted: bool = false,
communityTokens: seq[TokenItem] = @[],
loaderActive = false,
): SectionItem =
result.id = id
result.sectionType = sectionType
@ -132,6 +134,7 @@ proc initItem*(
result.encrypted = encrypted
result.communityTokensModel = newTokenModel()
result.communityTokensModel.setItems(communityTokens)
result.loaderActive = loaderActive
proc isEmpty*(self: SectionItem): bool =
return self.id.len == 0
@ -171,6 +174,7 @@ proc `$`*(self: SectionItem): string =
declinedMemberRequests:{self.declinedMemberRequestsModel},
encrypted:{self.encrypted},
communityTokensModel:{self.communityTokensModel},
loaderActive:{self.loaderActive},
]"""
proc id*(self: SectionItem): string {.inline.} =
@ -322,3 +326,9 @@ proc communityTokens*(self: SectionItem): community_tokens_model.TokenModel {.in
proc updatePendingRequestLoadingState*(self: SectionItem, memberKey: string, loading: bool) {.inline.} =
self.pendingMemberRequestsModel.updateLoadingState(memberKey, loading)
proc loaderActive*(self: SectionItem): bool {.inline.} =
self.loaderActive
proc `loaderActive=`*(self: var SectionItem, value: bool) {.inline.} =
self.loaderActive = value

View File

@ -43,6 +43,7 @@ type
PendingMemberRequestsModel
DeclinedMemberRequestsModel
AmIBanned
LoaderActive
QtObject:
type
@ -114,7 +115,8 @@ QtObject:
ModelRole.CommunityTokensModel.int:"communityTokens",
ModelRole.PendingMemberRequestsModel.int:"pendingMemberRequests",
ModelRole.DeclinedMemberRequestsModel.int:"declinedMemberRequests",
ModelRole.AmIBanned.int:"amIBanned"
ModelRole.AmIBanned.int:"amIBanned",
ModelRole.LoaderActive.int:"loaderActive"
}.toTable
method data(self: SectionModel, index: QModelIndex, role: int): QVariant =
@ -198,6 +200,8 @@ QtObject:
result = newQVariant(item.declinedMemberRequests)
of ModelRole.AmIBanned:
result = newQVariant(item.amIBanned)
of ModelRole.LoaderActive:
result = newQVariant(item.loaderActive)
proc isItemExist(self: SectionModel, id: string): bool =
for it in self.items:
@ -293,7 +297,8 @@ QtObject:
ModelRole.CommunityTokensModel.int,
ModelRole.PendingMemberRequestsModel.int,
ModelRole.DeclinedMemberRequestsModel.int,
ModelRole.AmIBanned.int
ModelRole.AmIBanned.int,
ModelRole.LoaderActive.int
])
proc getNthEnabledItem*(self: SectionModel, nth: int): SectionItem =
@ -331,7 +336,9 @@ QtObject:
if(self.items[i].id == id):
let index = self.createIndex(i, 0, nil)
self.items[i].active = true
self.dataChanged(index, index, @[ModelRole.Active.int])
self.items[i].loaderActive = true
self.dataChanged(index, index, @[ModelRole.Active.int, ModelRole.LoaderActive.int])
proc sectionVisibilityUpdated*(self: SectionModel) {.signal.}
@ -428,5 +435,13 @@ QtObject:
"ensOnly": item.ensOnly,
"nbMembers": item.members.getCount(),
"encrypted": item.encrypted,
"loaderActive": item.loaderActive,
}
return $jsonObj
proc disableSectionLoader*(self: SectionModel, sectionId: string) =
for i in 0 ..< self.items.len:
if(self.items[i].id == sectionId):
let index = self.createIndex(i, 0, nil)
self.items[i].loaderActive = false
self.dataChanged(index, index, @[ModelRole.LoaderActive.int])

View File

@ -124,21 +124,11 @@ Item {
id: chatLoader
// Channels/chats are not loaded by default and only load when first put active
active: false
active: model.loaderActive
width: parent.width
height: parent.height
visible: model.active
// Removing the binding in order not to unload the content:
// It is done for keeping:
// - the last channel/chat scroll position
// - the last typed but not sent text
Binding on active {
when: !chatLoader.active
restoreMode: Binding.RestoreNone
value: model.itemId && root.isSectionActive && (model.itemId === root.activeChatId || model.itemId === root.activeSubItemId)
}
sourceComponent: ChatContentView {
visible: !root.rootStore.openCreateChat && isActiveChannel
rootStore: root.rootStore

View File

@ -977,7 +977,7 @@ Item {
readonly property string sectionId: model.id
asynchronous: true
active: sectionId === appMain.rootStore.mainModuleInst.activeSection.id
active: model.loaderActive
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
@ -1015,13 +1015,6 @@ Item {
onOpenAppSearch: {
appSearch.openSearchPopup()
}
Component.onCompleted: {
// Do not unload section data from the memory in order not
// to reset scroll, not send text input and etc during the
// sections switching
communityLoader.active = true
}
}
}
}