refactor(online-users): adding online users for chat/channel

This commit is contained in:
Sale Djenic 2021-11-30 15:49:45 +01:00
parent afe6d34735
commit f138fecdd2
35 changed files with 763 additions and 325 deletions

View File

@ -362,6 +362,7 @@ proc load(self: AppController) =
# load main module # load main module
self.mainModule.load( self.mainModule.load(
self.statusFoundation.status.events, self.statusFoundation.status.events,
self.contactsService,
self.chatService, self.chatService,
self.communityService, self.communityService,
self.messageService self.messageService

View File

@ -4,7 +4,7 @@ import
proc handleSignals(self: ChatController) = proc handleSignals(self: ChatController) =
self.status.events.on(SignalType.Message.event) do(e:Args): self.status.events.on(SignalType.Message.event) do(e:Args):
var data = MessageSignal(e) var data = MessageSignal(e)
self.status.chat.update(data.chats, data.messages, data.emojiReactions, data.communities, data.membershipRequests, data.pinnedMessages, data.activityCenterNotification, data.statusUpdates, data.deletedMessages) #self.status.chat.update(data.chats, data.messages, data.emojiReactions, data.communities, data.membershipRequests, data.pinnedMessages, data.activityCenterNotification, data.statusUpdates, data.deletedMessages)
self.status.events.on(SignalType.DiscoverySummary.event) do(e:Args): self.status.events.on(SignalType.DiscoverySummary.event) do(e:Args):
## Handle mailserver peers being added and removed ## Handle mailserver peers being added and removed
@ -39,8 +39,9 @@ proc handleSignals(self: ChatController) =
self.view.messageView.messageList[chatId].checkTimeout(messageId) self.view.messageView.messageList[chatId].checkTimeout(messageId)
self.status.events.on(SignalType.CommunityFound.event) do(e: Args): self.status.events.on(SignalType.CommunityFound.event) do(e: Args):
var data = CommunitySignal(e) discard
self.view.communities.addCommunityToList(data.community) # var data = CommunitySignal(e)
# self.view.communities.addCommunityToList(data.community)
self.status.events.on(SignalType.MailserverRequestCompleted.event) do(e:Args): self.status.events.on(SignalType.MailserverRequestCompleted.event) do(e:Args):
# TODO: if the signal contains a cursor, request additional messages # TODO: if the signal contains a cursor, request additional messages

View File

@ -2,13 +2,13 @@ import json
import base import base
import status/types/community import ../../../../app_service/service/community/dto/[community]
import signal_type import signal_type
type CommunitySignal* = ref object of Signal type CommunitySignal* = ref object of Signal
community*: Community community*: CommunityDto
proc fromEvent*(T: type CommunitySignal, event: JsonNode): CommunitySignal = proc fromEvent*(T: type CommunitySignal, event: JsonNode): CommunitySignal =
result = CommunitySignal() result = CommunitySignal()
result.signalType = SignalType.CommunityFound result.signalType = SignalType.CommunityFound
result.community = event["event"].toCommunity() result.community = event["event"].toCommunityDto()

View File

@ -2,20 +2,26 @@ import json
import base import base
import status/types/[message, chat, community, profile, installation, # Step by step we should remove all these types from `status-lib`
activity_center_notification, status_update, removed_message] import status/types/[installation, activity_center_notification, removed_message]
import status/types/community as old_community
import ../../../../app_service/service/message/dto/[message, pinned_message, reaction]
import ../../../../app_service/service/chat/dto/[chat]
import ../../../../app_service/service/community/dto/[community]
import ../../../../app_service/service/contacts/dto/[contacts, status_update]
type MessageSignal* = ref object of Signal type MessageSignal* = ref object of Signal
messages*: seq[Message] messages*: seq[MessageDto]
pinnedMessages*: seq[Message] pinnedMessages*: seq[PinnedMessageDto]
chats*: seq[Chat] chats*: seq[ChatDto]
contacts*: seq[Profile] contacts*: seq[ContactsDto]
installations*: seq[Installation] installations*: seq[Installation]
emojiReactions*: seq[Reaction] emojiReactions*: seq[ReactionDto]
communities*: seq[Community] communities*: seq[CommunityDto]
membershipRequests*: seq[CommunityMembershipRequest] membershipRequests*: seq[old_community.CommunityMembershipRequest]
activityCenterNotification*: seq[ActivityCenterNotification] activityCenterNotification*: seq[ActivityCenterNotification]
statusUpdates*: seq[StatusUpdate] statusUpdates*: seq[StatusUpdateDto]
deletedMessages*: seq[RemovedMessage] deletedMessages*: seq[RemovedMessage]
proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal = proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal =
@ -25,27 +31,21 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal =
if event["event"]{"contacts"} != nil: if event["event"]{"contacts"} != nil:
for jsonContact in event["event"]["contacts"]: for jsonContact in event["event"]["contacts"]:
signal.contacts.add(jsonContact.toProfile()) signal.contacts.add(jsonContact.toContactsDto())
var chatsWithMentions: seq[string] = @[]
if event["event"]{"messages"} != nil: if event["event"]{"messages"} != nil:
for jsonMsg in event["event"]["messages"]: for jsonMsg in event["event"]["messages"]:
var message = jsonMsg.toMessage() var message = jsonMsg.toMessageDto()
if message.hasMention:
chatsWithMentions.add(message.chatId)
signal.messages.add(message) signal.messages.add(message)
if event["event"]{"chats"} != nil: if event["event"]{"chats"} != nil:
for jsonChat in event["event"]["chats"]: for jsonChat in event["event"]["chats"]:
var chat = jsonChat.toChat var chat = jsonChat.toChatDto()
if chatsWithMentions.contains(chat.id):
chat.mentionsCount.inc
signal.chats.add(chat) signal.chats.add(chat)
if event["event"]{"statusUpdates"} != nil: if event["event"]{"statusUpdates"} != nil:
for jsonStatusUpdate in event["event"]["statusUpdates"]: for jsonStatusUpdate in event["event"]["statusUpdates"]:
var statusUpdate = jsonStatusUpdate.toStatusUpdate var statusUpdate = jsonStatusUpdate.toStatusUpdateDto()
signal.statusUpdates.add(statusUpdate) signal.statusUpdates.add(statusUpdate)
if event["event"]{"installations"} != nil: if event["event"]{"installations"} != nil:
@ -54,11 +54,11 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal =
if event["event"]{"emojiReactions"} != nil: if event["event"]{"emojiReactions"} != nil:
for jsonReaction in event["event"]["emojiReactions"]: for jsonReaction in event["event"]["emojiReactions"]:
signal.emojiReactions.add(jsonReaction.toReaction) signal.emojiReactions.add(jsonReaction.toReactionDto())
if event["event"]{"communities"} != nil: if event["event"]{"communities"} != nil:
for jsonCommunity in event["event"]["communities"]: for jsonCommunity in event["event"]["communities"]:
signal.communities.add(jsonCommunity.toCommunity) signal.communities.add(jsonCommunity.toCommunityDto())
if event["event"]{"requestsToJoinCommunity"} != nil: if event["event"]{"requestsToJoinCommunity"} != nil:
for jsonCommunity in event["event"]["requestsToJoinCommunity"]: for jsonCommunity in event["event"]["requestsToJoinCommunity"]:
@ -73,23 +73,25 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal =
signal.activityCenterNotification.add(jsonNotification.toActivityCenterNotification()) signal.activityCenterNotification.add(jsonNotification.toActivityCenterNotification())
if event["event"]{"pinMessages"} != nil: if event["event"]{"pinMessages"} != nil:
for jsonPinnedMessage in event["event"]["pinMessages"]: discard
var contentType: ContentType # Need to refactor this
try: # for jsonPinnedMessage in event["event"]["pinMessages"]:
contentType = ContentType(jsonPinnedMessage{"contentType"}.getInt) # var contentType: ContentType
except: # try:
contentType = ContentType.Message # contentType = ContentType(jsonPinnedMessage{"contentType"}.getInt)
signal.pinnedMessages.add(Message( # except:
id: jsonPinnedMessage{"message_id"}.getStr, # contentType = ContentType.Message
chatId: jsonPinnedMessage{"chat_id"}.getStr, # signal.pinnedMessages.add(Message(
localChatId: jsonPinnedMessage{"localChatId"}.getStr, # id: jsonPinnedMessage{"message_id"}.getStr,
pinnedBy: jsonPinnedMessage{"from"}.getStr, # chatId: jsonPinnedMessage{"chat_id"}.getStr,
identicon: jsonPinnedMessage{"identicon"}.getStr, # localChatId: jsonPinnedMessage{"localChatId"}.getStr,
alias: jsonPinnedMessage{"alias"}.getStr, # pinnedBy: jsonPinnedMessage{"from"}.getStr,
clock: jsonPinnedMessage{"clock"}.getInt, # identicon: jsonPinnedMessage{"identicon"}.getStr,
isPinned: jsonPinnedMessage{"pinned"}.getBool, # alias: jsonPinnedMessage{"alias"}.getStr,
contentType: contentType # clock: jsonPinnedMessage{"clock"}.getInt,
)) # isPinned: jsonPinnedMessage{"pinned"}.getBool,
# contentType: contentType
# ))
result = signal result = signal

View File

@ -33,6 +33,8 @@ QtObject:
self.activeSubItemChanged() self.activeSubItemChanged()
proc getId(self: ActiveItem): string {.slot.} = proc getId(self: ActiveItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.id return self.item.id
QtProperty[string] id: QtProperty[string] id:
@ -48,54 +50,72 @@ QtObject:
read = getIsSubItemActive read = getIsSubItemActive
proc getName(self: ActiveItem): string {.slot.} = proc getName(self: ActiveItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.name return self.item.name
QtProperty[string] name: QtProperty[string] name:
read = getName read = getName
proc getIcon(self: ActiveItem): string {.slot.} = proc getIcon(self: ActiveItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.icon return self.item.icon
QtProperty[string] icon: QtProperty[string] icon:
read = getIcon read = getIcon
proc getColor(self: ActiveItem): string {.slot.} = proc getColor(self: ActiveItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.color return self.item.color
QtProperty[string] color: QtProperty[string] color:
read = getColor read = getColor
proc getDescription(self: ActiveItem): string {.slot.} = proc getDescription(self: ActiveItem): string {.slot.} =
if(self.item.isNil):
return ""
return self.item.description return self.item.description
QtProperty[string] description: QtProperty[string] description:
read = getDescription read = getDescription
proc getType(self: ActiveItem): int {.slot.} = proc getType(self: ActiveItem): int {.slot.} =
if(self.item.isNil):
return 0
return self.item.`type` return self.item.`type`
QtProperty[int] type: QtProperty[int] type:
read = getType read = getType
proc getHasUnreadMessages(self: ActiveItem): bool {.slot.} = proc getHasUnreadMessages(self: ActiveItem): bool {.slot.} =
if(self.item.isNil):
return false
return self.item.hasUnreadMessages return self.item.hasUnreadMessages
QtProperty[bool] hasUnreadMessages: QtProperty[bool] hasUnreadMessages:
read = getHasUnreadMessages read = getHasUnreadMessages
proc getNotificationCount(self: ActiveItem): int {.slot.} = proc getNotificationCount(self: ActiveItem): int {.slot.} =
if(self.item.isNil):
return 0
return self.item.notificationsCount return self.item.notificationsCount
QtProperty[int] notificationCount: QtProperty[int] notificationCount:
read = getNotificationCount read = getNotificationCount
proc getMuted(self: ActiveItem): bool {.slot.} = proc getMuted(self: ActiveItem): bool {.slot.} =
if(self.item.isNil):
return false
return self.item.muted return self.item.muted
QtProperty[bool] muted: QtProperty[bool] muted:
read = getMuted read = getMuted
proc getPosition(self: ActiveItem): int {.slot.} = proc getPosition(self: ActiveItem): int {.slot.} =
if(self.item.isNil):
return 0
return self.item.position return self.item.position
QtProperty[int] position: QtProperty[int] position:

View File

@ -18,18 +18,20 @@ type
events: EventEmitter events: EventEmitter
chatId: string chatId: string
belongsToCommunity: bool belongsToCommunity: bool
isUsersListAvailable: bool #users list is not available for 1:1 chat
chatService: chat_service.ServiceInterface chatService: chat_service.ServiceInterface
communityService: community_service.ServiceInterface communityService: community_service.ServiceInterface
messageService: message_service.Service messageService: message_service.Service
proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter, chatId: string, proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter, chatId: string,
belongsToCommunity: bool, chatService: chat_service.ServiceInterface, belongsToCommunity: bool, isUsersListAvailable: bool, chatService: chat_service.ServiceInterface,
communityService: community_service.ServiceInterface, messageService: message_service.Service): Controller = communityService: community_service.ServiceInterface, messageService: message_service.Service): Controller =
result = Controller() result = Controller()
result.delegate = delegate result.delegate = delegate
result.events = events result.events = events
result.chatId = chatId result.chatId = chatId
result.belongsToCommunity = belongsToCommunity result.belongsToCommunity = belongsToCommunity
result.isUsersListAvailable = isUsersListAvailable
result.chatService = chatService result.chatService = chatService
result.communityService = communityService result.communityService = communityService
result.messageService = messageService result.messageService = messageService
@ -67,4 +69,7 @@ method unpinMessage*(self: Controller, messageId: string) =
method getMessageDetails*(self: Controller, messageId: string): method getMessageDetails*(self: Controller, messageId: string):
tuple[message: MessageDto, reactions: seq[ReactionDto], error: string] = tuple[message: MessageDto, reactions: seq[ReactionDto], error: string] =
return self.messageService.getDetailsForMessage(self.chatId, messageId) return self.messageService.getDetailsForMessage(self.chatId, messageId)
method isUsersListAvailable*(self: Controller): bool =
return self.isUsersListAvailable

View File

@ -21,4 +21,7 @@ method unpinMessage*(self: AccessInterface, messageId: string) {.base.} =
method getMessageDetails*(self: AccessInterface, messageId: string): method getMessageDetails*(self: AccessInterface, messageId: string):
tuple[message: MessageDto, reactions: seq[ReactionDto], error: string] {.base.} = tuple[message: MessageDto, reactions: seq[ReactionDto], error: string] {.base.} =
raise newException(ValueError, "No implementation available")
method isUsersListAvailable*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -10,6 +10,7 @@ import input_area/module as input_area_module
import messages/module as messages_module import messages/module as messages_module
import users/module as users_module import users/module as users_module
import ../../../../../app_service/service/contacts/service as contact_service
import ../../../../../app_service/service/chat/service as chat_service import ../../../../../app_service/service/chat/service as chat_service
import ../../../../../app_service/service/community/service as community_service import ../../../../../app_service/service/community/service as community_service
import ../../../../../app_service/service/message/service as message_service import ../../../../../app_service/service/message/service as message_service
@ -32,7 +33,8 @@ type
usersModule: users_module.AccessInterface usersModule: users_module.AccessInterface
moduleLoaded: bool moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitter, chatId: string, belongsToCommunity: bool, proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitter, sectionId: string, chatId: string,
belongsToCommunity: bool, isUsersListAvailable: bool, contactService: contact_service.Service,
chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface, chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface,
messageService: message_service.Service): messageService: message_service.Service):
Module = Module =
@ -40,14 +42,15 @@ proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitt
result.delegate = delegate result.delegate = delegate
result.view = view.newView(result) result.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, chatId, belongsToCommunity, chatService, communityService, result.controller = controller.newController(result, events, chatId, belongsToCommunity, isUsersListAvailable,
messageService) chatService, communityService, messageService)
result.moduleLoaded = false result.moduleLoaded = false
result.inputAreaModule = input_area_module.newModule(result, chatId, belongsToCommunity, chatService, communityService) result.inputAreaModule = input_area_module.newModule(result, chatId, belongsToCommunity, chatService, communityService)
result.messagesModule = messages_module.newModule(result, events, chatId, belongsToCommunity, chatService, result.messagesModule = messages_module.newModule(result, events, chatId, belongsToCommunity, chatService,
communityService, messageService) communityService, messageService)
result.usersModule = users_module.newModule(result, chatId, belongsToCommunity, chatService, communityService) result.usersModule = users_module.newModule(result, events, sectionId, chatId, belongsToCommunity, isUsersListAvailable,
contactService, communityService, messageService)
method delete*(self: Module) = method delete*(self: Module) =
self.inputAreaModule.delete self.inputAreaModule.delete
@ -147,4 +150,6 @@ method onPinMessage*(self: Module, messageId: string) =
return return
self.view.model.appendItem(item) self.view.model.appendItem(item)
method isUsersListAvailable*(self: Module): bool =
self.controller.isUsersListAvailable()

View File

@ -13,4 +13,7 @@ method getUsersModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method unpinMessage*(self: AccessInterface, messageId: string) {.base.} = method unpinMessage*(self: AccessInterface, messageId: string) {.base.} =
raise newException(ValueError, "No implementation available")
method isUsersListAvailable*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -1,33 +1,80 @@
import sequtils, sugar
import controller_interface import controller_interface
import io_interface import io_interface
import ../../../../../../app_service/service/contacts/service as contact_service
import ../../../../../../app_service/service/community/service_interface as community_service import ../../../../../../app_service/service/community/service_interface as community_service
import ../../../../../../app_service/service/message/service as message_service
import eventemitter
export controller_interface export controller_interface
type type
Controller* = ref object of controller_interface.AccessInterface Controller* = ref object of controller_interface.AccessInterface
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
events: EventEmitter
sectionId: string
chatId: string chatId: string
belongsToCommunity: bool belongsToCommunity: bool
isUsersListAvailable: bool #users list is not available for 1:1 chat
contactService: contact_service.Service
communityService: community_service.ServiceInterface communityService: community_service.ServiceInterface
messageService: message_service.Service
proc newController*(delegate: io_interface.AccessInterface, chatId: string, belongsToCommunity: bool, proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter, sectionId: string, chatId: string,
communityService: community_service.ServiceInterface): Controller = belongsToCommunity: bool, isUsersListAvailable: bool, contactService: contact_service.Service,
communityService: community_service.ServiceInterface, messageService: message_service.Service):
Controller =
result = Controller() result = Controller()
result.delegate = delegate result.delegate = delegate
result.events = events
result.sectionId = sectionId
result.chatId = chatId result.chatId = chatId
result.belongsToCommunity = belongsToCommunity result.belongsToCommunity = belongsToCommunity
result.isUsersListAvailable = isUsersListAvailable
result.contactService = contactService
result.communityService = communityService result.communityService = communityService
result.messageService = messageService
method delete*(self: Controller) = method delete*(self: Controller) =
discard discard
method init*(self: Controller) = method init*(self: Controller) =
discard if(self.isUsersListAvailable):
self.events.on(SIGNAL_MESSAGES_LOADED) do(e:Args):
let args = MessagesLoadedArgs(e)
if(self.chatId != args.chatId):
return
method getChatId*(self: Controller): string = self.delegate.newMessagesLoaded(args.messages)
return self.chatId
method belongsToCommunity*(self: Controller): bool = self.events.on(SIGNAL_CONTACT_NICKNAME_CHANGED) do(e: Args):
return self.belongsToCommunity var args = ContactNicknameUpdatedArgs(e)
self.delegate.contactNicknameChanged(args.contactId, args.nickname)
self.events.on(SIGNAL_CONTACTS_STATUS_UPDATED) do(e: Args):
var args = ContactsStatusUpdatedArgs(e)
self.delegate.contactsStatusUpdated(args.statusUpdates)
self.events.on(SIGNAL_CONTACT_UPDATED) do(e: Args):
var args = ContactUpdatedArgs(e)
self.delegate.contactUpdated(args.contact)
self.events.on(SIGNAL_LOGGEDIN_USER_IMAGE_CHANGED) do(e: Args):
self.delegate.loggedInUserImageChanged()
method getMembersPublicKeys*(self: Controller): seq[string] =
# in case of chat section, there is no a members list
if(not self.belongsToCommunity):
return
let communityDto = self.communityService.getCommunityById(self.sectionId)
result = communityDto.members.map(x => x.id)
method getContactNameAndImage*(self: Controller, contactId: string):
tuple[name: string, image: string, isIdenticon: bool] =
return self.contactService.getContactNameAndImage(contactId)
method getStatusForContact*(self: Controller, contactId: string): StatusUpdateDto =
return self.contactService.getStatusForContactWithId(contactId)

