feat(@desktop): use emoji hash and identicon ring

Closes: #4782
This commit is contained in:
Patryk Osmaczko 2022-03-09 11:27:32 +01:00 committed by osmaczko
parent ade6a22fda
commit e130953634
45 changed files with 736 additions and 490 deletions

View File

@ -30,6 +30,7 @@ import ../../app_service/service/devices/service as devices_service
import ../../app_service/service/mailservers/service as mailservers_service
import ../../app_service/service/gif/service as gif_service
import ../../app_service/service/ens/service as ens_service
import ../../app_service/service/visual_identity/service as visual_identity_service
import ../modules/startup/module as startup_module
import ../modules/main/module as main_module
@ -83,6 +84,7 @@ type
nodeService: node_service.Service
gifService: gif_service.Service
ensService: ens_service.Service
visualIdentityService: visual_identity_service.Service
# Modules
startupModule: startup_module.AccessInterface
@ -146,7 +148,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
)
result.messageService = message_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.contactsService, result.ethService, result.tokenService, result.walletAccountService)
result.transactionService = transaction_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.transactionService = transaction_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.walletAccountService, result.ethService, result.networkService, result.settingsService)
result.bookmarkService = bookmark_service.newService()
result.profileService = profile_service.newService()
@ -175,10 +177,11 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.nodeService = node_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService)
result.gifService = gif_service.newService(result.settingsService)
result.ensService = ens_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService, result.walletAccountService, result.transactionService, result.ethService,
result.ensService = ens_service.newService(statusFoundation.events, statusFoundation.threadpool,
result.settingsService, result.walletAccountService, result.transactionService, result.ethService,
result.networkService, result.tokenService)
result.providerService = provider_service.newService(result.ensService)
result.visualIdentityService = visual_identity_service.newService()
# Modules
result.startupModule = startup_module.newModule[AppController](
@ -219,6 +222,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.gifService,
result.ensService,
result.networkService,
result.visualIdentityService
)
# Do connections
@ -267,6 +271,7 @@ proc delete*(self: AppController) =
self.generalService.delete
self.ensService.delete
self.gifService.delete
self.visualIdentityService.delete
proc startupDidLoad*(self: AppController) =
singletonInstance.engine.setRootContextProperty("localAppSettings", self.localAppSettingsVariant)
@ -336,7 +341,8 @@ proc load(self: AppController) =
self.communityService,
self.messageService,
self.gifService,
self.mailserversService
self.mailserversService,
self.visualIdentityService,
)
proc userLoggedIn*(self: AppController) =

View File

@ -20,6 +20,7 @@ import ../../../../../app_service/service/community/service as community_service
import ../../../../../app_service/service/gif/service as gif_service
import ../../../../../app_service/service/message/service as message_service
import ../../../../../app_service/service/mailservers/service as mailservers_service
import ../../../../../app_service/service/visual_identity/service as visual_identity_service
export io_interface
@ -41,7 +42,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitt
belongsToCommunity: bool, isUsersListAvailable: bool, settingsService: settings_service.ServiceInterface,
contactService: contact_service.Service, chatService: chat_service.Service,
communityService: community_service.Service, messageService: message_service.Service, gifService: gif_service.Service,
mailserversService: mailservers_service.Service):
mailserversService: mailservers_service.Service, visualIdentityService: visual_identity_service.Service):
Module =
result = Module()
result.delegate = delegate
@ -56,7 +57,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface, events: EventEmitt
contactService, communityService, chatService, messageService, mailserversService)
result.usersModule = users_module.newModule(
result, events, sectionId, chatId, belongsToCommunity, isUsersListAvailable,
contactService, chat_service, communityService, messageService,
contactService, chat_service, communityService, messageService, visualIdentityService
)
method delete*(self: Module) =

View File

@ -6,7 +6,7 @@ import ../../../../../../app_service/service/contacts/service as contact_service
import ../../../../../../app_service/service/community/service as community_service
import ../../../../../../app_service/service/message/service as message_service
import ../../../../../../app_service/service/chat/service as chat_service
import ../../../../../../app_service/service/visual_identity/service as visual_identity_service
import ../../../../../core/eventemitter
@ -24,12 +24,13 @@ type
chatService: chat_service.Service
communityService: community_service.Service
messageService: message_service.Service
visualIdentityService: visual_identity_service.Service
proc newController*(
delegate: io_interface.AccessInterface, events: EventEmitter, sectionId: string, chatId: string,
belongsToCommunity: bool, isUsersListAvailable: bool, contactService: contact_service.Service,
chatService: chat_service.Service, communityService: community_service.Service,
messageService: message_service.Service
messageService: message_service.Service, visualIdentityService: visual_identity_service.Service
): Controller =
result = Controller()
result.delegate = delegate
@ -43,6 +44,7 @@ proc newController*(
result.communityService = communityService
result.messageService = messageService
result.chatService = chatService
result.visualIdentityService = visualIdentityService
method delete*(self: Controller) =
discard
@ -153,3 +155,9 @@ method getContactDetails*(self: Controller, contactId: string): ContactDetails =
method getStatusForContact*(self: Controller, contactId: string): StatusUpdateDto =
return self.contactService.getStatusForContactWithId(contactId)
method getEmojiHash*(self: Controller, pubkey: string): EmojiHashDto =
return self.visual_identity_service.emojiHashOf(pubkey)
method getColorHash*(self: Controller, pubkey: string): ColorHashDto =
return self.visual_identity_service.colorHashOf(pubkey)

View File

@ -1,5 +1,6 @@
import ../../../../../../app_service/service/contacts/service as contacts_service
import ../../../../../../app_service/service/chat/service as chat_service
import ../../../../../../app_service/service/visual_identity/service
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -32,3 +33,9 @@ method getChat*(self: AccessInterface): ChatDto {.base.} =
method getChatMemberInfo*(self: AccessInterface, id: string): (bool, bool) =
raise newException(ValueError, "No implementation available")
method getEmojiHash*(self: AccessInterface, pubkey: string): EmojiHashDto {.base.} =
raise newException(ValueError, "No implementation available")
method getColorHash*(self: AccessInterface, pubkey: string): ColorHashDto {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -9,6 +9,7 @@ import ../../../../../../app_service/service/contacts/service as contact_service
import ../../../../../../app_service/service/chat/service as chat_service
import ../../../../../../app_service/service/community/service as community_service
import ../../../../../../app_service/service/message/service as message_service
import ../../../../../../app_service/service/visual_identity/service as visual_identity_service
export io_interface
@ -24,7 +25,7 @@ proc newModule*(
delegate: delegate_interface.AccessInterface, events: EventEmitter, sectionId: string, chatId: string,
belongsToCommunity: bool, isUsersListAvailable: bool, contactService: contact_service.Service,
chatService: chat_service.Service, communityService: community_service.Service,
messageService: message_service.Service,
messageService: message_service.Service, visualIdentityService: visual_identity_service.Service
): Module =
result = Module()
result.delegate = delegate
@ -32,7 +33,7 @@ proc newModule*(
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(
result, events, sectionId, chatId, belongsToCommunity, isUsersListAvailable,
contactService, chatService, communityService, messageService,
contactService, chatService, communityService, messageService, visualIdentityService
)
result.moduleLoaded = false
@ -62,6 +63,8 @@ method viewDidLoad*(self: Module) =
singletonInstance.userProfile.getIcon(),
singletonInstance.userProfile.getIdenticon(),
singletonInstance.userProfile.getIsIdenticon(),
self.controller.getEmojiHash(singletonInstance.userProfile.getPubKey()),
self.controller.getColorHash(singletonInstance.userProfile.getPubKey()),
isAdded = true,
admin,
joined,
@ -87,8 +90,10 @@ method viewDidLoad*(self: Module) =
contactDetails.icon,
contactDetails.details.identicon,
contactDetails.isidenticon,
self.controller.getEmojiHash(publicKey),
self.controller.getColorHash(publicKey),
contactDetails.details.added,
admin,
admin,
joined
))
@ -120,6 +125,8 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto]) =
contactDetails.icon,
contactDetails.details.identicon,
contactDetails.isidenticon,
self.controller.getEmojiHash(m.`from`),
self.controller.getColorHash(m.`from`),
contactDetails.details.added,
))
@ -173,8 +180,10 @@ method onChatMembersAdded*(self: Module, ids: seq[string]) =
contactDetails.icon,
contactDetails.details.identicon,
contactDetails.isidenticon,
self.controller.getEmojiHash(id),
self.controller.getColorHash(id),
contactDetails.details.added,
admin,
admin,
joined
))

View File

@ -19,6 +19,7 @@ import ../../../../app_service/service/community/service as community_service
import ../../../../app_service/service/message/service as message_service
import ../../../../app_service/service/mailservers/service as mailservers_service
import ../../../../app_service/service/gif/service as gif_service
import ../../../../app_service/service/visual_identity/service as visual_identity_service
export io_interface
@ -85,10 +86,11 @@ proc addSubmodule(self: Module, chatId: string, belongToCommunity: bool, isUsers
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service) =
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service) =
self.chatContentModules[chatId] = chat_content_module.newModule(self, events, self.controller.getMySectionId(), chatId,
belongToCommunity, isUsersListAvailable, settingsService, contactService, chatService, communityService,
messageService, gifService, mailserversService)
messageService, gifService, mailserversService, visualIdentityService)
proc removeSubmodule(self: Module, chatId: string) =
if(not self.chatContentModules.contains(chatId)):
@ -102,7 +104,8 @@ proc buildChatUI(self: Module, events: EventEmitter,
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service) =
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service) =
let types = @[ChatType.OneToOne, ChatType.Public, ChatType.PrivateGroupChat]
let chats = self.controller.getChatDetailsForChatTypes(types)
@ -130,7 +133,7 @@ proc buildChatUI(self: Module, events: EventEmitter,
active=false, c.position, c.categoryId)
self.view.chatsModel().appendItem(item)
self.addSubmodule(c.id, false, isUsersListAvailable, events, settingsService, contactService, chatService,
communityService, messageService, gifService, mailserversService)
communityService, messageService, gifService, mailserversService, visualIdentityService)
# make the first Public chat active when load the app
if(selectedItemId.len == 0 and c.chatType == ChatType.Public):
@ -145,7 +148,8 @@ proc buildCommunityUI(self: Module, events: EventEmitter,
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service) =
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service) =
var selectedItemId = ""
var selectedSubItemId = ""
let communities = self.controller.getJoinedCommunities()
@ -166,7 +170,7 @@ proc buildCommunityUI(self: Module, events: EventEmitter,
notificationsCount, chatDto.muted, blocked=false, active = false, c.position, c.categoryId)
self.view.chatsModel().appendItem(channelItem)
self.addSubmodule(chatDto.id, true, true, events, settingsService, contactService, chatService, communityService,
messageService, gifService, mailserversService)
messageService, gifService, mailserversService, visualIdentityService)
# make the first channel which doesn't belong to any category active when load the app
if(selectedItemId.len == 0):
@ -197,7 +201,7 @@ proc buildCommunityUI(self: Module, events: EventEmitter,
active=false, c.position)
categoryChannels.add(channelItem)
self.addSubmodule(chatDto.id, true, true, events, settingsService, contactService, chatService, communityService,
messageService, gifService, mailserversService)
messageService, gifService, mailserversService, visualIdentityService)
# in case there is no channels beyond categories,
# make the first channel of the first category active when load the app
@ -262,14 +266,15 @@ method load*(self: Module, events: EventEmitter,
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service) =
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service) =
self.controller.init()
self.view.load()
if(self.controller.isCommunity()):
self.buildCommunityUI(events, settingsService, contactService, chatService, communityService, messageService, gifService, mailserversService)
self.buildCommunityUI(events, settingsService, contactService, chatService, communityService, messageService, gifService, mailserversService, visualIdentityService)
else:
self.buildChatUI(events, settingsService, contactService, chatService, communityService, messageService, gifService, mailserversService)
self.buildChatUI(events, settingsService, contactService, chatService, communityService, messageService, gifService, mailserversService, visualIdentityService)
self.initContactRequestsModel() # we do this only in case of chat section (not in case of communities)
for cModule in self.chatContentModules.values:
@ -400,6 +405,7 @@ method addNewChat*(
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service,
setChatAsActive: bool = true) =
let hasNotification = chatDto.unviewedMessagesCount > 0 or chatDto.unviewedMentionsCount > 0
let notificationsCount = chatDto.unviewedMentionsCount
@ -421,7 +427,7 @@ method addNewChat*(
chatDto.description, chatDto.chatType.int, amIChatAdmin, hasNotification, notificationsCount,
chatDto.muted, blocked=false, active=false, position = 0, chatDto.categoryId, chatDto.highlight)
self.addSubmodule(chatDto.id, belongsToCommunity, isUsersListAvailable, events, settingsService, contactService, chatService,
communityService, messageService, gifService, mailserversService)
communityService, messageService, gifService, mailserversService, visualIdentityService)
self.chatContentModules[chatDto.id].load()
self.view.chatsModel().appendItem(item)
if setChatAsActive:
@ -437,7 +443,7 @@ method addNewChat*(
amIChatAdmin, hasNotification, notificationsCount, chatDto.muted, blocked=false, active=false,
chatDto.position)
self.addSubmodule(chatDto.id, belongsToCommunity, isUsersListAvailable, events, settingsService, contactService, chatService,
communityService, messageService, gifService, mailserversService)
communityService, messageService, gifService, mailserversService, visualIdentityService)
self.chatContentModules[chatDto.id].load()
categoryItem.appendSubItem(channelItem)
if setChatAsActive:
@ -638,8 +644,8 @@ method onContactDetailsUpdated*(self: Module, publicKey: string) =
let item = self.createItemFromPublicKey(publicKey)
self.view.contactRequestsModel().addItem(item)
self.updateParentBadgeNotifications()
singletonInstance.globalEvents.showNewContactRequestNotification("New Contact Request",
fmt "{contactDetails.displayName} added you as contact", conf.CHAT_SECTION_ID)
singletonInstance.globalEvents.showNewContactRequestNotification("New Contact Request",
fmt "{contactDetails.displayName} added you as contact", conf.CHAT_SECTION_ID)
let chatName = contactDetails.displayName
let chatImage = contactDetails.icon
@ -659,10 +665,10 @@ method onNewMessagesReceived*(self: Module, chatId: string, unviewedMessagesCoun
let renderedMessageText = self.controller.getRenderedText(m.parsedText)
let plainText = singletonInstance.utils.plainText(renderedMessageText)
if(m.isUserWithPkMentioned(myPK)):
singletonInstance.globalEvents.showMentionMessageNotification(contactDetails.displayName, plainText,
singletonInstance.globalEvents.showMentionMessageNotification(contactDetails.displayName, plainText,
self.controller.getMySectionId(), chatId, m.id)
else:
singletonInstance.globalEvents.showNormalMessageNotification(contactDetails.displayName, plainText,
singletonInstance.globalEvents.showNormalMessageNotification(contactDetails.displayName, plainText,
self.controller.getMySectionId(), chatId, m.id)
method addGroupMembers*(self: Module, communityID: string, chatId: string, pubKeys: string) =
@ -757,7 +763,7 @@ method reorderCommunityCategories*(self: Module, categoryId: string, position: i
method reorderCommunityChat*(self: Module, categoryId: string, chatId: string, position: int): string =
self.controller.reorderCommunityChat(categoryId, chatId, position)
method setLoadingHistoryMessagesInProgress*(self: Module, isLoading: bool) =
self.view.setLoadingHistoryMessagesInProgress(isLoading)

View File

@ -7,6 +7,7 @@ import ../../../../../app_service/service/community/service as community_service
import ../../../../../app_service/service/message/service as message_service
import ../../../../../app_service/service/gif/service as gif_service
import ../../../../../app_service/service/mailservers/service as mailservers_service
import ../../../../../app_service/service/visual_identity/service as visual_identity_service
import ../model as chats_model
@ -22,7 +23,8 @@ method load*(self: AccessInterface, events: EventEmitter,
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service) {.base.} =
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =

View File

@ -6,6 +6,7 @@ import ../../../core/signals/types
import ../../../core/eventemitter
import ../../../../app_service/service/community/service as community_service
import ../../../../app_service/service/contacts/service as contacts_service
import ../../../../app_service/service/visual_identity/service as visual_identity_service
export controller_interface
@ -15,18 +16,21 @@ type
events: EventEmitter
communityService: community_service.Service
contactsService: contacts_service.Service
visualIdentityService: visual_identity_service.Service
proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter,
communityService: community_service.Service,
contactsService: contacts_service.Service
contactsService: contacts_service.Service,
visualIdentityService: visual_identity_service.Service
): Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.communityService = communityService
result.contactsService = contactsService
result.visualIdentityService = visualIdentityService
method delete*(self: Controller) =
discard
@ -133,4 +137,10 @@ method userCanJoin*(self: Controller, communityId: string): bool =
return self.communityService.userCanJoin(communityId)
method isCommunityRequestPending*(self: Controller, communityId: string): bool =
return self.communityService.isCommunityRequestPending(communityId)
return self.communityService.isCommunityRequestPending(communityId)
method getEmojiHash*(self: Controller, pubkey: string): EmojiHashDto =
return self.visualIdentityService.emojiHashOf(pubkey)
method getColorHash*(self: Controller, pubkey: string): ColorHashDto =
return self.visualIdentityService.colorHashOf(pubkey)

View File

@ -1,5 +1,6 @@
import ../../../../app_service/service/community/service as community_service
import ../../../../app_service/service/contacts/service as contacts_service
import ../../../../app_service/service/visual_identity/service as visual_identity_service
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -63,6 +64,12 @@ method getContactNameAndImage*(self: AccessInterface, contactId: string):
method getContactDetails*(self: AccessInterface, contactId: string): ContactDetails {.base.} =
raise newException(ValueError, "No implementation available")
method getEmojiHash*(self: AccessInterface, pubkey: string): EmojiHashDto {.base.} =
raise newException(ValueError, "No implementation available")
method getColorHash*(self: AccessInterface, pubkey: string): ColorHashDto {.base.} =
raise newException(ValueError, "No implementation available")
type
## Abstract class (concept) which must be implemented by object/s used in this
## module.

View File

@ -9,6 +9,7 @@ import ../../../global/global_singleton
import ../../../core/eventemitter
import ../../../../app_service/service/community/service as community_service
import ../../../../app_service/service/contacts/service as contacts_service
import ../../../../app_service/service/visual_identity/service as visual_identity_service
export io_interface
@ -33,7 +34,8 @@ proc newModule*(
delegate: delegate_interface.AccessInterface,
events: EventEmitter,
communityService: community_service.Service,
contactsService: contacts_service.Service
contactsService: contacts_service.Service,
visualIdentityService: visual_identity_service.Service
): Module =
result = Module()
result.delegate = delegate
@ -43,7 +45,8 @@ proc newModule*(
result,
events,
communityService,
contactsService
contactsService,
visualIdentityService
)
result.moduleLoaded = false
@ -100,6 +103,8 @@ method getCommunityItem(self: Module, c: CommunityDto): SectionItem =
contactDetails.icon,
contactDetails.details.identicon,
contactDetails.isidenticon,
self.controller.getEmojiHash(member.id),
self.controller.getColorHash(member.id),
contactDetails.details.added,
))
)

