parent
ade6a22fda
commit
e130953634
|
@ -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) =
|
||||
|
|
|
@ -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) =
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
))
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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.} =
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
))
|
||||
)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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")
|
|
@ -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
|
|
@ -98,6 +98,7 @@ StatusAppThreePanelLayout {
|
|||
Component {
|
||||
id: userListComponent
|
||||
UserListPanel {
|
||||
rootStore: root.rootStore
|
||||
label: localAccountSensitiveSettings.communitiesEnabled &&
|
||||
root.rootStore.chatCommunitySectionModule.isCommunity() ?
|
||||
//% "Members"
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"'
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue