refactor(chat): make getChats async to speed up start time

Fixes #9340
This commit is contained in:
Jonathan Rainville 2023-01-30 16:05:34 -05:00
parent 5f4000b7a5
commit f8ecd9dbce
10 changed files with 257 additions and 77 deletions

View File

@ -166,7 +166,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
statusFoundation.events, statusFoundation.threadpool, result.networkService, result.settingsService,
result.activityCenterService
)
result.chatService = chat_service.newService(statusFoundation.events, result.contactsService)
result.chatService = chat_service.newService(statusFoundation.events, statusFoundation.threadpool, result.contactsService)
result.tokenService = token_service.newService(
statusFoundation.events, statusFoundation.threadpool, result.networkService
)

View File

@ -86,6 +86,24 @@ proc init*(self: Controller) =
let d9 = 9*86400 # 9 days
discard self.settingsService.setDefaultSyncPeriod(d9)
self.events.on(SIGNAL_CHATS_LOADED) do(e:Args):
let args = ChannelGroupsArgs(e)
self.delegate.onChatsLoaded(
args.channelGroups,
self.events,
self.settingsService,
self.nodeConfigurationService,
self.contactsService,
self.chatService,
self.communityService,
self.messageService,
self.gifService,
self.mailserversService,
)
self.events.on(SIGNAL_CHATS_LOADING_FAILED) do(e:Args):
self.delegate.onChatsLoadingFailed()
self.events.on(SIGNAL_ACTIVE_MAILSERVER_CHANGED) do(e:Args):
let args = ActiveMailserverChangedArgs(e)
if args.nodeAddress == "":

View File