View File

@ -16,6 +16,7 @@ import ../../../app_service/service/gif/service as gif_service
import ../../../app_service/service/mailservers/service as mailservers_service
import ../../../app_service/service/privacy/service as privacy_service
import ../../../app_service/service/node/service as node_service
import ../../../app_service/service/visual_identity/service as visual_identity_service
export controller_interface
@ -37,6 +38,7 @@ type
privacyService: privacy_service.Service
mailserversService: mailservers_service.Service
nodeService: node_service.Service
visualIdentityService: visual_identity_service.Service
activeSectionId: string
proc newController*(delegate: io_interface.AccessInterface,
@ -51,7 +53,8 @@ proc newController*(delegate: io_interface.AccessInterface,
gifService: gif_service.Service,
privacyService: privacy_service.Service,
mailserversService: mailservers_service.Service,
nodeService: node_service.Service
nodeService: node_service.Service,
visualIdentityService: visual_identity_service.Service
):
Controller =
result = Controller()
@ -67,6 +70,7 @@ proc newController*(delegate: io_interface.AccessInterface,
result.gifService = gifService
result.privacyService = privacyService
result.nodeService = nodeService
result.visualIdentityService = visualIdentityService
method delete*(self: Controller) =
discard
@ -103,7 +107,8 @@ method init*(self: Controller) =
self.communityService,
self.messageService,
self.gifService,
self.mailserversService
self.mailserversService,
self.visualIdentityService
)
self.events.on(TOGGLE_SECTION) do(e:Args):
@ -121,7 +126,8 @@ method init*(self: Controller) =
self.communityService,
self.messageService,
self.gifService,
self.mailserversService
self.mailserversService,
self.visualIdentityService
)
self.events.on(SIGNAL_COMMUNITY_IMPORTED) do(e:Args):
@ -137,7 +143,8 @@ method init*(self: Controller) =
self.communityService,
self.messageService,
self.gifService,
self.mailserversService
self.mailserversService,
self.visualIdentityService
)
self.events.on(SIGNAL_COMMUNITY_LEFT) do(e:Args):
@ -172,20 +179,20 @@ method init*(self: Controller) =
var args = ActiveSectionChatArgs(e)
let sectionType = if args.sectionId == conf.CHAT_SECTION_ID: SectionType.Chat else: SectionType.Community
self.setActiveSection(args.sectionId, sectionType)
self.events.on(SIGNAL_OS_NOTIFICATION_CLICKED) do(e: Args):
var args = ClickedNotificationArgs(e)
self.delegate.osNotificationClicked(args.details)
self.events.on(SIGNAL_NEW_REQUEST_TO_JOIN_COMMUNITY) do(e: Args):
var args = CommunityRequestArgs(e)
self.delegate.newCommunityMembershipRequestReceived(args.communityRequest)
self.delegate.newCommunityMembershipRequestReceived(args.communityRequest)
self.events.on(SIGNAL_NETWORK_CONNECTED) do(e: Args):
self.delegate.onNetworkConnected()
self.delegate.onNetworkConnected()
self.events.on(SIGNAL_NETWORK_DISCONNECTED) do(e: Args):
self.delegate.onNetworkDisconnected()
self.delegate.onNetworkDisconnected()
method isConnected*(self: Controller): bool =
return self.nodeService.isConnected()
@ -285,4 +292,10 @@ method switchTo*(self: Controller, sectionId, chatId, messageId: string) =
self.events.emit(SIGNAL_MAKE_SECTION_CHAT_ACTIVE, data)
method getCommunityById*(self: Controller, communityId: string): CommunityDto =
return self.communityService.getCommunityById(communityId)
return self.communityService.getCommunityById(communityId)
method getEmojiHash*(self: Controller, pubkey: string): EmojiHashDto =
return self.visualIdentityService.emojiHashOf(pubkey)
method getColorHash*(self: Controller, pubkey: string): ColorHashDto =
return self.visualIdentityService.colorHashOf(pubkey)

View File