View File

@ -1,4 +1,4 @@
import ../../../../../../app_service/service/community/service_interface as community_service import ../../../../../../app_service/service/contacts/service as contacts_service
type type
AccessInterface* {.pure inheritable.} = ref object of RootObj AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -13,7 +13,12 @@ method init*(self: AccessInterface) {.base.} =
method getChatId*(self: AccessInterface): string {.base.} = method getChatId*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method belongsToCommunity*(self: AccessInterface): bool {.base.} = method getMembersPublicKeys*(self: AccessInterface): seq[string] {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getContactNameAndImage*(self: AccessInterface, contactId: string):
tuple[name: string, image: string, isIdenticon: bool] {.base.} =
raise newException(ValueError, "No implementation available")
method getStatusForContact*(self: AccessInterface, contactId: string): StatusUpdateDto {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,24 +1,26 @@
type type
OnlineStatus* {.pure.} = enum OnlineStatus* {.pure.} = enum
Online = 0 Offline = 0
Idle Online
DoNotDisturb DoNotDisturb
Idle
Invisible Invisible
Offline
type type
Item* = ref object Item* = ref object
id: string id: string
name: string name: string
onlineStatus: OnlineStatus onlineStatus: OnlineStatus
identicon: string icon: string
isIdenticon: bool
proc initItem*(id: string, name: string, onlineStatus: OnlineStatus, identicon: string): Item = proc initItem*(id: string, name: string, onlineStatus: OnlineStatus, icon: string, isidenticon: bool): Item =
result = Item() result = Item()
result.id = id result.id = id
result.name = name result.name = name
result.onlineStatus = onlineStatus result.onlineStatus = onlineStatus
result.identicon = identicon result.icon = icon
result.isIdenticon = isidenticon
proc id*(self: Item): string {.inline.} = proc id*(self: Item): string {.inline.} =
self.id self.id
@ -35,5 +37,14 @@ proc onlineStatus*(self: Item): OnlineStatus {.inline.} =
proc `onlineStatus=`*(self: Item, value: OnlineStatus) {.inline.} = proc `onlineStatus=`*(self: Item, value: OnlineStatus) {.inline.} =
self.onlineStatus = value self.onlineStatus = value
proc identicon*(self: Item): string {.inline.} = proc icon*(self: Item): string {.inline.} =
self.identicon self.icon
proc `icon=`*(self: Item, value: string) {.inline.} =
self.icon = value
proc isIdenticon*(self: Item): bool {.inline.} =
self.isIdenticon
proc `isIdenticon=`*(self: Item, value: bool) {.inline.} =
self.isIdenticon = value

View File

@ -7,7 +7,8 @@ type
Id = UserRole + 1 Id = UserRole + 1
Name Name
OnlineStatus OnlineStatus
Identicon Icon
IsIdenticon
QtObject: QtObject:
type type
@ -33,7 +34,8 @@ QtObject:
ModelRole.Id.int:"id", ModelRole.Id.int:"id",
ModelRole.Name.int:"name", ModelRole.Name.int:"name",
ModelRole.OnlineStatus.int:"onlineStatus", ModelRole.OnlineStatus.int:"onlineStatus",
ModelRole.Identicon.int:"identicon", ModelRole.Icon.int:"icon",
ModelRole.IsIdenticon.int:"isIdenticon",
}.toTable }.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant = method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -53,13 +55,30 @@ QtObject:
result = newQVariant(item.name) result = newQVariant(item.name)
of ModelRole.OnlineStatus: of ModelRole.OnlineStatus:
result = newQVariant(item.onlineStatus.int) result = newQVariant(item.onlineStatus.int)
of ModelRole.Identicon: of ModelRole.Icon:
result = newQVariant(item.identicon) result = newQVariant(item.icon)
of ModelRole.IsIdenticon:
result = newQVariant(item.isIdenticon)
proc setItems*(self: Model, items: seq[Item]) = proc addItem*(self: Model, item: Item) =
self.beginResetModel() # we need to maintain online contact on top, that means
self.items = items # if we add an item online status we add it as the last online item (before the first offline item)
self.endResetModel() # if we add an item with offline status we add it as the first offline item (after the last online item)
var position = -1
for i in 0 ..< self.items.len:
if(self.items[i].onlineStatus == OnlineStatus.Offline):
position = i
break
if(position == -1):
position = self.items.len
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, position, position)
self.items.insert(item, position)
self.endInsertRows()
proc findIndexForMessageId(self: Model, id: string): int = proc findIndexForMessageId(self: Model, id: string): int =
for i in 0 ..< self.items.len: for i in 0 ..< self.items.len:
@ -68,6 +87,17 @@ QtObject:
return -1 return -1
proc removeItemWithIndex(self: Model, index: int) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginRemoveRows(parentModelIndex, index, index)
self.items.delete(index)
self.endRemoveRows()
proc isContactWithIdAdded*(self: Model, id: string): bool =
return self.findIndexForMessageId(id) != -1
proc setName*(self: Model, id: string, name: string) = proc setName*(self: Model, id: string, name: string) =
let ind = self.findIndexForMessageId(id) let ind = self.findIndexForMessageId(id)
if(ind == -1): if(ind == -1):
@ -78,12 +108,38 @@ QtObject:
let index = self.createIndex(ind, 0, nil) let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.Name.int]) self.dataChanged(index, index, @[ModelRole.Name.int])
proc setIcon*(self: Model, id: string, icon: string, isIdenticon: bool) =
let ind = self.findIndexForMessageId(id)
if(ind == -1):
return
self.items[ind].icon = icon
self.items[ind].isIdenticon = isIdenticon
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.Icon.int, ModelRole.IsIdenticon.int])
proc updateItem*(self: Model, id: string, name: string, icon: string, isIdenticon: bool) =
let ind = self.findIndexForMessageId(id)
if(ind == -1):
return
self.items[ind].name = name
self.items[ind].icon = icon
self.items[ind].isIdenticon = isIdenticon
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.Name.int, ModelRole.Icon.int, ModelRole.IsIdenticon.int])
proc setOnlineStatus*(self: Model, id: string, onlineStatus: OnlineStatus) = proc setOnlineStatus*(self: Model, id: string, onlineStatus: OnlineStatus) =
let ind = self.findIndexForMessageId(id) let ind = self.findIndexForMessageId(id)
if(ind == -1): if(ind == -1):
return return
self.items[ind].onlineStatus = onlineStatus if(self.items[ind].onlineStatus == onlineStatus):
return
let index = self.createIndex(ind, 0, nil)
self.dataChanged(index, index, @[ModelRole.OnlineStatus.int]) var item = self.items[ind]
item.onlineStatus = onlineStatus
self.removeItemWithIndex(ind)
self.addItem(item)