@ -68,6 +68,24 @@ method chatSectionDidLoad*(self: AccessInterface) {.base.} =
method communitySectionDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onChatsLoaded*(
self: AccessInterface,
channelGroups: seq[ChannelGroupDto],
events: EventEmitter,
settingsService: settings_service.Service,
nodeConfigurationService: node_configuration_service.Service,
contactsService: contacts_service.Service,
chatService: chat_service.Service,
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service)
{.base.} =
raise newException(ValueError, "No implementation available")
method onChatsLoadingFailed*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onActiveChatChange*(self: AccessInterface, sectionId: string, chatId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -334,7 +334,6 @@ proc createChannelGroupItem[T](self: Module[T], c: ChannelGroupDto): SectionItem
c.encrypted
)
method load*[T](
self: Module[T],
events: EventEmitter,
@ -356,30 +355,6 @@ method load*[T](
if (activeSectionId == ""):
activeSectionId = singletonInstance.userProfile.getPubKey()
let channelGroups = self.controller.getChannelGroups()
for channelGroup in channelGroups:
self.channelGroupModules[channelGroup.id] = chat_section_module.newModule(
self,
events,
channelGroup.id,
isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community,
settingsService,
nodeConfigurationService,
contactsService,
chatService,
communityService,
messageService,
gifService,
mailserversService
)
let channelGroupItem = self.createChannelGroupItem(channelGroup)
self.view.model().addItem(channelGroupItem)
if(activeSectionId == channelGroupItem.id):
activeSection = channelGroupItem
self.channelGroupModules[channelGroup.id].load(channelGroup, events, settingsService, nodeConfigurationService,
contactsService, chatService, communityService, messageService, gifService, mailserversService)
# Communities Portal Section
let communitiesPortalSectionItem = initItem(conf.COMMUNITIESPORTAL_SECTION_ID, SectionType.CommunitiesPortal, conf.COMMUNITIESPORTAL_SECTION_NAME,
amISectionAdmin = false,
@ -485,6 +460,56 @@ method load*[T](
else:
self.setActiveSection(activeSection)
method onChatsLoaded*[T](
self: Module[T],
channelGroups: seq[ChannelGroupDto],
events: EventEmitter,
settingsService: settings_service.Service,
nodeConfigurationService: node_configuration_service.Service,
contactsService: contacts_service.Service,
chatService: chat_service.Service,
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service,
) =
var activeSection: SectionItem
var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection()
if activeSectionId == "":
activeSectionId = singletonInstance.userProfile.getPubKey()
for channelGroup in channelGroups:
self.channelGroupModules[channelGroup.id] = chat_section_module.newModule(
self,
events,
channelGroup.id,
isCommunity = channelGroup.channelGroupType == ChannelGroupType.Community,
settingsService,
nodeConfigurationService,
contactsService,
chatService,
communityService,
messageService,
gifService,
mailserversService
)
let channelGroupItem = self.createChannelGroupItem(channelGroup)
self.view.model().addItem(channelGroupItem)
if activeSectionId == channelGroupItem.id:
activeSection = channelGroupItem
self.channelGroupModules[channelGroup.id].load(channelGroup, events, settingsService, nodeConfigurationService,
contactsService, chatService, communityService, messageService, gifService, mailserversService)
# Set active section if it is one of the channel sections
if not activeSection.isEmpty():
self.setActiveSection(activeSection)
self.view.chatsLoaded()
method onChatsLoadingFailed*[T](self: Module[T]) =
self.view.chatsLoadingFailed()
proc checkIfModuleDidLoad [T](self: Module[T]) =
if self.moduleLoaded:
return

View File

@ -14,6 +14,8 @@ QtObject:
delegate: io_interface.AccessInterface
model: section_model.SectionModel
modelVariant: QVariant
chatsLoaded: bool
chatsLoadingFailed: bool
activeSection: ActiveSection
activeSectionVariant: QVariant
chatSearchModel: chat_search_model.Model
@ -40,6 +42,8 @@ QtObject:
result.QObject.setup
result.delegate = delegate
result.model = section_model.newModel()
result.chatsLoaded = false
result.chatsLoadingFailed = false
result.modelVariant = newQVariant(result.model)
result.activeSection = newActiveSection()
result.activeSectionVariant = newQVariant(result.activeSection)
@ -155,6 +159,30 @@ QtObject:
proc setCurrentUserStatus*(self: View, status: int) {.slot.} =
self.delegate.setCurrentUserStatus(intToEnum(status, StatusType.Unknown))
proc chatsLoadedChanged(self: View) {.signal.}
proc chatsLoaded*(self: View) =
self.chatsLoaded = true
self.chatsLoadedChanged()
proc getChatsLoaded(self: View): bool {.slot.} =
return self.chatsLoaded
QtProperty[bool] chatsLoaded:
read = getChatsLoaded
notify = chatsLoadedChanged
proc chatsLoadingFailedChanged(self: View) {.signal.}
proc chatsLoadingFailed*(self: View) =
self.chatsLoadingFailed = true
self.chatsLoadingFailedChanged()
proc getChatsLoadingFailed(self: View): bool {.slot.} =
return self.chatsLoadingFailed
QtProperty[bool] chatsLoadingFailed:
read = getChatsLoadingFailed
notify = chatsLoadingFailedChanged
# Since we cannot return QVariant from the proc which has arguments, so cannot have proc like this:
# prepareCommunitySectionModuleForCommunityId(self: View, communityId: string): QVariant {.slot.}
# we're using combination of

View File

@ -0,0 +1,15 @@
#################################################
# Async get chats (channel groups)
#################################################
type
AsyncGetChatsTaskArg = ref object of QObjectTaskArg
const asyncGetChatsTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncGetChatsTaskArg](argEncoded)
let response = status_chat.getChats()
let responseJson = %*{
"channelGroups": response.result
}
arg.finish(responseJson)

View File

@ -1,5 +1,7 @@
import NimQml, Tables, json, sequtils, strformat, chronicles, os, std/algorithm, strutils, uuids, base64
import std/[times, os]
import ../../../app/core/tasks/[qt, threadpool]
import ./dto/chat as chat_dto
import ../message/dto/message as message_dto
import ../activity_center/dto/notification as notification_dto
@ -18,24 +20,20 @@ from ../../common/account_constants import ZERO_ADDRESS
export chat_dto
logScope:
topics = "chat-service"
include ../../common/json_utils
include ../../../app/core/tasks/common
include async_tasks
type
ChannelGroupsArgs* = ref object of Args
channelGroups*: seq[ChannelGroupDto]
ChatUpdateArgs* = ref object of Args
chats*: seq[ChatDto]
messages*: seq[MessageDto]
# TODO refactor that part
# pinnedMessages*: seq[MessageDto]
# emojiReactions*: seq[Reaction]
# communities*: seq[Community]
# communityMembershipRequests*: seq[CommunityMembershipRequest]
activityCenterNotifications*: seq[ActivityCenterNotificationDto]
# statusUpdates*: seq[StatusUpdate]
# deletedMessages*: seq[RemovedMessage]
CreatedChatArgs* = ref object of Args
chat*: ChatDto
@ -85,6 +83,8 @@ type
# Signals which may be emitted by this service:
const SIGNAL_CHATS_LOADED* = "chatsLoaded"
const SIGNAL_CHATS_LOADING_FAILED* = "chatsLoadingFailed"
const SIGNAL_CHAT_UPDATE* = "chatUpdate"
const SIGNAL_CHAT_LEFT* = "channelLeft"
const SIGNAL_SENDING_FAILED* = "messageSendingFailed"
@ -105,19 +105,27 @@ const SIGNAL_CHAT_CREATED* = "chatCreated"
QtObject:
type Service* = ref object of QObject
threadpool: ThreadPool
events: EventEmitter
chats: Table[string, ChatDto] # [chat_id, ChatDto]
channelGroups: OrderedTable[string, ChannelGroupDto] # [chatGroup_id, ChannelGroupDto]
contactService: contact_service.Service
proc delete*(self: Service) =
discard
self.QObject.delete
proc newService*(events: EventEmitter, contactService: contact_service.Service): Service =
proc newService*(
events: EventEmitter,
threadpool: ThreadPool,
contactService: contact_service.Service
): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
result.contactService = contactService
result.chats = initTable[string, ChatDto]()
result.channelGroups = initOrderedTable[string, ChannelGroupDto]()
# Forward declarations
proc updateOrAddChat*(self: Service, chat: ChatDto)
@ -152,19 +160,31 @@ QtObject:
if (community.joined):
self.updateOrAddChannelGroup(community.toChannelGroupDto())
proc getChannelGroups*(self: Service): seq[ChannelGroupDto] =
return toSeq(self.channelGroups.values)
proc asyncGetChats*(self: Service) =
let arg = AsyncGetChatsTaskArg(
tptr: cast[ByteAddress](asyncGetChatsTask),
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncGetChatsResponse",
)
self.threadpool.start(arg)
proc sortPersonnalChatAsFirst[T, D](x, y: (T, D)): int =
if (x[1].channelGroupType == Personal): return -1
if (y[1].channelGroupType == Personal): return 1
return 0
proc init*(self: Service) =
self.doConnect()
proc onAsyncGetChatsResponse*(self: Service, response: string) {.slot.} =
try:
let response = status_chat.getChats()
let rpcResponseObj = response.parseJson
if(rpcResponseObj["channelGroups"].kind == JNull):
raise newException(RpcException, "No channel groups returned")
var chats: seq[ChatDto] = @[]
for (sectionId, section) in response.result.pairs:
for (sectionId, section) in rpcResponseObj["channelGroups"].pairs:
var channelGroup = section.toChannelGroupDto()
channelGroup.id = sectionId
self.channelGroups[sectionId] = channelGroup
@ -181,13 +201,17 @@ QtObject:
discard status_chat.deactivateChat(chat.id)
else:
self.chats[chat.id] = chat
self.events.emit(SIGNAL_CHATS_LOADED, ChannelGroupsArgs(channelGroups: self.getChannelGroups()))
except Exception as e:
let errDesription = e.msg
error "error: ", errDesription
return
self.events.emit(SIGNAL_CHATS_LOADING_FAILED, Args())
proc getChannelGroups*(self: Service): seq[ChannelGroupDto] =
return toSeq(self.channelGroups.values)
proc init*(self: Service) =
self.doConnect()
self.asyncGetChats()
proc hasChannel*(self: Service, chatId: string): bool =
self.chats.hasKey(chatId)

View File

@ -20,7 +20,7 @@ import "../popups/community"
Item {
id: root
width: 304
width: Constants.chatSectionLeftColumnWidth
height: parent.height
// Important:

View File

@ -374,9 +374,13 @@ Item {
height: 440
}
StatusStickersPopup {
id: statusStickersPopup
store: chatLayoutContainer.rootStore
Loader {
id: statusStickersPopupLoader
active: appMain.rootStore.mainModuleInst.chatsLoaded
sourceComponent: StatusStickersPopup {
id: statusStickersPopup
store: personalChatLayoutLoader.item.rootStore
}
}
StatusMainLayout {
@ -874,34 +878,78 @@ Item {
// If we ever change stack layout component order we need to updade
// Constants.appViewStackIndex accordingly
ChatLayout {
id: chatLayoutContainer
Loader {
id: personalChatLayoutLoader
sourceComponent: {
if (appMain.rootStore.mainModuleInst.chatsLoadingFailed) {
return errorStateComponent
}
if (appMain.rootStore.mainModuleInst.chatsLoaded) {
return personalChatLayoutComponent
}
return loadingStateComponent
}
Component {
id: loadingStateComponent
Item {
anchors.fill: parent
chatView.emojiPopup: statusEmojiPopup
chatView.stickersPopup: statusStickersPopup
contactsStore: appMain.rootStore.contactStore
rootStore.emojiReactionsModel: appMain.rootStore.emojiReactionsModel
rootStore.openCreateChat: createChatView.opened
chatView.onProfileButtonClicked: {
Global.changeAppSectionBySectionType(Constants.appSection.profile);
Row {
anchors.centerIn: parent
spacing: 6
StatusBaseText {
text: qsTr("Loading...")
}
LoadingAnimation {}
}
}
}
Component {
id: errorStateComponent
Item {
anchors.fill: parent
StatusBaseText {
text: qsTr("Error loading chats, try closing the app and restarting")
anchors.centerIn: parent
}
}
}
chatView.onOpenAppSearch: {
appSearch.openSearchPopup()
}
Component {
id: personalChatLayoutComponent
onImportCommunityClicked: {
Global.openPopup(communitiesPortalLayoutContainer.importCommunitiesPopup);
}
ChatLayout {
id: chatLayoutContainer
onCreateCommunityClicked: {
Global.openPopup(communitiesPortalLayoutContainer.createCommunitiesPopup);
}
chatView.emojiPopup: statusEmojiPopup
chatView.stickersPopup: statusStickersPopupLoader.item
Component.onCompleted: {
rootStore.chatCommunitySectionModule = appMain.rootStore.mainModuleInst.getChatSectionModule()
contactsStore: appMain.rootStore.contactStore
rootStore.emojiReactionsModel: appMain.rootStore.emojiReactionsModel
rootStore.openCreateChat: createChatView.opened
chatView.onProfileButtonClicked: {
Global.changeAppSectionBySectionType(Constants.appSection.profile);
}
chatView.onOpenAppSearch: {
appSearch.openSearchPopup()
}
onImportCommunityClicked: {
Global.openPopup(communitiesPortalLayoutContainer.importCommunitiesPopup);
}
onCreateCommunityClicked: {
Global.openPopup(communitiesPortalLayoutContainer.createCommunitiesPopup);
}
Component.onCompleted: {
rootStore.chatCommunitySectionModule = appMain.rootStore.mainModuleInst.getChatSectionModule()
}
}
}
}
@ -975,7 +1023,7 @@ Item {
sourceComponent: ChatLayout {
chatView.emojiPopup: statusEmojiPopup
chatView.stickersPopup: statusStickersPopup
chatView.stickersPopup: statusStickersPopupLoader.item
contactsStore: appMain.rootStore.contactStore
rootStore.emojiReactionsModel: appMain.rootStore.emojiReactionsModel
@ -1006,7 +1054,7 @@ Item {
id: createChatView
property bool opened: false
active: opened
active: appMain.rootStore.mainModuleInst.chatsLoaded && opened
asynchronous: true
anchors.top: parent.top
@ -1014,12 +1062,14 @@ Item {
anchors.rightMargin: 8
anchors.bottom: parent.bottom
anchors.right: parent.right
width: parent.width - chatLayoutContainer.chatView.leftPanel.width - anchors.rightMargin - anchors.leftMargin
width: active ?
parent.width - Constants.chatSectionLeftColumnWidth -
anchors.rightMargin - anchors.leftMargin : 0
sourceComponent: CreateChatView {
rootStore: chatLayoutContainer.rootStore
rootStore: personalChatLayoutLoader.item.rootStore
emojiPopup: statusEmojiPopup
stickersPopup: statusStickersPopup
stickersPopup: statusStickersPopupLoader.item
}
}
}
@ -1076,7 +1126,7 @@ Item {
x: parent.width - width - Style.current.smallPadding
y: parent.y + _buttonSize
height: appView.height - _buttonSize * 2
store: chatLayoutContainer.rootStore
store: personalChatLayoutLoader.item.rootStore
activityCenterStore: appMain.activityCenterStore
}
}

View File

@ -291,6 +291,8 @@ QtObject {
}
}
readonly property int chatSectionLeftColumnWidth: 304
readonly property QtObject appSection: QtObject {
readonly property int chat: 0
readonly property int community: 1