@ -2,6 +2,7 @@ import ../shared_models/section_item
import ../../../app_service/service/contacts/dto/contact_details as contact_details
import ../../../app_service/service/contacts/dto/contacts as contacts_dto
import ../../../app_service/service/community/service as community_service
import ../../../app_service/service/visual_identity/service as visual_identity_service
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -64,4 +65,10 @@ method getCommunityById*(self: AccessInterface, communityId: string): CommunityD
raise newException(ValueError, "No implementation available")
method isConnected*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
raise newException(ValueError, "No implementation available")
method getEmojiHash*(self: AccessInterface, pubkey: string): EmojiHashDto {.base.} =
raise newException(ValueError, "No implementation available")
method getColorHash*(self: AccessInterface, pubkey: string): ColorHashDto {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,4 +1,4 @@
import NimQml, tables, json, sugar, sequtils, strformat
import NimQml, tables, json, sugar, sequtils, strformat, marshal
import io_interface, view, controller, chat_search_item, chat_search_model
import ./communities/models/[pending_request_item, pending_request_model]
@ -49,6 +49,7 @@ import ../../../app_service/service/mailservers/service as mailservers_service
import ../../../app_service/service/gif/service as gif_service
import ../../../app_service/service/ens/service as ens_service
import ../../../app_service/service/network/service as network_service
import ../../../app_service/service/visual_identity/service as visual_identity_service
import ../../core/notifications/details
import ../../core/eventemitter
@ -108,6 +109,7 @@ proc newModule*[T](
gifService: gif_service.Service,
ensService: ens_service.Service,
networkService: network_service.Service,
visualIdentityService: visual_identity_service.Service
): Module[T] =
result = Module[T]()
result.delegate = delegate
@ -126,7 +128,8 @@ proc newModule*[T](
gifService,
privacyService,
mailserversService,
nodeService
nodeService,
visualIdentityService
)
result.moduleLoaded = false
@ -149,7 +152,7 @@ proc newModule*[T](
result.stickersModule = stickers_module.newModule(result, events, stickersService, settingsService, walletAccountService)
result.activityCenterModule = activity_center_module.newModule(result, events, activityCenterService, contactsService,
messageService, chatService)
result.communitiesModule = communities_module.newModule(result, events, communityService, contactsService)
result.communitiesModule = communities_module.newModule(result, events, communityService, contactsService, visualIdentityService)
result.appSearchModule = app_search_module.newModule(result, events, contactsService, chatService, communityService,
messageService)
result.nodeSectionModule = node_section_module.newModule(result, events, settingsService, nodeService, nodeConfigurationService)
@ -210,6 +213,8 @@ proc createCommunityItem[T](self: Module[T], c: CommunityDto): SectionItem =
contactDetails.icon,
contactDetails.details.identicon,
contactDetails.isidenticon,
self.controller.getEmojiHash(member.id),
self.controller.getColorHash(member.id),
contactDetails.details.added
)),
c.pendingRequestsToJoin.map(x => pending_request_item.initItem(
@ -231,7 +236,8 @@ method load*[T](
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service
) =
singletonInstance.engine.setRootContextProperty("mainModule", self.viewVariant)
self.controller.init()
@ -347,9 +353,9 @@ method load*[T](
activeSection = profileSettingsSectionItem
# Load all sections
self.chatSectionModule.load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService)
self.chatSectionModule.load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService, visualIdentityService)
for cModule in self.communitySectionsModule.values:
cModule.load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService)
cModule.load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService, visualIdentityService)
self.browserSectionModule.load()
# self.nodeManagementSectionModule.load()
@ -575,7 +581,8 @@ method communityJoined*[T](
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service
) =
var firstCommunityJoined = false
if (self.communitySectionsModule.len == 0):
@ -593,7 +600,7 @@ method communityJoined*[T](
gifService,
mailserversService
)
self.communitySectionsModule[community.id].load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService)
self.communitySectionsModule[community.id].load(events, settingsService, contactsService, chatService, communityService, messageService, gifService, mailserversService, visualIdentityService)
let communitySectionItem = self.createCommunityItem(community)
if (firstCommunityJoined):
@ -646,6 +653,17 @@ method getContactDetailsAsJson*[T](self: Module[T], publicKey: string): string =
}
return $jsonObj
method getEmojiHashAsJson*[T](self: Module[T], publicKey: string): string =
let emojiHash = self.controller.getEmojiHash(publicKey)
return $$emojiHash
method getColorHashAsJson*[T](self: Module[T], publicKey: string): string =
let colorHash = self.controller.getColorHash(publicKey)
let json = newJArray()
for segment in colorHash:
json.add(%* {"segmentLength": segment.len, "colorId": segment.colorIdx})
return $json
method resolveENS*[T](self: Module[T], ensName: string, uuid: string) =
if ensName.len == 0:
error "error: cannot do a lookup for empty ens name"
@ -692,4 +710,4 @@ method newCommunityMembershipRequestReceived*[T](self: Module[T], membershipRequ
let (contactName, _, _) = self.controller.getContactNameAndImage(membershipRequest.publicKey)
let community = self.controller.getCommunityById(membershipRequest.communityId)
singletonInstance.globalEvents.newCommunityMembershipRequestNotification("New membership request",
fmt "{contactName} asks to join {community.name}", community.id)
fmt "{contactName} asks to join {community.name}", community.id)

View File

@ -5,6 +5,7 @@ import ../../../../app_service/service/community/service as community_service
import ../../../../app_service/service/message/service as message_service
import ../../../../app_service/service/gif/service as gif_service
import ../../../../app_service/service/mailservers/service as mailservers_service
import ../../../../app_service/service/visual_identity/service as visual_identity_service
import ../../../core/eventemitter
@ -20,7 +21,8 @@ method load*(
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service
)
{.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -23,7 +23,8 @@ method communityJoined*(self: AccessInterface, community: CommunityDto, events:
communityService: community_service.Service,
messageService: message_service.Service,
gifService: gif_service.Service,
mailserversService: mailservers_service.Service) {.base.} =
mailserversService: mailservers_service.Service,
visualIdentityService: visual_identity_service.Service) {.base.} =
raise newException(ValueError, "No implementation available")
method communityEdited*(self: AccessInterface, community: CommunityDto) {.base.} =
@ -44,7 +45,7 @@ method mnemonicBackedUp*(self: AccessInterface) {.base.} =
method osNotificationClicked*(self: AccessInterface, details: NotificationDetails) {.base.} =
raise newException(ValueError, "No implementation available")
method newCommunityMembershipRequestReceived*(self: AccessInterface, membershipRequest: CommunityMembershipRequestDto)
method newCommunityMembershipRequestReceived*(self: AccessInterface, membershipRequest: CommunityMembershipRequestDto)
{.base.} =
raise newException(ValueError, "No implementation available")
@ -52,4 +53,4 @@ method onNetworkConnected*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onNetworkDisconnected*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
raise newException(ValueError, "No implementation available")

View File