View File

@ -1,11 +1,14 @@
import NimQml import NimQml
import io_interface import io_interface
import ../io_interface as delegate_interface import ../io_interface as delegate_interface
import view, controller import view, item, model, controller
import ../../../../../global/global_singleton import ../../../../../global/global_singleton
import ../../../../../../app_service/service/chat/service_interface as chat_service import ../../../../../../app_service/service/contacts/service as contact_service
import ../../../../../../app_service/service/community/service_interface as community_service import ../../../../../../app_service/service/community/service_interface as community_service
import ../../../../../../app_service/service/message/service as message_service
import eventemitter
export io_interface export io_interface
@ -17,14 +20,16 @@ type
controller: controller.AccessInterface controller: controller.AccessInterface
moduleLoaded: bool moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface, chatId: string, belongsToCommunity: bool, proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitter, sectionId: string, chatId: string,
chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface): belongsToCommunity: bool, isUsersListAvailable: bool, contactService: contact_service.Service,
communityService: community_service.ServiceInterface, messageService: message_service.Service):
Module = Module =
result = Module() result = Module()
result.delegate = delegate result.delegate = delegate
result.view = view.newView(result) result.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, chatId, belongsToCommunity, communityService) result.controller = controller.newController(result, events, sectionId, chatId, belongsToCommunity, isUsersListAvailable,
contactService, communityService, messageService)
result.moduleLoaded = false result.moduleLoaded = false
method delete*(self: Module) = method delete*(self: Module) =
@ -33,8 +38,6 @@ method delete*(self: Module) =
self.controller.delete self.controller.delete
method load*(self: Module) = method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("usersModule", self.viewVariant)
self.controller.init() self.controller.init()
self.view.load() self.view.load()
@ -42,8 +45,56 @@ method isLoaded*(self: Module): bool =
return self.moduleLoaded return self.moduleLoaded
method viewDidLoad*(self: Module) = method viewDidLoad*(self: Module) =
# add me as the first user to the list
let loggedInUserDisplayName = singletonInstance.userProfile.getName() & "(You)"
self.view.model().addItem(initItem(singletonInstance.userProfile.getPubKey(), loggedInUserDisplayName,
OnlineStatus.Online, singletonInstance.userProfile.getIcon(), singletonInstance.userProfile.getIsIdenticon()))
# add other memebers
let usersKeys = self.controller.getMembersPublicKeys()
for k in usersKeys:
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(k)
let statusUpdateDto = self.controller.getStatusForContact(k)
let status = statusUpdateDto.statusType.int.OnlineStatus
self.view.model().addItem(initItem(k, name, status, image, isidenticon))
self.moduleLoaded = true self.moduleLoaded = true
self.delegate.usersDidLoad() self.delegate.usersDidLoad()
method getModuleAsVariant*(self: Module): QVariant = method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant return self.viewVariant
method newMessagesLoaded*(self: Module, messages: seq[MessageDto]) =
for m in messages:
if(self.view.model().isContactWithIdAdded(m.`from`)):
continue
let (name, image, isIdenticon) = self.controller.getContactNameAndImage(m.`from`)
let statusUpdateDto = self.controller.getStatusForContact(m.`from`)
let status = statusUpdateDto.statusType.int.OnlineStatus
self.view.model().addItem(initItem(m.`from`, name, status, image, isidenticon))
method contactNicknameChanged*(self: Module, publicKey: string, nickname: string) =
if(nickname.len == 0):
let (name, _, _) = self.controller.getContactNameAndImage(publicKey)
self.view.model().setName(publicKey, name)
else:
self.view.model().setName(publicKey, nickname)
method contactsStatusUpdated*(self: Module, statusUpdates: seq[StatusUpdateDto]) =
for s in statusUpdates:
let status = s.statusType.int.OnlineStatus
self.view.model().setOnlineStatus(s.publicKey, status)
method contactUpdated*(self: Module, contact: ContactsDto) =
var icon = contact.identicon
var isIdenticon = contact.identicon.len > 0
if(contact.image.thumbnail.len > 0):
icon = contact.image.thumbnail
isIdenticon = false
self.view.model().updateItem(contact.id, contact.userNameOrAlias(), icon, isIdenticon)
method loggedInUserImageChanged*(self: Module) =
self.view.model().setIcon(singletonInstance.userProfile.getPubKey(), singletonInstance.userProfile.getThumbnailImage(),
singletonInstance.userProfile.getIsIdenticon())