@ -26,6 +26,12 @@ method getAppSearchModule*(self: AccessInterface): QVariant {.base.} =
method getContactDetailsAsJson*(self: AccessInterface, publicKey: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method getEmojiHashAsJson*(self: AccessInterface, publicKey: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method getColorHashAsJson*(self: AccessInterface, publicKey: string): string {.base.} =
raise newException(ValueError, "No implementation available")
method resolveENS*(self: AccessInterface, ensName: string, uuid: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -151,6 +151,14 @@ QtObject:
proc getContactDetailsAsJson(self: View, publicKey: string): string {.slot.} =
return self.delegate.getContactDetailsAsJson(publicKey)
# serialized return - nimqml does not allow QVariant return type in slots
proc getEmojiHashAsJson(self: View, publicKey: string): string {.slot.} =
return self.delegate.getEmojiHashAsJson(publicKey)
# serialized return - nimqml does not allow QVariant return type in slots
proc getColorHashAsJson(self: View, publicKey: string): string {.slot.} =
return self.delegate.getColorHashAsJson(publicKey)
proc resolveENS*(self: View, ensName: string, uuid: string) {.slot.} =
self.delegate.resolveENS(ensName, uuid)
@ -176,4 +184,4 @@ QtObject:
QtProperty[bool] isOnline:
read = isConnected
notify = onlineStatusChanged
notify = onlineStatusChanged

View File

@ -0,0 +1,23 @@
import strformat
type
Item* = ref object
length: int
colorIdx: int
proc initItem*(length: int, colorIdx: int): Item =
result = Item()
result.length = length
result.colorIdx = colorIdx
proc `$`*(self: Item): string =
result = fmt"""ColorHashItem(
length: {$self.length},
colorIdx: {$self.colorIdx},
]"""
proc length*(self: Item): int {.inline.} =
self.length
proc colorIdx*(self: Item): int {.inline.} =
self.colorIdx

View File

@ -0,0 +1,53 @@
import NimQml, Tables
import color_hash_item
type
ModelRole {.pure.} = enum
Length = UserRole + 1
ColorIdx
QtObject:
type
Model* = ref object of QAbstractListModel
items*: seq[Item]
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(): Model =
new(result, delete)
result.setup
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()
self.items = items
self.endResetModel()
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method data(self: Model, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.items.len:
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Length:
result = newQVariant(item.length)
of ModelRole.ColorIdx:
result = newQVariant(item.colorIdx)
method roleNames(self: Model): Table[int, string] =
{
ModelRole.Length.int:"length",
ModelRole.ColorIdx.int:"colorIdx",
}.toTable

View File

@ -0,0 +1,42 @@
import NimQml, Tables
type
RoleNames {.pure.} = enum
Emoji = UserRole + 1,
QtObject:
type
Model* = ref object of QAbstractListModel
items*: seq[string]
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(): Model =
new(result, delete)
result.setup
proc setItems*(self: Model, items: seq[string]) =
self.beginResetModel()
self.items = items
self.endResetModel()
proc items*(self: Model): seq[string] =
return self.items
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method data(self: Model, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.items.len:
return
return newQVariant(self.items[index.row])
method roleNames(self: Model): Table[int, string] =
{ RoleNames.Emoji.int:"emoji" }.toTable

View File

@ -1,4 +1,6 @@
import strformat
import strformat, sequtils, sugar
import ./emojis_model, ./color_hash_model, ./color_hash_item
type
OnlineStatus* {.pure.} = enum
@ -8,6 +10,9 @@ type
Idle
Invisible
type
ColorHashSegment* = tuple[len, colorIdx: int]
# TODO add role when it is needed
type
Item* = ref object
@ -20,6 +25,8 @@ type
icon: string
identicon: string
isIdenticon: bool
emojiHashModel: emojis_model.Model
colorHashModel: color_hash_model.Model
isAdded: bool
isAdmin: bool
joined: bool
@ -34,6 +41,8 @@ proc initItem*(
icon: string,
identicon: string,
isidenticon: bool,
emojiHash: seq[string],
colorHash: seq[ColorHashSegment],
isAdded: bool = false,
isAdmin: bool = false,
joined: bool = false,
@ -48,6 +57,10 @@ proc initItem*(
result.icon = icon
result.identicon = identicon
result.isIdenticon = isidenticon
result.emojiHashModel = emojis_model.newModel()
result.emojiHashModel.setItems(emojiHash)
result.colorHashModel = color_hash_model.newModel()
result.colorHashModel.setItems(map(colorHash, x => color_hash_item.initItem(x.len, x.colorIdx)))
result.isAdded = isAdded
result.isAdmin = isAdmin
result.joined = joined
@ -61,10 +74,10 @@ proc `$`*(self: Item): string =
onlineStatus: {$self.onlineStatus.int},
icon: {self.icon},
identicon: {self.identicon},
isIdenticon: {$self.isIdenticon}
isAdded: {$self.isAdded}
isAdmin: {$self.isAdmin}
joined: {$self.joined}
isIdenticon: {$self.isIdenticon},
isAdded: {$self.isAdded},
isAdmin: {$self.isAdmin},
joined: {$self.joined},
]"""
proc id*(self: Item): string {.inline.} =
@ -132,3 +145,9 @@ proc joined*(self: Item): bool {.inline.} =
proc `joined=`*(self: Item, value: bool) {.inline.} =
self.joined = value
proc emojiHashModel*(self: Item): emojis_model.Model {.inline.} =
self.emojiHashModel
proc colorHashModel*(self: Item): color_hash_model.Model {.inline.} =
self.colorHashModel

View File

@ -13,6 +13,8 @@ type
Icon
Identicon
IsIdenticon
EmojiHashModel
ColorHashModel
IsAdded
IsAdmin
Joined
@ -66,6 +68,8 @@ QtObject:
ModelRole.Icon.int:"icon",
ModelRole.Identicon.int:"identicon",
ModelRole.IsIdenticon.int:"isIdenticon",
ModelRole.EmojiHashModel.int:"emojiHashModel",
ModelRole.ColorHashModel.int:"colorHashModel",
ModelRole.IsAdded.int:"isAdded",
ModelRole.IsAdmin.int:"isAdmin",
ModelRole.Joined.int:"joined",
@ -100,6 +104,10 @@ QtObject:
result = newQVariant(item.identicon)
of ModelRole.IsIdenticon:
result = newQVariant(item.isIdenticon)
of ModelRole.EmojiHashModel:
result = newQVariant(item.emojiHashModel)
of ModelRole.ColorHashModel:
result = newQVariant(item.colorHashModel)
of ModelRole.IsAdded:
result = newQVariant(item.isAdded)
of ModelRole.IsAdmin:

View File

@ -0,0 +1,17 @@
import json, sequtils, sugar
type
EmojiHashDto* = seq[string]
ColorHashSegmentDto* = tuple[len, colorIdx: int]
ColorHashDto* = seq[ColorHashSegmentDto]
proc toEmojiHashDto*(jsonObj: JsonNode): EmojiHashDto =
result = map(jsonObj.getElems(), node => node.getStr())
return
proc toColorHashDto*(jsonObj: JsonNode): ColorHashDto =
result = map(jsonObj.getElems(),
node => (len: node.getElems()[0].getInt(),
colorIdx: node.getElems()[1].getInt())
)
return

View File

@ -0,0 +1,42 @@
import chronicles
import ./dto as dto
import ./service_interface
import ../../../backend/visual_identity as status_visual_identity
export dto
type
Service* = ref object of service_interface.ServiceInterface
proc newService*(): Service =
result = Service()
method delete*(self: Service) =
discard
proc emojiHashOf*(self: Service, pubkey: string): EmojiHashDto =
try:
let response = status_visual_identity.emojiHashOf(pubkey)
if(not response.error.isNil):
error "error emojiHashOf: ", errDescription = response.error.message
result = toEmojiHashDto(response.result)
except Exception as e:
error "error: ", methodName = "emojiHashOf", errName = e.name,
errDesription = e.msg
proc colorHashOf*(self: Service, pubkey: string): ColorHashDto =
try:
let response = status_visual_identity.colorHashOf(pubkey)
if(not response.error.isNil):
error "error colorHashOf: ", errDescription = response.error.message
result = toColorHashDto(response.result)
except Exception as e:
error "error: ", methodName = "colorHashOf", errName = e.name,
errDesription = e.msg

View File

@ -0,0 +1,15 @@
import ./dto as dto
export dto
type
ServiceInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for this service access.
method delete*(self: ServiceInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method emojiHashOf*(self: ServiceInterface, pubkey: string): EmojiHashDto {.base.} =
raise newException(ValueError, "No implementation available")
method colorHashOf*(self: ServiceInterface, pubkey: string): ColorHashDto {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,15 @@
import json, chronicles, core
import response_type
export response_type
logScope:
topics = "rpc-visual-identity"
proc emojiHashOf*(key: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [key]
result = core.callPrivateRPC("visualIdentity_emojiHashOf", payload)
proc colorHashOf*(key: string): RpcResponse[JsonNode] {.raises: [Exception].} =
let payload = %* [key]
result = core.callPrivateRPC("visualIdentity_colorHashOf", payload)

@ -1 +1 @@
Subproject commit 428b165198e53caa5a55ef605f241cc4b6f7e02d
Subproject commit 082bb8ef45b64986be1bf67f4d7c95b1a8ea1440

View File

@ -98,6 +98,7 @@ StatusAppThreePanelLayout {
Component {
id: userListComponent
UserListPanel {
rootStore: root.rootStore
label: localAccountSensitiveSettings.communitiesEnabled &&
root.rootStore.chatCommunitySectionModule.isCommunity() ?
//% "Members"

View File

@ -1,10 +1,12 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import shared 1.0
import shared.panels 1.0
import QtQuick 2.14
import QtQuick.Controls 2.14
import utils 1.0
import shared 1.0
import shared.panels 1.0
import shared.controls.chat 1.0
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
@ -20,6 +22,7 @@ Item {
property string icon: ""
property string identicon: ""
property bool isIdenticon: true
property bool isCurrentUser: false
property bool isAdded: false
property string iconToShow: {
if (isIdenticon || (!isAdded &&
@ -48,23 +51,19 @@ Item {
radius: 8
color: wrapper.color
StatusSmartIdenticon {
UserImage {
id: contactImage
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.verticalCenter: parent.verticalCenter
image: StatusImageSettings {
width: 28
height: 28
source: wrapper.iconToShow
isIdenticon: wrapper.isIdenticon
}
icon: StatusIconSettings {
width: 28
height: 28
letterSize: 15
}
imageWidth: 28
imageHeight: 28
name: wrapper.name
pubkey: wrapper.publicKey
icon: wrapper.iconToShow
isIdenticon: wrapper.isIdenticon
showRing: !(wrapper.isCurrentUser || wrapper.isAdded)
}
StyledText {

View File

@ -27,11 +27,9 @@ ScrollView {
clip: true
delegate: StatusListItem {
id: contactDelegate
property bool isChecked: selectedPubKeys.indexOf(model.pubKey) !== -1
title: !model.name.endsWith(".eth") && !!model.localNickname ?
model.localNickname : Utils.removeStatusEns(model.name)
image.source: Global.getProfileImage(model.pubKey) || model.identicon
image.isIdenticon: !!model.identicon
visible: {
if (selectMode) {
return !searchString || model.name.toLowerCase().includes(searchString)
@ -39,6 +37,12 @@ ScrollView {
return checkbox.checked
}
title: !model.name.endsWith(".eth") && !!model.localNickname ?
model.localNickname : Utils.removeStatusEns(model.name)
image.source: Global.getProfileImage(model.pubKey) || model.identicon
image.isIdenticon: !!model.identicon
ringSettings.ringSpecModel: Utils.getColorHashAsJson(model.pubKey)
height: visible ? implicitHeight : 0
function contactToggled(pubKey) {

View File

@ -22,6 +22,8 @@ Item {
property var messageContextMenu
property string label
property var rootStore
StatusBaseText {
id: titleText
anchors.top: parent.top
@ -61,6 +63,7 @@ Item {
isAdded: model.isAdded
userStatus: model.onlineStatus
messageContextMenu: root.messageContextMenu
isCurrentUser: rootStore.isCurrentUser(model.id)
}
section.property: "onlineStatus"
section.delegate: (root.width > 58) ? sectionDelegateComponent : null

View File

@ -134,6 +134,14 @@ QtObject {
globalUtilsInst.downloadImage(content, path)
}
function isCurrentUser(pubkey) {
return userProfileInst.pubKey === pubkey
}
function displayName(name, pubkey) {
return isCurrentUser(pubkey) ? qsTr("You") : name
}
function getCommunity(communityId) {
// Not Refactored Yet
// try {

View File

@ -289,10 +289,9 @@ Item {
icon: root.ensUsernamesStore.icon
isIdenticon: root.ensUsernamesStore.isIdenticon
showRing: true
onClickMessage: {
root.parent.clickMessage(isProfileClick, isSticker, isImage, image, emojiOnly, hideEmojiPicker, isReply);
}
onClicked: root.parent.clickMessage(true, false, false, null, false, false, false)
}
UsernameLabel {

View File

@ -0,0 +1,115 @@
import QtQuick 2.14
import QtQuick.Layouts 1.14
import utils 1.0
import shared.panels 1.0
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
Item {
id: root
property string displayName
property string pubkey
property string icon
property bool isIdenticon: false
property bool displayNameVisible: true
property bool pubkeyVisible: true
property alias imageWidth: userImage.imageWidth
property alias imageHeight: userImage.imageHeight
property alias emojiSize: emojihash.size
property alias imageOverlay: imageOverlay.sourceComponent
signal clicked()
height: visible ? contentContainer.height : 0
ColumnLayout {
id: contentContainer
anchors {
left: parent.left
right: parent.right
leftMargin: Style.current.smallPadding
rightMargin: Style.current.smallPadding
}
UserImage {
id: userImage
Layout.alignment: Qt.AlignHCenter
name: root.displayName
pubkey: root.pubkey
icon: root.icon
isIdenticon: root.isIdenticon
showRing: true
interactive: false
Loader {
id: imageOverlay
anchors.fill: parent
}
}
StyledText {
Layout.fillWidth: true
visible: root.displayNameVisible
text: root.displayName
horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
maximumLineCount: 3
wrapMode: Text.Wrap
font {
weight: Font.Medium
pixelSize: Style.current.primaryTextFontSize
}
}
StyledText {
Layout.fillWidth: true
visible: root.pubkeyVisible
text: pubkey.substring(0, 10) + "..." + pubkey.substring(
pubkey.length - 4)
horizontalAlignment: Text.AlignHCenter
font.pixelSize: Style.current.asideTextFontSize
color: Style.current.secondaryText
}
Text {
id: emojihash
property string size: "14x14"
Layout.fillWidth: true
text: {
const emojiHash = Utils.getEmojiHashAsJson(root.pubkey)
var emojiHashFirstLine = ""
var emojiHashSecondLine = ""
for (var i = 0; i < 7; i++) {
emojiHashFirstLine += emojiHash[i]
}
for (var i = 7; i < emojiHash.length; i++) {
emojiHashSecondLine += emojiHash[i]
}
return StatusQUtils.Emoji.parse(emojiHashFirstLine, size) + "<br>" +
StatusQUtils.Emoji.parse(emojiHashSecondLine, size)
}
horizontalAlignment: Text.AlignHCenter
font.pointSize: 1 // make sure there is no padding for emojis due to 'style: "vertical-align: top"'
}
}
}

View File

@ -4,46 +4,56 @@ import shared.panels 1.0
import utils 1.0
import StatusQ.Components 0.1
Loader {
id: root
height: active ? item.height : 0
property int imageHeight: 36
property int imageWidth: 36
property string name
property string pubkey
property string icon: ""
property bool isIdenticon: false
property bool showRing: false
signal clickMessage(bool isProfileClick, bool isSticker, bool isImage, var image, bool emojiOnly, bool hideEmojiPicker, bool isReply)
property bool interactive: true
sourceComponent: Component {
Item {
id: chatImage
width: identiconImage.width
height: identiconImage.height
signal clicked()
RoundedImage {
id: identiconImage
width: root.imageWidth
height: root.imageHeight
border.width: root.isIdenticon ? 1 : 0
border.color: Style.current.border
source: root.icon
smooth: false
antialiasing: true
height: active ? item.height : 0
MouseArea {
cursorShape: Qt.PointingHandCursor
acceptedButtons: Qt.LeftButton | Qt.RightButton
anchors.fill: parent
onClicked: {
if (!!messageContextMenu) {
// Set parent, X & Y positions for the messageContextMenu
messageContextMenu.parent = root
messageContextMenu.setXPosition = function() { return root.width + 4}
messageContextMenu.setYPosition = function() { return root.height/2 + 4}
}
root.clickMessage(true, false, false, null, false, false, isReplyImage)
}
}
sourceComponent: StatusSmartIdenticon {
name: root.name
image {
width: root.imageWidth
height: root.imageHeight
source: root.isIdenticon ? "" : root.icon
isIdenticon: false
}
icon {
width: root.imageWidth
height: root.imageHeight
color: Style.current.background
textColor: Style.current.secondaryText
letterSize: Math.max(4, root.imageWidth / 2.4)
charactersLen: 2
}
ringSettings {
ringSpecModel: root.showRing ? Utils.getColorHashAsJson(root.pubkey) : undefined
ringPxSize: Math.max(root.imageWidth / 24.0)
}
Loader {
anchors.fill: parent
active: root.interactive
sourceComponent: MouseArea {
cursorShape: Qt.PointingHandCursor
hoverEnabled: true
onClicked: root.clicked()
}
}
}

View File

@ -12,3 +12,4 @@ StateBubble 1.0 StateBubble.qml
GasSelectorButton 1.0 GasSelectorButton.qml
MessageBorder 1.0 MessageBorder.qml
EmojiReaction 1.0 EmojiReaction.qml
ProfileHeader 1.0 ProfileHeader.qml

View File

@ -13,12 +13,15 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils
Loader {
id: root
property bool amISenderOfTheRepliedMessage
property int repliedMessageContentType
property string repliedMessageSenderIcon
property bool repliedMessageSenderIconIsIdenticon
property bool repliedMessageIsEdited
property string repliedMessageSender
property string repliedMessageSenderPubkey
property bool repliedMessageSenderIsAdded
property string repliedMessageContent
property string repliedMessageImage
property bool isCurrentUser: false
@ -97,16 +100,20 @@ Loader {
UserImage {
id: userImage
anchors.left: replyCorner.right
anchors.leftMargin: Style.current.halfPadding
imageHeight: 20
imageWidth: 20
active: true
anchors.left: replyCorner.right
anchors.leftMargin: Style.current.halfPadding
name: repliedMessageSender
pubkey: repliedMessageSenderPubkey
icon: repliedMessageSenderIcon
isIdenticon: repliedMessageSenderIconIsIdenticon
onClickMessage: {
root.clickMessage(true, false, false, null, false, false, true)
}
showRing: !(amISenderOfTheRepliedMessage || repliedMessageSenderIsAdded)
onClicked: root.clickMessage(true, false, false, null, false, false, true)
}
StyledTextEdit {

View File

@ -3,12 +3,13 @@ import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtGraphicalEffects 1.13
import utils 1.0
import shared 1.0
import shared.popups 1.0
import shared.stores 1.0
import shared.controls.chat 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
@ -29,9 +30,9 @@ StatusModal {
property string userNickname: ""
property string userEnsName: ""
property string userIcon: ""
property bool isUserIconIdenticon: true
property string text: ""
readonly property int innerMargin: 20
property bool userIsEnsVerified: false
@ -62,6 +63,7 @@ StatusModal {
} else {
userIcon = contactDetails.displayIcon
}
isUserIconIdenticon = contactDetails.isDisplayIconIdenticon
userIsEnsVerified = contactDetails.ensVerified
userIsBlocked = contactDetails.isBlocked
isAddedContact = contactDetails.isContact
@ -72,17 +74,9 @@ StatusModal {
popup.open()
}
onHeaderImageClicked: {
Global.openChangeProfilePicPopup()
}
header.title: userDisplayName
header.subTitle: userIsEnsVerified ? userName : userPublicKey
header.subTitleElide: Text.ElideMiddle
// In the following line we need to use `icon` property as that's the easiest way
// to update image on change (in case of logged in user)
header.image.source: isCurrentUser? popup.profileStore.icon : userIcon
header.headerImageEditable: isCurrentUser
headerActionButton: StatusFlatRoundButton {
type: StatusFlatRoundButton.Type.Secondary
@ -109,6 +103,50 @@ StatusModal {
anchors.top: parent.top
width: parent.width
Item {
height: 16
width: parent.width
}
ProfileHeader {
width: parent.width
displayName: popup.userDisplayName
pubkey: popup.userPublicKey
icon: popup.isCurrentUser ? popup.profileStore.icon : popup.userIcon
isIdenticon: popup.isCurrentUser ? popup.profileStore.isIdenticon : popup.isUserIconIdenticon
displayNameVisible: false
pubkeyVisible: false
emojiSize: "20x20"
imageWidth: 80
imageHeight: 80
imageOverlay: Item {
visible: popup.isCurrentUser
StatusFlatRoundButton {
width: 24
height: 24
anchors {
right: parent.right
bottom: parent.bottom
rightMargin: -8
}
type: StatusFlatRoundButton.Type.Secondary
icon.name: "pencil"
icon.color: Theme.palette.directColor1
icon.width: 12.5
icon.height: 12.5
onClicked: Global.openChangeProfilePicPopup()
}
}
}
StatusBanner {
width: parent.width
visible: popup.userIsBlocked

View File

@ -4,85 +4,54 @@ import QtQuick.Layouts 1.3
import QtQml.Models 2.3
import utils 1.0
import "../panels"
import "."
import shared.controls.chat 1.0
import shared.panels 1.0
import StatusQ.Components 0.1
// TODO: replace with StatusPopupMenu
PopupMenu {
id: root
property var store
width: profileHeader.width
width: 200
closePolicy: Popup.CloseOnReleaseOutsideParent | Popup.CloseOnEscape
overrideTextColor: Style.current.textColor
ProfileHeader {
width: parent.width
displayName: root.store.userProfileInst.name
pubkey: root.store.userProfileInst.pubKey
icon: root.store.userProfileInst.icon
isIdenticon: root.store.userProfileInst.isIdenticon
}
Item {
id: profileHeader
width: 200
height: visible ? profileImage.height + username.height + viewProfileBtn.height + Style.current.padding * 2 : 0
Rectangle {
anchors.fill: parent
visible: mouseArea.containsMouse
color: Style.current.backgroundHover
}
StatusSmartIdenticon {
id: profileImage
anchors.top: parent.top
anchors.topMargin: 4
anchors.horizontalCenter: parent.horizontalCenter
image.source: root.store.userProfileInst.icon
image.isIdenticon: root.store.userProfileInst.isIdenticon
}
StyledText {
id: username
text: root.store.userProfileInst.name
elide: Text.ElideRight
maximumLineCount: 3
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
anchors.top: profileImage.bottom
anchors.topMargin: 4
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
font.weight: Font.Medium
font.pixelSize: 13
}
height: root.topPadding
}
StyledText {
id: viewProfileBtn
text: qsTr("My profile →")
horizontalAlignment: Text.AlignHCenter
anchors.top: username.bottom
anchors.topMargin: 4
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
font.weight: Font.Medium
font.pixelSize: Style.current.tertiaryTextFontSize
color: Style.current.secondaryText
}
Separator {
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
Global.openProfilePopup(root.store.userProfileInst.pubKey)
root.close()
}
Action {
text: qsTr("View My Profile")
icon.source: Style.svg("profile")
icon.width: 16
icon.height: 16
onTriggered: {
Global.openProfilePopup(root.store.userProfileInst.pubKey)
root.close()
}
}
Separator {
anchors.bottom: profileHeader.bottom
}
overrideTextColor: Style.current.textColor
Action {
text: qsTr("Online")
onTriggered: {
@ -113,5 +82,4 @@ PopupMenu {
icon.width: 16
icon.height: 16
}
}

View File

@ -11,6 +11,7 @@ import shared.controls.chat 1.0
import StatusQ.Controls 0.1 as StatusQControls
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Components 0.1
Item {
id: root
@ -306,6 +307,8 @@ Item {
// TODO: not sure about is edited at the moment
repliedMessageIsEdited = false
repliedMessageSender = obj.senderDisplayName
repliedMessageSenderPubkey = obj.senderId
repliedMessageSenderIsAdded = obj.senderIsAdded
repliedMessageContent = obj.messageText
repliedMessageImage = obj.messageImage
}
@ -327,17 +330,22 @@ Item {
UserImage {
id: chatImage
active: isMessage && headerRepeatCondition
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.top: chatReply.active ? chatReply.bottom :
pinnedRectangleLoader.active ? pinnedRectangleLoader.bottom : parent.top
anchors.topMargin: chatReply.active || pinnedRectangleLoader.active ? 4 : Style.current.smallPadding
icon: root.senderIcon
isIdenticon: root.isSenderIconIdenticon
onClickMessage: {
root.clickMessage(isProfileClick, isSticker, isImage, image, emojiOnly, hideEmojiPicker, isReply, false, "")
}
pubkey: senderId
name: senderDisplayName
showRing: !(root.amISender || senderIsAdded)
onClicked: root.clickMessage(true, false, false, null, false, false, false, false, "")
}
UsernameLabel {

View File

@ -116,53 +116,19 @@ StatusPopupMenu {
}
}
Item {
id: profileHeader
visible: root.isProfile
ProfileHeader {
width: parent.width
height: visible ? profileImage.height + username.height + Style.current.padding : 0
Rectangle {
anchors.fill: parent
visible: mouseArea.containsMouse
color: Style.current.backgroundHover
}
visible: root.isProfile
StatusSmartIdenticon {
id: profileImage
anchors.top: parent.top
anchors.topMargin: 4
anchors.horizontalCenter: parent.horizontalCenter
image.source: root.selectedUserIcon
image.isIdenticon: root.isSelectedUserIconIdenticon
}
displayName: root.selectedUserDisplayName
pubkey: root.selectedUserPublicKey
icon: root.selectedUserIcon
isIdenticon: root.isSelectedUserIconIdenticon
}
StyledText {
id: username
text: selectedUserDisplayName
elide: Text.ElideRight
maximumLineCount: 3
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.Wrap
anchors.top: profileImage.bottom
anchors.topMargin: 4
anchors.left: parent.left
anchors.leftMargin: Style.current.smallPadding
anchors.right: parent.right
anchors.rightMargin: Style.current.smallPadding
font.weight: Font.Medium
font.pixelSize: 15
}
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: {
root.openProfileClicked(root.selectedUserPublicKey)
root.close()
}
}
Item {
visible: root.isProfile
height: root.topPadding
}
Separator {

View File

@ -103,7 +103,7 @@ Column {
// The system message for private groups appear as created by the group host, but it shouldn't
prevMessageAsJsonObj.contentType === Constants.messageContentType.systemMessagePrivateGroupType) {
return ""
}
}
return prevMessageAsJsonObj.senderId
}
@ -149,7 +149,6 @@ Column {
|| contentType === Constants.messageContentType.communityInviteType || contentType === Constants.messageContentType.transactionType
property bool isExpired: (outgoingStatus === "sending" && (Math.floor(timestamp) + 180000) < Date.now())
property bool isStatusUpdate: false
property int statusAgeEpoch: 0
signal imageClicked(var image)
@ -196,8 +195,8 @@ Column {
if(!obj)
return
messageContextMenu.messageSenderId = obj.id
messageContextMenu.selectedUserPublicKey = obj.id
messageContextMenu.messageSenderId = obj.senderId
messageContextMenu.selectedUserPublicKey = obj.senderId
messageContextMenu.selectedUserDisplayName = obj.senderDisplayName
messageContextMenu.selectedUserIcon = obj.senderIcon
messageContextMenu.isSelectedUserIconIdenticon = obj.isSenderIconIdenticon
@ -258,7 +257,7 @@ Column {
case Constants.messageContentType.gapType:
return gapComponent
default:
return isStatusUpdate ? statusUpdateComponent : compactMessageComponent
return compactMessageComponent
}
}
@ -335,38 +334,6 @@ Column {
}
}
Component {
id: statusUpdateComponent
StatusUpdateView {
messageStore: root.messageStore
statusAgeEpoch: root.statusAgeEpoch
container: root
// Not Refactored Yet
// store: root.rootStore
messageContextMenu: root.messageContextMenu
onAddEmoji: {
root.clickMessage(isProfileClick, isSticker, isImage , image, emojiOnly, hideEmojiPicker);
}
onChatImageClicked: root.imageClicked(image)
onUserNameClicked: {
// Not Refactored Yet - Should do it via messageStore
// root.parent.clickMessage(isProfileClick);
}
onEmojiBtnClicked: {
// Not Refactored Yet - Should do it via messageStore
// root.parent.clickMessage(isProfileClick, isSticker, isImage, image, emojiOnly);
}
onClickMessage: {
// Not Refactored Yet - Should do it via messageStore
// root.parent.clickMessage(isProfileClick, isSticker, isImage, image, emojiOnly, hideEmojiPicker, isReply);
}
onSetMessageActive: {
root.setMessageActive(messageId, active);
}
}
}
Component {
id: compactMessageComponent

View File

@ -1,211 +0,0 @@
import QtQuick 2.3
import QtGraphicalEffects 1.13
import utils 1.0
import shared 1.0
import shared.panels 1.0
import shared.status 1.0
import shared.panels.chat 1.0
import shared.views.chat 1.0
import shared.controls.chat 1.0
import StatusQ.Controls 0.1
MouseArea {
id: root
// property var store
property var messageStore
property bool hovered: containsMouse
property var container
property int statusAgeEpoch: 0
property var messageContextMenu
signal userNameClicked(bool isProfileClick)
signal setMessageActive(string messageId, bool active)
signal emojiBtnClicked(bool isProfileClick, bool isSticker, bool isImage, var image, bool emojiOnly)
signal clickMessage(bool isProfileClick, bool isSticker, bool isImage, var image, bool emojiOnly, bool hideEmojiPicker, bool isReply)
// TODO bring those back and remove dynamic scoping
// property var emojiReactionsModel
// property string timestamp: ""
// property bool isCurrentUser: false
// property bool isMessageActive: false
// property string userName: ""
// property string localName: ""
// property string displayUserName: ""
// property bool isImage: false
// property bool isMessage: false
// property string profileImageSource: ""
// property string userIdenticon: ""
anchors.top: parent.top
anchors.topMargin: 0
height: (isImage ? chatImageContent.height : chatText.height) + chatName.height + 2* Style.current.padding + (emojiReactionsModel.length ? 20 : 0)
width: parent.width
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true
signal chatImageClicked(string image)
signal addEmoji(bool isProfileClick, bool isSticker, bool isImage , var image, bool emojiOnly, bool hideEmojiPicker)
onClicked: {
mouse.accepted = false
}
Rectangle {
id: rootRect
anchors.fill: parent
radius: Style.current.radius
color: root.hovered ? Style.current.border : Style.current.background
UserImage {
id: chatImage
active: isMessage || isImage
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.top: parent.top
anchors.topMargin: Style.current.halfPadding
// messageContextMenu: root.messageContextMenu
// profileImage: root.profileImageSource
// isMessage: root.isMessage
// identiconImageSource: root.userIdenticon
onClickMessage: {
root.clickMessage(true, false, false, null, false, false, isReplyImage)
}
}
UsernameLabel {
id: chatName
z: 51
visible: chatImage.visible
anchors.leftMargin: Style.current.halfPadding
anchors.top: chatImage.top
anchors.left: chatImage.right
label.font.pixelSize: Style.current.primaryTextFontSize
// messageContextMenu: root.messageContextMenu
// isCurrentUser: root.isCurrentUser
// userName: root.userName
// localName: root.localName
// displayUserName: root.displayUserName
onClickMessage: {
root.userNameClicked(true);
}
}
ChatTimePanel {
id: chatTime
// statusAgeEpoch is used to trigger Qt property update
// since the returned string will be the same in 99% cases, this should not trigger ChatTime re-rendering
text: Utils.formatAgeFromTime(timestamp, statusAgeEpoch)
visible: chatName.visible
anchors.verticalCenter: chatName.verticalCenter
anchors.left: chatName.right
anchors.leftMargin: Style.current.halfPadding
//timestamp: timestamp
}
ChatTextView {
id: chatText
anchors.top: chatName.visible ? chatName.bottom : chatImage.top
anchors.topMargin: chatName.visible ? 6 : 0
anchors.left: chatImage.right
anchors.leftMargin: Style.current.halfPadding
anchors.right: parent.right
anchors.rightMargin: Style.current.padding
// store: root.store
}
Loader {
id: chatImageContent
active: isImage
anchors.left: chatImage.right
anchors.leftMargin: Style.current.halfPadding
anchors.top: chatText.bottom
z: 51
sourceComponent: Component {
StatusChatImage {
playing: root.messageStore.playAnimation
imageSource: image
imageWidth: 200
container: root.container
onClicked: {
root.chatImageClicked(image);
}
}
}
}
StatusFlatRoundButton {
id: emojiBtn
width: 32
height: 32
anchors.top: rootRect.top
anchors.topMargin: -height / 4
anchors.right: rootRect.right
anchors.rightMargin: Style.current.halfPadding
visible: root.hovered
icon.name: "reaction-b"
icon.width: 20
icon.height: 20
type: StatusFlatRoundButton.Type.Tertiary
backgroundHoverColor: Style.current.background
onClicked: {
// Set parent, X & Y positions for the messageContextMenu
messageContextMenu.parent = emojiBtn
messageContextMenu.setXPosition = function() { return -messageContextMenu.width + emojiBtn.width}
messageContextMenu.setYPosition = function() { return -messageContextMenu.height - 4}
root.emojiBtnClicked(false, false, false, null, true)
}
}
DropShadow {
anchors.fill: emojiBtn
horizontalOffset: 0
verticalOffset: 2
radius: 10
samples: 12
color: "#22000000"
source: emojiBtn
}
Loader {
id: emojiReactionLoader
active: emojiReactionsModel.length
sourceComponent: emojiReactionsComponent
anchors.left: chatImage.right
anchors.leftMargin: Style.current.halfPadding
anchors.top: isImage ? chatImageContent.bottom : chatText.bottom
anchors.topMargin: Style.current.halfPadding
}
Component {
id: emojiReactionsComponent
EmojiReactionsPanel {
store: messageStore
emojiReactionsModel: reactionsModel
isMessageActive: isMessageActive
isCurrentUser: isCurrentUser
onAddEmojiClicked: {
root.addEmoji(false, false, false, null, true, false);
// Set parent, X & Y positions for the messageContextMenu
messageContextMenu.parent = emojiReactionLoader
messageContextMenu.setXPosition = function() { return (messageContextMenu.parent.x + 4)}
messageContextMenu.setYPosition = function() { return (-messageContextMenu.height - 4)}
}
onToggleReaction: messageStore.toggleReaction(messageId, emojiID)
onSetMessageActive: {
root.setMessageActive(messageId, active);
}
}
}
Separator {
anchors.bottom: parent.bottom
visible: !root.hovered
}
}
}

View File

@ -1,7 +1,6 @@
ChannelIdentifierView 1.0 ChannelIdentifierView.qml
ChatTextView 1.0 ChatTextView.qml
MessageView 1.0 MessageView.qml
StatusUpdateView 1.0 StatusUpdateView.qml
TransactionBubbleView 1.0 TransactionBubbleView.qml
LinksMessageView 1.0 LinksMessageView.qml
InvitationBubbleView 1.0 InvitationBubbleView.qml

View File

@ -604,7 +604,21 @@ QtObject {
}
}
function getEmojiHashAsJson(publicKey) {
if (publicKey === "") {
return ""
}
let jsonObj = mainModule.getEmojiHashAsJson(publicKey)
return JSON.parse(jsonObj)
}
function getColorHashAsJson(publicKey) {
if (publicKey === "") {
return ""
}
let jsonObj = mainModule.getColorHashAsJson(publicKey)
return JSON.parse(jsonObj)
}
// Leave this function at the bottom of the file as QT Creator messes up the code color after this
function isPunct(c) {