View File

@ -0,0 +1,17 @@
import ../../../../../../../app_service/service/message/dto/[message]
import ../../../../../../../app_service/service/contacts/dto/[contacts, status_update]
method newMessagesLoaded*(self: AccessInterface, messages: seq[MessageDto]) {.base.} =
raise newException(ValueError, "No implementation available")
method contactNicknameChanged*(self: AccessInterface, publicKey: string, nickname: string) {.base.} =
raise newException(ValueError, "No implementation available")
method contactsStatusUpdated*(self: AccessInterface, statusUpdates: seq[StatusUpdateDto]) {.base.} =
raise newException(ValueError, "No implementation available")
method contactUpdated*(self: AccessInterface, contact: ContactsDto) {.base.} =
raise newException(ValueError, "No implementation available")
method loggedInUserImageChanged*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -7,9 +7,11 @@ QtObject:
View* = ref object of QObject View* = ref object of QObject
delegate: io_interface.AccessInterface delegate: io_interface.AccessInterface
model: Model model: Model
modelVariant: QVariant
proc delete*(self: View) = proc delete*(self: View) =
self.model.delete self.model.delete
self.modelVariant.delete
self.QObject.delete self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View = proc newView*(delegate: io_interface.AccessInterface): View =
@ -17,6 +19,19 @@ QtObject:
result.QObject.setup result.QObject.setup
result.delegate = delegate result.delegate = delegate
result.model = newModel() result.model = newModel()
result.modelVariant = newQVariant(result.model)
proc model*(self: View): Model =
return self.model
proc load*(self: View) = proc load*(self: View) =
self.delegate.viewDidLoad() self.delegate.viewDidLoad()
proc modelChanged*(self: View) {.signal.}
proc getModel(self: View): QVariant {.slot.} =
return self.modelVariant
QtProperty[QVariant] model:
read = getModel
notify = modelChanged

View File

@ -52,4 +52,7 @@ QtObject:
read = getModel read = getModel
proc unpinMessage*(self: View, messageId: string) {.slot.} = proc unpinMessage*(self: View, messageId: string) {.slot.} =
self.delegate.unpinMessage(messageId) self.delegate.unpinMessage(messageId)
proc isUsersListAvailable*(self: View): bool {.slot.} =
return self.delegate.isUsersListAvailable()

View File

@ -3,6 +3,7 @@ import Tables
import controller_interface import controller_interface
import io_interface import io_interface
import ../../../../app_service/service/contacts/service as contact_service
import ../../../../app_service/service/chat/service_interface as chat_service import ../../../../app_service/service/chat/service_interface as chat_service
import ../../../../app_service/service/community/service_interface as community_service import ../../../../app_service/service/community/service_interface as community_service
import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/message/service as message_service
@ -19,19 +20,20 @@ type
activeItemId: string activeItemId: string
activeSubItemId: string activeSubItemId: string
events: EventEmitter events: EventEmitter
contactService: contact_service.Service
chatService: chat_service.ServiceInterface chatService: chat_service.ServiceInterface
communityService: community_service.ServiceInterface communityService: community_service.ServiceInterface
messageService: message_service.Service messageService: message_service.Service
proc newController*(delegate: io_interface.AccessInterface, sectionId: string, isCommunity: bool, events: EventEmitter, proc newController*(delegate: io_interface.AccessInterface, sectionId: string, isCommunity: bool, events: EventEmitter,
chatService: chat_service.ServiceInterface, contactService: contact_service.Service, chatService: chat_service.ServiceInterface,
communityService: community_service.ServiceInterface, communityService: community_service.ServiceInterface, messageService: message_service.Service): Controller =
messageService: message_service.Service): Controller =
result = Controller() result = Controller()
result.delegate = delegate result.delegate = delegate
result.sectionId = sectionId result.sectionId = sectionId
result.isCommunitySection = isCommunity result.isCommunitySection = isCommunity
result.events = events result.events = events
result.contactService = contactService
result.chatService = chatService result.chatService = chatService
result.communityService = communityService result.communityService = communityService
result.messageService = messageService result.messageService = messageService
@ -87,5 +89,5 @@ method getOneToOneChatNameAndImage*(self: Controller, chatId: string):
method createPublicChat*(self: Controller, chatId: string) = method createPublicChat*(self: Controller, chatId: string) =
let response = self.chatService.createPublicChat(chatId) let response = self.chatService.createPublicChat(chatId)
if(response.success): if(response.success):
self.delegate.addNewPublicChat(response.chatDto, self.events, self.chatService, self.communityService, self.delegate.addNewPublicChat(response.chatDto, self.events, self.contactService, self.chatService,
self.messageService) self.communityService, self.messageService)

View File

@ -5,6 +5,7 @@ import view, controller, item, sub_item, model, sub_model
import chat_content/module as chat_content_module import chat_content/module as chat_content_module
import ../../../../app_service/service/contacts/service as contact_service
import ../../../../app_service/service/chat/service_interface as chat_service import ../../../../app_service/service/chat/service_interface as chat_service
import ../../../../app_service/service/community/service_interface as community_service import ../../../../app_service/service/community/service_interface as community_service
import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/message/service as message_service
@ -31,6 +32,7 @@ proc newModule*(
events: EventEmitter, events: EventEmitter,
sectionId: string, sectionId: string,
isCommunity: bool, isCommunity: bool,
contactService: contact_service.Service,
chatService: chat_service.ServiceInterface, chatService: chat_service.ServiceInterface,
communityService: community_service.ServiceInterface, communityService: community_service.ServiceInterface,
messageService: message_service.Service messageService: message_service.Service
@ -39,8 +41,8 @@ proc newModule*(
result.delegate = delegate result.delegate = delegate
result.view = view.newView(result) result.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, sectionId, isCommunity, events, chatService, communityService, result.controller = controller.newController(result, sectionId, isCommunity, events, contactService, chatService,
messageService) communityService, messageService)
result.moduleLoaded = false result.moduleLoaded = false
result.chatContentModule = initOrderedTable[string, chat_content_module.AccessInterface]() result.chatContentModule = initOrderedTable[string, chat_content_module.AccessInterface]()
@ -56,14 +58,15 @@ method delete*(self: Module) =
method isCommunity*(self: Module): bool = method isCommunity*(self: Module): bool =
return self.controller.isCommunity() return self.controller.isCommunity()
proc addSubmodule(self: Module, chatId: string, belongToCommunity: bool, events: EventEmitter, proc addSubmodule(self: Module, chatId: string, belongToCommunity: bool, isUsersListAvailable: bool, events: EventEmitter,
contactService: contact_service.Service, chatService: chat_service.ServiceInterface,
communityService: community_service.ServiceInterface, messageService: message_service.Service) =
self.chatContentModule[chatId] = chat_content_module.newModule(self, events, self.controller.getMySectionId(), chatId,
belongToCommunity, isUsersListAvailable, contactService, chatService, communityService, messageService)
proc buildChatUI(self: Module, events: EventEmitter, contactService: contact_service.Service,
chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface, chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface,
messageService: message_service.Service) = messageService: message_service.Service) =
self.chatContentModule[chatId] = chat_content_module.newModule(self, events, chatId, belongToCommunity, chatService,
communityService, messageService)
proc buildChatUI(self: Module, events: EventEmitter, chatService: chat_service.ServiceInterface,
communityService: community_service.ServiceInterface, messageService: message_service.Service) =
let types = @[ChatType.OneToOne, ChatType.Public, ChatType.PrivateGroupChat] let types = @[ChatType.OneToOne, ChatType.Public, ChatType.PrivateGroupChat]
let chats = self.controller.getChatDetailsForChatTypes(types) let chats = self.controller.getChatDetailsForChatTypes(types)
@ -74,13 +77,15 @@ proc buildChatUI(self: Module, events: EventEmitter, chatService: chat_service.S
var chatName = c.name var chatName = c.name
var chatImage = c.identicon var chatImage = c.identicon
var isIdenticon = false var isIdenticon = false
var isUsersListAvailable = true
if(c.chatType == ChatType.OneToOne): if(c.chatType == ChatType.OneToOne):
isUsersListAvailable = false
(chatName, chatImage, isIdenticon) = self.controller.getOneToOneChatNameAndImage(c.id) (chatName, chatImage, isIdenticon) = self.controller.getOneToOneChatNameAndImage(c.id)
let item = initItem(c.id, chatName, chatImage, isIdenticon, c.color, c.description, c.chatType.int, hasNotification, let item = initItem(c.id, chatName, chatImage, isIdenticon, c.color, c.description, c.chatType.int, hasNotification,
notificationsCount, c.muted, false, 0) notificationsCount, c.muted, false, 0)
self.view.appendItem(item) self.view.appendItem(item)
self.addSubmodule(c.id, false, events, chatService, communityService, messageService) self.addSubmodule(c.id, false, isUsersListAvailable, events, contactService, chatService, communityService, messageService)
# make the first Public chat active when load the app # make the first Public chat active when load the app
if(selectedItemId.len == 0 and c.chatType == ChatType.Public): if(selectedItemId.len == 0 and c.chatType == ChatType.Public):
@ -88,8 +93,9 @@ proc buildChatUI(self: Module, events: EventEmitter, chatService: chat_service.S
self.setActiveItemSubItem(selectedItemId, "") self.setActiveItemSubItem(selectedItemId, "")
proc buildCommunityUI(self: Module, events: EventEmitter, chatService: chat_service.ServiceInterface, proc buildCommunityUI(self: Module, events: EventEmitter, contactService: contact_service.Service,
communityService: community_service.ServiceInterface, messageService: message_service.Service) = chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface,
messageService: message_service.Service) =
var selectedItemId = "" var selectedItemId = ""
var selectedSubItemId = "" var selectedSubItemId = ""
let communityIds = self.controller.getCommunityIds() let communityIds = self.controller.getCommunityIds()
@ -107,7 +113,7 @@ proc buildCommunityUI(self: Module, events: EventEmitter, chatService: chat_serv
let channelItem = initItem(chatDto.id, chatDto.name, chatDto.identicon, false, chatDto.color, chatDto.description, let channelItem = initItem(chatDto.id, chatDto.name, chatDto.identicon, false, chatDto.color, chatDto.description,
chatDto.chatType.int, hasNotification, notificationsCount, chatDto.muted, false, c.position) chatDto.chatType.int, hasNotification, notificationsCount, chatDto.muted, false, c.position)
self.view.appendItem(channelItem) self.view.appendItem(channelItem)
self.addSubmodule(chatDto.id, true, events, chatService, communityService, messageService) self.addSubmodule(chatDto.id, true, false, events, contactService, chatService, communityService, messageService)
# make the first channel which doesn't belong to any category active when load the app # make the first channel which doesn't belong to any category active when load the app
if(selectedItemId.len == 0): if(selectedItemId.len == 0):
@ -133,7 +139,7 @@ proc buildCommunityUI(self: Module, events: EventEmitter, chatService: chat_serv
let channelItem = initSubItem(chatDto.id, cat.id, chatDto.name, chatDto.identicon, false, chatDto.color, let channelItem = initSubItem(chatDto.id, cat.id, chatDto.name, chatDto.identicon, false, chatDto.color,
chatDto.description, hasNotification, notificationsCount, chatDto.muted, false, c.position) chatDto.description, hasNotification, notificationsCount, chatDto.muted, false, c.position)
categoryChannels.add(channelItem) categoryChannels.add(channelItem)
self.addSubmodule(chatDto.id, true, events, chatService, communityService, messageService) self.addSubmodule(chatDto.id, true, false, events, contactService, chatService, communityService, messageService)
# in case there is no channels beyond categories, # in case there is no channels beyond categories,
# make the first channel of the first category active when load the app # make the first channel of the first category active when load the app
@ -148,15 +154,16 @@ proc buildCommunityUI(self: Module, events: EventEmitter, chatService: chat_serv
self.setActiveItemSubItem(selectedItemId, selectedSubItemId) self.setActiveItemSubItem(selectedItemId, selectedSubItemId)
method load*(self: Module, events: EventEmitter, chatService: chat_service.ServiceInterface, method load*(self: Module, events: EventEmitter, contactService: contact_service.Service,
communityService: community_service.ServiceInterface, messageService: message_service.Service) = chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface,
messageService: message_service.Service) =
self.controller.init() self.controller.init()
self.view.load() self.view.load()
if(self.controller.isCommunity()): if(self.controller.isCommunity()):
self.buildCommunityUI(events, chatService, communityService, messageService) self.buildCommunityUI(events, contactService, chatService, communityService, messageService)
else: else:
self.buildChatUI(events, chatService, communityService, messageService) self.buildChatUI(events, contactService, chatService, communityService, messageService)
for cModule in self.chatContentModule.values: for cModule in self.chatContentModule.values:
cModule.load() cModule.load()
@ -230,14 +237,15 @@ method createPublicChat*(self: Module, chatId: string) =
self.controller.createPublicChat(chatId) self.controller.createPublicChat(chatId)
method addNewPublicChat*(self: Module, chatDto: ChatDto, events: EventEmitter, chatService: chat_service.ServiceInterface, method addNewPublicChat*(self: Module, chatDto: ChatDto, events: EventEmitter, contactService: contact_service.Service,
communityService: community_service.ServiceInterface, messageService: message_service.Service) = chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface,
messageService: message_service.Service) =
let hasNotification = chatDto.unviewedMessagesCount > 0 or chatDto.unviewedMentionsCount > 0 let hasNotification = chatDto.unviewedMessagesCount > 0 or chatDto.unviewedMentionsCount > 0
let notificationsCount = chatDto.unviewedMentionsCount let notificationsCount = chatDto.unviewedMentionsCount
let item = initItem(chatDto.id, chatDto.name, chatDto.identicon, true, chatDto.color, chatDto.description, let item = initItem(chatDto.id, chatDto.name, chatDto.identicon, true, chatDto.color, chatDto.description,
chatDto.chatType.int, hasNotification, notificationsCount, chatDto.muted, false, 0) chatDto.chatType.int, hasNotification, notificationsCount, chatDto.muted, false, 0)
self.view.appendItem(item) self.view.appendItem(item)
self.addSubmodule(chatDto.id, false, events, chatService, communityService, messageService) self.addSubmodule(chatDto.id, false, true, events, contactService, chatService, communityService, messageService)
# make new added chat active one # make new added chat active one
self.setActiveItemSubItem(item.id, "") self.setActiveItemSubItem(item.id, "")

View File

@ -1,5 +1,6 @@
import NimQml import NimQml
import ../../../../../app_service/service/contacts/service as contact_service
import ../../../../../app_service/service/chat/service_interface as chat_service import ../../../../../app_service/service/chat/service_interface as chat_service
import ../../../../../app_service/service/community/service_interface as community_service import ../../../../../app_service/service/community/service_interface as community_service
import ../../../../../app_service/service/message/service as message_service import ../../../../../app_service/service/message/service as message_service
@ -9,8 +10,9 @@ import eventemitter
method delete*(self: AccessInterface) {.base.} = method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface, events: EventEmitter, chatService: chat_service.ServiceInterface, method load*(self: AccessInterface, events: EventEmitter, contactService: contact_service.Service,
communityService: community_service.ServiceInterface, messageService: message_service.Service) {.base.} = chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface,
messageService: message_service.Service) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} = method isLoaded*(self: AccessInterface): bool {.base.} =

View File

@ -2,6 +2,6 @@ method activeItemSubItemSet*(self: AccessInterface, itemId: string, subItemId: s
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method addNewPublicChat*(self: AccessInterface, chatDto: ChatDto, events: EventEmitter, method addNewPublicChat*(self: AccessInterface, chatDto: ChatDto, events: EventEmitter,
chatService: chat_service.ServiceInterface, communityService: community_service.ServiceInterface, contactService: contact_service.Service, chatService: chat_service.ServiceInterface,
messageService: message_service.Service) {.base.} = communityService: community_service.ServiceInterface, messageService: message_service.Service) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -84,8 +84,8 @@ proc newModule*[T](
result.moduleLoaded = false result.moduleLoaded = false
# Submodules # Submodules
result.chatSectionModule = chat_section_module.newModule(result, events, conf.CHAT_SECTION_ID, false, chatService, result.chatSectionModule = chat_section_module.newModule(result, events, conf.CHAT_SECTION_ID, false, contactsService,
communityService, messageService) chatService, communityService, messageService)
result.communitySectionsModule = initOrderedTable[string, chat_section_module.AccessInterface]() result.communitySectionsModule = initOrderedTable[string, chat_section_module.AccessInterface]()
result.walletSectionModule = wallet_section_module.newModule[Module[T]](result, events, tokenService, result.walletSectionModule = wallet_section_module.newModule[Module[T]](result, events, tokenService,
transactionService, collectible_service, walletAccountService, settingsService) transactionService, collectible_service, walletAccountService, settingsService)
@ -114,6 +114,7 @@ method delete*[T](self: Module[T]) =
method load*[T]( method load*[T](
self: Module[T], self: Module[T],
events: EventEmitter, events: EventEmitter,
contactsService: contacts_service.Service,
chatService: chat_service.Service, chatService: chat_service.Service,
communityService: community_service.Service, communityService: community_service.Service,
messageService: message_service.Service messageService: message_service.Service
@ -131,6 +132,7 @@ method load*[T](
events, events,
c.id, c.id,
true, true,
contactsService,
chatService, chatService,
communityService, communityService,
messageService messageService
@ -209,9 +211,9 @@ method load*[T](
activeSection = profileSettingsSectionItem activeSection = profileSettingsSectionItem
# Load all sections # Load all sections
self.chatSectionModule.load(events, chatService, communityService, messageService) self.chatSectionModule.load(events, contactsService, chatService, communityService, messageService)
for cModule in self.communitySectionsModule.values: for cModule in self.communitySectionsModule.values:
cModule.load(events, chatService, communityService, messageService) cModule.load(events, contactsService, chatService, communityService, messageService)
self.walletSectionModule.load() self.walletSectionModule.load()
# self.walletV2SectionModule.load() # self.walletV2SectionModule.load()
self.browserSectionModule.load() self.browserSectionModule.load()

View File

@ -1,3 +1,4 @@
import ../../../../app_service/service/contacts/service as contacts_service
import ../../../../app_service/service/chat/service as chat_service import ../../../../app_service/service/chat/service as chat_service
import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/community/service as community_service
import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/message/service as message_service
@ -10,6 +11,7 @@ method delete*(self: AccessInterface) {.base.} =
method load*( method load*(
self: AccessInterface, self: AccessInterface,
events: EventEmitter, events: EventEmitter,
contactsService: contacts_service.Service,
chatService: chat_service.Service, chatService: chat_service.Service,
communityService: community_service.Service, communityService: community_service.Service,
messageService: message_service.Service messageService: message_service.Service

View File

@ -17,6 +17,14 @@ template getProp(obj: JsonNode, prop: string, value: var typedesc[int64]): bool
success success
template getProp(obj: JsonNode, prop: string, value: var typedesc[uint64]): bool =
var success = false
if (obj.kind == JObject and obj.contains(prop)):
value = uint64(obj[prop].getBiggestInt)
success = true
success
template getProp(obj: JsonNode, prop: string, value: var typedesc[string]): bool = template getProp(obj: JsonNode, prop: string, value: var typedesc[string]): bool =
var success = false var success = false
if (obj.kind == JObject and obj.contains(prop)): if (obj.kind == JObject and obj.contains(prop)):

View File

@ -1,3 +1,4 @@
import os
import status/ens as status_ens import status/ens as status_ens
include ../../common/json_utils include ../../common/json_utils
@ -17,3 +18,16 @@ const lookupContactTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
if not id.startsWith("0x"): if not id.startsWith("0x"):
id = status_ens.pubkey(id) id = status_ens.pubkey(id)
arg.finish(id) arg.finish(id)
#################################################
# Async timer
#################################################
type
TimerTaskArg = ref object of QObjectTaskArg
timeoutInMilliseconds: int
const timerTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[TimerTaskArg](argEncoded)
sleep(arg.timeoutInMilliseconds)
arg.finish("done")

View File

@ -17,6 +17,7 @@ type ContactsDto* = object
alias*: string alias*: string
identicon*: string identicon*: string
lastUpdated*: int64 lastUpdated*: int64
lastUpdatedLocally*: int64
localNickname*: string localNickname*: string
image*: Images image*: Images
added*: bool added*: bool
@ -36,9 +37,10 @@ proc `$`*(self: ContactsDto): string =
id: {self.id}, id: {self.id},
name: {self.name}, name: {self.name},
ensVerified: {self.ensVerified}, ensVerified: {self.ensVerified},
alias: {self.alias}, alias: {self.alias},
identicon: {self.identicon}, identicon: {self.identicon},
lastUpdated: {self.lastUpdated}, lastUpdated: {self.lastUpdated},
lastUpdatedLocally: {self.lastUpdatedLocally},
localNickname: {self.localNickname}, localNickname: {self.localNickname},
image:[ image:[
{$self.image} {$self.image}
@ -69,6 +71,7 @@ proc toContactsDto*(jsonObj: JsonNode): ContactsDto =
discard jsonObj.getProp("alias", result.alias) discard jsonObj.getProp("alias", result.alias)
discard jsonObj.getProp("identicon", result.identicon) discard jsonObj.getProp("identicon", result.identicon)
discard jsonObj.getProp("lastUpdated", result.lastUpdated) discard jsonObj.getProp("lastUpdated", result.lastUpdated)
discard jsonObj.getProp("lastUpdatedLocally", result.lastUpdatedLocally)
discard jsonObj.getProp("localNickname", result.localNickname) discard jsonObj.getProp("localNickname", result.localNickname)
var imageObj: JsonNode var imageObj: JsonNode

View File

@ -0,0 +1,26 @@
import json
include ../../../common/json_utils
type StatusType* {.pure.}= enum
Offline = 0,
Online
DoNotDisturb
Idle
Invisible
type StatusUpdateDto* = object
publicKey*: string
statusType*: StatusType
clock*: uint64
text*: string
proc toStatusUpdateDto*(jsonObj: JsonNode): StatusUpdateDto =
discard jsonObj.getProp("publicKey", result.publicKey)
discard jsonObj.getProp("clock", result.clock)
discard jsonObj.getProp("text", result.text)
result.statusType = StatusType.Offline
var statusTypeInt: int
if (jsonObj.getProp("statusType", statusTypeInt) and
(statusTypeInt >= ord(low(StatusType)) or statusTypeInt <= ord(high(StatusType)))):
result.statusType = StatusType(statusTypeInt)

View File

@ -1,15 +1,18 @@
import NimQml, Tables, json, sequtils, strformat, chronicles, strutils import NimQml, Tables, json, sequtils, strformat, chronicles, strutils, times
import eventemitter import eventemitter
import ../../../app/global/global_singleton
import ../../../app/core/signals/types
import ../../../app/core/tasks/[qt, threadpool] import ../../../app/core/tasks/[qt, threadpool]
import ./dto/contacts as contacts_dto import ./dto/contacts as contacts_dto
import ./dto/status_update as status_update_dto
import status/statusgo_backend_new/contacts as status_contacts import status/statusgo_backend_new/contacts as status_contacts
import status/statusgo_backend_new/accounts as status_accounts import status/statusgo_backend_new/accounts as status_accounts
import status/statusgo_backend_new/chat as status_chat import status/statusgo_backend_new/chat as status_chat
import status/statusgo_backend_new/utils as status_utils import status/statusgo_backend_new/utils as status_utils
export contacts_dto export contacts_dto, status_update_dto
include async_tasks include async_tasks
@ -26,6 +29,17 @@ type
ContactAddedArgs* = ref object of Args ContactAddedArgs* = ref object of Args
contact*: ContactsDto contact*: ContactsDto
ContactUpdatedArgs* = ref object of Args
contact*: ContactsDto
ContactsStatusUpdatedArgs* = ref object of Args
statusUpdates*: seq[StatusUpdateDto]
# Local Constants:
const CheckStatusIntervalInMilliseconds = 5000 # 5 seconds, this is timeout how often do we check for user status.
const OnlineLimitInSeconds = int(5.5 * 60) # 5.5 minutes
const IdleLimitInSeconds = int(7 * 60) # 7 minutes
# Signals which may be emitted by this service: # Signals which may be emitted by this service:
const SIGNAL_CONTACT_LOOKED_UP* = "SIGNAL_CONTACT_LOOKED_UP" const SIGNAL_CONTACT_LOOKED_UP* = "SIGNAL_CONTACT_LOOKED_UP"
# Remove new when old code is removed # Remove new when old code is removed
@ -34,23 +48,43 @@ const SIGNAL_CONTACT_BLOCKED* = "new-contactBlocked"
const SIGNAL_CONTACT_UNBLOCKED* = "new-contactUnblocked" const SIGNAL_CONTACT_UNBLOCKED* = "new-contactUnblocked"
const SIGNAL_CONTACT_REMOVED* = "new-contactRemoved" const SIGNAL_CONTACT_REMOVED* = "new-contactRemoved"
const SIGNAL_CONTACT_NICKNAME_CHANGED* = "new-contactNicknameChanged" const SIGNAL_CONTACT_NICKNAME_CHANGED* = "new-contactNicknameChanged"
const SIGNAL_CONTACTS_STATUS_UPDATED* = "new-contactsStatusUpdated"
const SIGNAL_CONTACT_UPDATED* = "new-contactUpdated"
const SIGNAL_LOGGEDIN_USER_IMAGE_CHANGED* = "new-loggedInUserImageChanged"
QtObject: QtObject:
type Service* = ref object of QObject type Service* = ref object of QObject
threadpool: ThreadPool threadpool: ThreadPool
contacts: Table[string, ContactsDto] # [contact_id, ContactsDto] contacts: Table[string, ContactsDto] # [contact_id, ContactsDto]
contactsStatus: Table[string, StatusUpdateDto] # [contact_id, StatusUpdateDto]
events: EventEmitter events: EventEmitter
closingApp: bool
# Forward declaration
proc getContactById*(self: Service, id: string): ContactsDto
proc saveContact(self: Service, contact: ContactsDto)
proc startCheckingContactStatuses(self: Service)
proc delete*(self: Service) = proc delete*(self: Service) =
self.closingApp = true
self.contacts.clear
self.contactsStatus.clear
self.QObject.delete self.QObject.delete
proc newService*(events: EventEmitter, threadpool: ThreadPool): Service = proc newService*(events: EventEmitter, threadpool: ThreadPool): Service =
new(result, delete) new(result, delete)
result.QObject.setup result.QObject.setup
result.closingApp = false
result.events = events result.events = events
result.threadpool = threadpool result.threadpool = threadpool
result.contacts = initTable[string, ContactsDto]() result.contacts = initTable[string, ContactsDto]()
signalConnect(singletonInstance.userProfile, "imageChanged()", result, "onLoggedInUserImageChange()", 2)
proc addContact(self: Service, contact: ContactsDto) =
# Private proc, used for adding contacts only.
self.contacts[contact.id] = contact
self.contactsStatus[contact.id] = StatusUpdateDto(publicKey: contact.id, statusType: StatusType.Offline)
proc fetchContacts*(self: Service) = proc fetchContacts*(self: Service) =
try: try:
@ -59,15 +93,45 @@ QtObject:
let contacts = map(response.result.getElems(), proc(x: JsonNode): ContactsDto = x.toContactsDto()) let contacts = map(response.result.getElems(), proc(x: JsonNode): ContactsDto = x.toContactsDto())
for contact in contacts: for contact in contacts:
self.contacts[contact.id] = contact self.addContact(contact)
except Exception as e: except Exception as e:
let errDesription = e.msg let errDesription = e.msg
error "error: ", errDesription error "error: ", errDesription
return return
proc doConnect(self: Service) =
self.events.on(SignalType.Message.event) do(e:Args):
var receivedData = MessageSignal(e)
if(receivedData.statusUpdates.len > 0):
for s in receivedData.statusUpdates:
if(not self.contactsStatus.hasKey(s.publicKey)):
# we shouldn't be here ever, but the following line ensures we have added a contact before setting status for it
discard self.getContactById(s.publicKey)
self.contactsStatus[s.publicKey] = s
let data = ContactsStatusUpdatedArgs(statusUpdates: receivedData.statusUpdates)
self.events.emit(SIGNAL_CONTACTS_STATUS_UPDATED, data)
if(receivedData.contacts.len > 0):
for c in receivedData.contacts:
let localContact = self.getContactById(c.id)
var receivedContact = c
receivedContact.localNickname = localContact.localNickname
self.saveContact(receivedContact)
let data = ContactUpdatedArgs(contact: receivedContact)
self.events.emit(SIGNAL_CONTACT_UPDATED, data)
proc init*(self: Service) = proc init*(self: Service) =
self.fetchContacts() self.fetchContacts()
self.doConnect()
self.startCheckingContactStatuses()
proc onLoggedInUserImageChange*(self: Service) {.slot.} =
let data = Args()
self.events.emit(SIGNAL_LOGGEDIN_USER_IMAGE_CHANGED, data)
proc getContacts*(self: Service): seq[ContactsDto] = proc getContacts*(self: Service): seq[ContactsDto] =
return toSeq(self.contacts.values) return toSeq(self.contacts.values)
@ -77,7 +141,10 @@ QtObject:
let response = status_contacts.getContactByID(id) let response = status_contacts.getContactByID(id)
result = response.result.toContactsDto() result = response.result.toContactsDto()
self.contacts[result.id] = result if result.id.len == 0:
return
self.addContact(result)
except Exception as e: except Exception as e:
let errDesription = e.msg let errDesription = e.msg
@ -109,6 +176,15 @@ QtObject:
blocked: false, blocked: false,
hasAddedUs: false hasAddedUs: false
) )
self.addContact(result)
proc getStatusForContactWithId*(self: Service, publicKey: string): StatusUpdateDto =
# This method will fetch current accurate status from `status-go` once we add an api point there for it.
if(not self.contactsStatus.hasKey(publicKey)):
# following line ensures that we have added a contact before setting status for it
discard self.getContactById(publicKey)
return self.contactsStatus[publicKey]
proc getContactNameAndImage*(self: Service, publicKey: string): tuple[name: string, image: string, isIdenticon: bool] = proc getContactNameAndImage*(self: Service, publicKey: string): tuple[name: string, image: string, isIdenticon: bool] =
## This proc should be used accross the app in order to have for the same contact ## This proc should be used accross the app in order to have for the same contact
@ -207,6 +283,8 @@ QtObject:
self.events.emit(SIGNAL_CONTACT_LOOKED_UP, data) self.events.emit(SIGNAL_CONTACT_LOOKED_UP, data)
proc lookupContact*(self: Service, value: string) = proc lookupContact*(self: Service, value: string) =
if(self.closingApp):
return
let arg = LookupContactTaskArg( let arg = LookupContactTaskArg(
tptr: cast[ByteAddress](lookupContactTask), tptr: cast[ByteAddress](lookupContactTask),
vptr: cast[ByteAddress](self.vptr), vptr: cast[ByteAddress](self.vptr),
@ -214,3 +292,42 @@ QtObject:
value: value value: value
) )
self.threadpool.start(arg) self.threadpool.start(arg)
proc checkContactsStatus*(self: Service, response: string) {.slot.} =
let nowInMyLocalZone = now()
let timestampNow = uint64(nowInMyLocalZone.toTime().toUnix())
var updatedStatuses: seq[StatusUpdateDto]
for status in self.contactsStatus.mvalues:
if(timestampNow - status.clock < uint64(OnlineLimitInSeconds)):
if(status.statusType == StatusType.Online):
continue
else:
status.statusType = StatusType.Online
updatedStatuses.add(status)
elif(timestampNow - status.clock < uint64(IdleLimitInSeconds)):
if(status.statusType == StatusType.Idle):
continue
else:
status.statusType = StatusType.Idle
updatedStatuses.add(status)
elif(status.statusType != StatusType.Offline):
status.statusType = StatusType.Offline
updatedStatuses.add(status)
if(updatedStatuses.len > 0):
let data = ContactsStatusUpdatedArgs(statusUpdates: updatedStatuses)
self.events.emit(SIGNAL_CONTACTS_STATUS_UPDATED, data)
self.startCheckingContactStatuses()
proc startCheckingContactStatuses(self: Service) =
if(self.closingApp):
return
let arg = TimerTaskArg(
tptr: cast[ByteAddress](timerTask),
vptr: cast[ByteAddress](self.vptr),
slot: "checkContactsStatus",
timeoutInMilliseconds: CheckStatusIntervalInMilliseconds
)
self.threadpool.start(arg)

View File

@ -29,6 +29,18 @@ StatusAppThreePanelLayout {
// Each `ChatLayout` has its own chatCommunitySectionModule // Each `ChatLayout` has its own chatCommunitySectionModule
// (on the backend chat and community sections share the same module since they are actually the same) // (on the backend chat and community sections share the same module since they are actually the same)
property var chatCommunitySectionModule property var chatCommunitySectionModule
// Since qml component doesn't follow encaptulation from the backend side, we're introducing
// a method which will return appropriate chat content module for selected chat/channel
function currentChatContentModule(){
// When we decide to have the same struct as it's on the backend we will remove this function.
// So far this is a way to deal with refactord backend from the current qml structure.
if(chatCommunitySectionModule.activeItem.isSubItemActive)
chatCommunitySectionModule.prepareChatContentModuleForChatId(chatCommunitySectionModule.activeItem.activeSubItem.id)
else
chatCommunitySectionModule.prepareChatContentModuleForChatId(chatCommunitySectionModule.activeItem.id)
return chatCommunitySectionModule.getChatContentModule()
}
// Not Refactored // Not Refactored
property var messageStore property var messageStore
@ -72,35 +84,37 @@ StatusAppThreePanelLayout {
} }
} }
showRightPanel: (localAccountSensitiveSettings.expandUsersList && (localAccountSensitiveSettings.showOnlineUsers || chatsModel.communities.activeCommunity.active) showRightPanel: {
&& (chatsModel.channelView.activeChannel.chatType !== Constants.chatTypeOneToOne)) // Check if user list is available as an option for particular chat content module.
let usersListAvailable = currentChatContentModule().isUsersListAvailable()
return localAccountSensitiveSettings.showOnlineUsers && usersListAvailable && localAccountSensitiveSettings.expandUsersList
}
rightPanel: localAccountSensitiveSettings.communitiesEnabled && chatCommunitySectionModule.isCommunity()? communityUserListComponent : userListComponent rightPanel: localAccountSensitiveSettings.communitiesEnabled && chatCommunitySectionModule.isCommunity()? communityUserListComponent : userListComponent
Component { Component {
id: communityUserListComponent id: communityUserListComponent
CommunityUserListPanel { CommunityUserListPanel {
//Not Refactored Yet
//currentTime: chatColumn.currentTime
messageContextMenu: quickActionMessageOptionsMenu messageContextMenu: quickActionMessageOptionsMenu
// profilePubKey: userProfile.pubKey usersModule: {
// community: root.rootStore.chatsModelInst.communities.activeCommunity if(chatCommunitySectionModule.activeItem.isSubItemActive)
// currentUserName: Utils.removeStatusEns(root.rootStore.profileModelInst.ens.preferredUsername chatCommunitySectionModule.prepareChatContentModuleForChatId(chatCommunitySectionModule.activeItem.activeSubItem.id)
// || root.rootStore.profileModelInst.profile.username) else
// currentUserOnline: root.store.userProfileInst.userStatus chatCommunitySectionModule.prepareChatContentModuleForChatId(chatCommunitySectionModule.activeItem.id)
// contactsList: root.rootStore.allContacts
return chatCommunitySectionModule.getChatContentModule().usersModule
}
} }
} }
Component { Component {
id: userListComponent id: userListComponent
UserListPanel { UserListPanel {
//Not Refactored Yet
//currentTime: chatColumn.currentTime
//userList: chatColumn.userList
messageContextMenu: quickActionMessageOptionsMenu messageContextMenu: quickActionMessageOptionsMenu
// profilePubKey: userProfile.pubKey usersModule: {
// contactsList: root.rootStore.allContacts chatCommunitySectionModule.prepareChatContentModuleForChatId(chatCommunitySectionModule.activeItem.id)
// isOnline: root.rootStore.chatsModelInst.isOnline return chatCommunitySectionModule.getChatContentModule().usersModule
}
} }
} }

View File

@ -9,95 +9,113 @@ import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1 import StatusQ.Core 0.1
StatusListItem { Item {
id: root id: wrapper
anchors.right: parent.right anchors.right: parent.right
anchors.rightMargin: 8
anchors.left: parent.left anchors.left: parent.left
anchors.leftMargin: 8 height: rectangle.height + 4
implicitHeight: 44
leftPadding: 8
rightPadding: 8
property var contactsList
property int statusType: -1
property string name: ""
property string publicKey: "" property string publicKey: ""
property string profilePubKey: "" property string name: ""
property string identicon: "" property string identicon: ""
property bool isCurrentUser: (publicKey === profilePubKey) property bool isIdenticon: true
property string profileImage: appMain.getProfileImage(publicKey) || "" property int userStatus: Constants.userStatus.offline
property bool highlighted: false
property string lastSeen: ""
property bool isOnline: false
property var currentTime
property var messageContextMenu property var messageContextMenu
property bool enableMouseArea: true
property color color: {
title: isCurrentUser ? qsTr("You") : Emoji.parse(Utils.removeStatusEns(Utils.filterXSS(root.name))) if (wrapper.hovered) {
image.source: profileImage || identicon return Style.current.menuBackgroundHover
image.isIdenticon: !profileImage
image.width: 24
image.height: 24
statusListItemIcon.anchors.topMargin: 10
statusListItemTitle.elide: Text.ElideRight
statusListItemTitle.wrapMode: Text.NoWrap
color: sensor.containsMouse ?
Theme.palette.statusChatListItem.hoverBackgroundColor :
Theme.palette.baseColor4
sensor.onClicked: {
if (mouse.button === Qt.LeftButton) {
//TODO remove dynamic scoping
openProfilePopup(root.name, root.publicKey, (root.profileImage || root.identicon), "", appMain.getUserNickname(root.publicKey));
}
else if (mouse.button === Qt.RightButton && !!messageContextMenu) {
// Set parent, X & Y positions for the messageContextMenu
messageContextMenu.parent = root
messageContextMenu.setXPosition = function() { return 0}
messageContextMenu.setYPosition = function() { return root.height}
messageContextMenu.isProfile = true;
messageContextMenu.show(root.name, root.publicKey, (root.profileImage || root.identicon), "", appMain.getUserNickname(root.publicKey))
} }
return Style.current.transparent
} }
Connections { Rectangle {
enabled: !!root.contactsList id: rectangle
target: root.contactsList width: parent.width
onContactChanged: { height: 40
if (pubkey === root.publicKey) { radius: 8
root.profileImage = !!appMain.getProfileImage(root.publicKey) ? color: wrapper.color
appMain.getProfileImage(root.publicKey) : ""
StatusSmartIdenticon {
id: contactImage
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
image: StatusImageSettings {
width: 28
height: 28
source: wrapper.identicon
isIdenticon: wrapper.isIdenticon
}
icon: StatusIconSettings {
width: 28
height: 28
letterSize: 15
}
name: wrapper.name
}
StyledText {
id: contactInfo
text: wrapper.name
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
elide: Text.ElideRight
color: Style.current.textColor
font.weight: Font.Medium
font.pixelSize: 15
anchors.left: contactImage.right
anchors.leftMargin: Style.current.halfPadding
anchors.verticalCenter: parent.verticalCenter
}
StatusBadge {
id: statusBadge
width: 15
height: 15
anchors.left: contactImage.right
anchors.leftMargin: -Style.current.smallPadding
anchors.bottom: contactImage.bottom
visible: wrapper.userStatus !== Constants.userStatus.offline
border.width: 3
border.color: Theme.palette.statusAppNavBar.backgroundColor
color: {
if(wrapper.userStatus === Constants.userStatus.online)
return Style.current.green
else if(wrapper.userStatus === Constants.userStatus.idle)
return Style.current.orange
else if(wrapper.userStatus === Constants.userStatus.doNotDisturb)
return Style.current.red
return "transparent"
} }
} }
}
StatusBadge { MouseArea {
id: statusBadge enabled: enableMouseArea
width: 15 cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
height: 15 acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.left: parent.left anchors.fill: parent
anchors.leftMargin: 22 hoverEnabled: true
anchors.bottom: parent.bottom onEntered: {
anchors.bottomMargin: 6 wrapper.hovered = true
visible: root.isOnline && !((root.statusType === -1) && (lastSeenMinutesAgo > 7)) }
border.width: 3 onExited: {
border.color: root.sensor.containsMouse ? Theme.palette.baseColor2 : Theme.palette.baseColor4 wrapper.hovered = false
property real lastSeenMinutesAgo: ((currentTime/1000 - parseInt(lastSeen)) / 60); }
color: { onClicked: {
if (visible) { if (mouse.button === Qt.LeftButton) {
if (statusType === Constants.statusType_DoNotDisturb) { //TODO remove dynamic scoping
return Style.current.red; openProfilePopup(wrapper.name, wrapper.publicKey, wrapper.identicon, "", wrapper.name);
} else if (isCurrentUser || (lastSeenMinutesAgo < 5.5)) { }
return Style.current.green; else if (mouse.button === Qt.RightButton && !!messageContextMenu) {
} else if (((statusType !== -1) && (lastSeenMinutesAgo > 5.5)) || // Set parent, X & Y positions for the messageContextMenu
((statusType === -1) && (lastSeenMinutesAgo < 7))) { messageContextMenu.parent = rectangle
return Style.current.orange; messageContextMenu.setXPosition = function() { return 0}
} else if ((statusType === -1) && (lastSeenMinutesAgo > 7)) { messageContextMenu.setYPosition = function() { return rectangle.height}
return "transparent"; messageContextMenu.isProfile = true;
messageContextMenu.show(wrapper.name, wrapper.publicKey, wrapper.identicon, "", wrapper.name)
} }
} else {
return "transparent";
} }
} }
} }

View File

@ -1,11 +1,5 @@
import QtQuick 2.13 import QtQuick 2.13
import Qt.labs.platform 1.1
import QtQuick.Controls 2.13 import QtQuick.Controls 2.13
import QtQuick.Window 2.13
import QtQuick.Layouts 1.13
import QtQml.Models 2.13
import QtGraphicalEffects 1.13
import QtQuick.Dialogs 1.3
import shared 1.0 import shared 1.0
import shared.panels 1.0 import shared.panels 1.0
import shared.status 1.0 import shared.status 1.0
@ -17,11 +11,11 @@ import utils 1.0
Item { Item {
id: root id: root
anchors.fill: parent anchors.fill: parent
property var userList
property var currentTime // Important:
property bool isOnline // Each chat/community has its own ChatContentModule and each ChatContentModule has a single usersModule
property var contactsList // usersModule on the backend contains everything needed for this component
property string profilePubKey property var usersModule
property var messageContextMenu property var messageContextMenu
StyledText { StyledText {
@ -52,26 +46,14 @@ Item {
bottomMargin: Style.current.bigPadding bottomMargin: Style.current.bigPadding
} }
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
model: userListDelegate model: usersModule.model
}
DelegateModelGeneralized {
id: userListDelegate
lessThan: [
function (left, right) {
return (left.lastSeen > right.lastSeen);
}
]
model: root.userList
delegate: UserDelegate { delegate: UserDelegate {
name: model.userName publicKey: model.id
publicKey: model.publicKey name: model.name
profilePubKey: root.profilePubKey identicon: model.icon
identicon: model.identicon isIdenticon: model.isIdenticon
contactsList: root.contactsList userStatus: model.onlineStatus
lastSeen: model.lastSeen / 1000 messageContextMenu: root.messageContextMenu
currentTime: root.currentTime
isOnline: root.isOnline
} }
} }
} }

View File

@ -1,11 +1,5 @@
import QtQuick 2.13 import QtQuick 2.13
import Qt.labs.platform 1.1
import QtQuick.Controls 2.13 import QtQuick.Controls 2.13
import QtQuick.Window 2.13
import QtQuick.Layouts 1.13
import QtQml.Models 2.13
import QtGraphicalEffects 1.13
import QtQuick.Dialogs 1.3
import shared 1.0 import shared 1.0
import shared.panels 1.0 import shared.panels 1.0
import shared.status 1.0 import shared.status 1.0
@ -20,14 +14,12 @@ import StatusQ.Core.Theme 0.1
Item { Item {
id: root id: root
anchors.fill: parent anchors.fill: parent
property var userList
property var currentTime // Important:
property var contactsList // Each chat/community has its own ChatContentModule and each ChatContentModule has a single usersModule
property string profilePubKey // usersModule on the backend contains everything needed for this component
property var usersModule
property var messageContextMenu property var messageContextMenu
property var community
property string currentUserName: ""
property bool currentUserOnline: true
StatusBaseText { StatusBaseText {
id: titleText id: titleText
@ -59,46 +51,32 @@ Item {
bottomMargin: Style.current.bigPadding bottomMargin: Style.current.bigPadding
} }
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
model: userListDelegate model: usersModule.model
section.property: "online"
section.delegate: (root.width > 58) ? sectionDelegateComponent : null
Component {
id: sectionDelegateComponent
Item {
width: parent.width
height: 24
StatusBaseText {
anchors.fill: parent
anchors.leftMargin: Style.current.padding
verticalAlignment: Text.AlignVCenter
font.pixelSize: Style.current.additionalTextSize
color: Theme.palette.baseColor1
text: section === 'true' ? qsTr("Online") : qsTr("Offline")
}
}
}
}
DelegateModelGeneralized {
id: userListDelegate
lessThan: [
function(left, right) {
return left.sortKey.localeCompare(right.sortKey) < 0
}
]
model: community.members
delegate: UserDelegate { delegate: UserDelegate {
name: model.userName publicKey: model.id
publicKey: model.pubKey name: model.name
profilePubKey: root.profilePubKey identicon: model.icon
identicon: model.identicon isIdenticon: model.isIdenticon
contactsList: root.contactsList userStatus: model.onlineStatus
lastSeen: model.lastSeen
statusType: model.statusType
currentTime: root.currentTime
isOnline: (model.userName === root.currentUserName) ?
root.currentUserOnline : model.online
messageContextMenu: root.messageContextMenu messageContextMenu: root.messageContextMenu
} }
section.property: "onlineStatus"
section.delegate: (root.width > 58) ? sectionDelegateComponent : null
}
Component {
id: sectionDelegateComponent
Item {
width: parent.width
height: 24
StyledText {
anchors.fill: parent
anchors.leftMargin: Style.current.padding
verticalAlignment: Text.AlignVCenter
font.pixelSize: Style.current.additionalTextSize
color: Theme.palette.baseColor1
text: model.onlineStatus === Constants.userStatus.online? qsTr("Online") : qsTr("Offline")
}
}
} }
} }

View File

@ -28,6 +28,18 @@ Item {
// Important: we have parent module in this context only cause qml components // Important: we have parent module in this context only cause qml components
// don't follow struct from we have on the backend. // don't follow struct from we have on the backend.
property var parentModule property var parentModule
// Since qml component doesn't follow encaptulation from the backend side, we're introducing
// a method which will return appropriate chat content module for selected chat/channel
function currentChatContentModule(){
// When we decide to have the same struct as it's on the backend we will remove this function.
// So far this is a way to deal with refactord backend from the current qml structure.
if(parentModule.activeItem.isSubItemActive)
parentModule.prepareChatContentModuleForChatId(chatCommunitySectionModule.activeItem.activeSubItem.id)
else
parentModule.prepareChatContentModuleForChatId(chatCommunitySectionModule.activeItem.id)
return parentModule.getChatContentModule()
}
property var rootStore property var rootStore
property alias pinnedMessagesPopupComponent: pinnedMessagesPopupComponent property alias pinnedMessagesPopupComponent: pinnedMessagesPopupComponent
@ -210,8 +222,11 @@ Item {
} }
} }
membersButton.visible: (localAccountSensitiveSettings.showOnlineUsers || root.rootStore.chatsModelInst.communities.activeCommunity.active) membersButton.visible: {
&& root.rootStore.chatsModelInst.channelView.activeChannel.chatType !== Constants.chatTypeOneToOne // Check if user list is available as an option for particular chat content module.
let usersListAvailable = currentChatContentModule().isUsersListAvailable()
return localAccountSensitiveSettings.showOnlineUsers && usersListAvailable
}
membersButton.highlighted: localAccountSensitiveSettings.expandUsersList membersButton.highlighted: localAccountSensitiveSettings.expandUsersList
notificationButton.visible: localAccountSensitiveSettings.isActivityCenterEnabled notificationButton.visible: localAccountSensitiveSettings.isActivityCenterEnabled
notificationButton.tooltip.offset: localAccountSensitiveSettings.expandUsersList ? 0 : 14 notificationButton.tooltip.offset: localAccountSensitiveSettings.expandUsersList ? 0 : 14

View File

@ -320,19 +320,18 @@ Item {
badge.implicitHeight: 15 badge.implicitHeight: 15
badge.implicitWidth: 15 badge.implicitWidth: 15
badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusAppNavBar.backgroundColor badge.border.color: hovered ? Theme.palette.statusBadge.hoverBorderColor : Theme.palette.statusAppNavBar.backgroundColor
badge.color: { /*
return userProfile.sendUserStatus ? Style.current.green : Style.current.midGrey //This is still not in use. Read a comment for `currentUserStatus` in UserProfile on the nim side.
/* // Use this code once support for custom user status is added
// Use this code once support for custom user status is added switch(userProfile.currentUserStatus){
switch(userProfile.currentUserStatus){ case Constants.userStatus.online:
case Constants.statusType_Online: return Style.current.green;
return Style.current.green; case Constants.userStatus.doNotDisturb:
case Constants.statusType_DoNotDisturb: return Style.current.red;
return Style.current.red; default:
default: return Style.current.midGrey;
return Style.current.midGrey; }*/
}*/ badge.color: appMain.rootStore.userProfileInst.userStatus ? Style.current.green : Style.current.midGrey
}
badge.border.width: 3 badge.border.width: 3
onClicked: { onClicked: {
userStatusContextMenu.opened ? userStatusContextMenu.opened ?

View File

@ -40,6 +40,13 @@ QtObject {
readonly property int newMessage: 6 readonly property int newMessage: 6
} }
readonly property QtObject userStatus: QtObject{
readonly property int offline: 0
readonly property int online: 1
readonly property int doNotDisturb: 2
readonly property int idle: 3
}
readonly property int communityImported: 0 readonly property int communityImported: 0
readonly property int communityImportingInProgress: 1 readonly property int communityImportingInProgress: 1
readonly property int communityImportingError: 2 readonly property int communityImportingError: 2
@ -113,10 +120,6 @@ QtObject {
readonly property string linux: "linux" readonly property string linux: "linux"
readonly property string mac: "mac" readonly property string mac: "mac"
readonly property int statusType_Unknown: 0
readonly property int statusType_Online: 1
readonly property int statusType_DoNotDisturb: 2
// Transaction states // Transaction states
readonly property int addressRequested: 1 readonly property int addressRequested: 1
readonly property int declined: 2 readonly property int declined: 2