diff --git a/src/app/core/signals/remote_signals/messages.nim b/src/app/core/signals/remote_signals/messages.nim index 534829ad59..8811997f5c 100644 --- a/src/app/core/signals/remote_signals/messages.nim +++ b/src/app/core/signals/remote_signals/messages.nim @@ -28,6 +28,7 @@ type MessageSignal* = ref object of Signal currentStatus*: seq[StatusUpdateDto] settings*: seq[SettingsFieldDto] clearedHistories*: seq[ClearedHistoryDto] + verificationRequests*: seq[VerificationRequest] proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal = var signal:MessageSignal = MessageSignal() @@ -103,5 +104,9 @@ proc fromEvent*(T: type MessageSignal, event: JsonNode): MessageSignal = for jsonSettingsField in event["event"]["settings"]: signal.settings.add(jsonSettingsField.toSettingsFieldDto()) + if event["event"]{"verificationRequests"} != nil: + for jsonVerificationRequest in event["event"]["verificationRequests"]: + signal.verificationRequests.add(jsonVerificationRequest.toVerificationRequest()) + result = signal diff --git a/src/app/modules/main/activity_center/module.nim b/src/app/modules/main/activity_center/module.nim index 5fb36df937..bc566d85a2 100644 --- a/src/app/modules/main/activity_center/module.nim +++ b/src/app/modules/main/activity_center/module.nim @@ -92,7 +92,8 @@ proc createMessageItemFromDto(self: Module, message: MessageDto, chatDetails: Ch message.sticker.pack, message.links, newTransactionParametersItem("","","","","","",-1,""), - message.mentionedUsersPks + message.mentionedUsersPks, + contactDetails.details.trustStatus )) method convertToItems*( diff --git a/src/app/modules/main/chat_section/base_item.nim b/src/app/modules/main/chat_section/base_item.nim index c21e090a18..6c8cbc42f0 100644 --- a/src/app/modules/main/chat_section/base_item.nim +++ b/src/app/modules/main/chat_section/base_item.nim @@ -1,4 +1,5 @@ import sequtils, sugar +import ../../../../app_service/service/contacts/dto/contacts import ../../shared_models/[color_hash_item, color_hash_model] @@ -22,10 +23,13 @@ type position: int categoryId: string highlight: bool + trustStatus: TrustStatus proc setup*(self: BaseItem, id, name, icon: string, color, emoji, description: string, - `type`: int, amIChatAdmin: bool, hasUnreadMessages: bool, notificationsCount: int, muted, blocked, active: bool, - position: int, categoryId: string = "", colorId: int = 0, colorHash: seq[ColorHashSegment] = @[], highlight: bool = false) = + `type`: int, amIChatAdmin: bool, hasUnreadMessages: bool, notificationsCount: int, muted, + blocked, active: bool, position: int, categoryId: string = "", colorId: int = 0, + colorHash: seq[ColorHashSegment] = @[], highlight: bool = false, + trustStatus: TrustStatus = TrustStatus.Unknown) = self.id = id self.name = name self.amIChatAdmin = amIChatAdmin @@ -45,13 +49,16 @@ proc setup*(self: BaseItem, id, name, icon: string, color, emoji, description: s self.position = position self.categoryId = categoryId self.highlight = highlight + self.trustStatus = trustStatus proc initBaseItem*(id, name, icon: string, color, emoji, description: string, `type`: int, amIChatAdmin: bool, hasUnreadMessages: bool, notificationsCount: int, muted, blocked, active: bool, - position: int, categoryId: string = "", colorId: int = 0, colorHash: seq[ColorHashSegment] = @[], highlight: bool = false): BaseItem = + position: int, categoryId: string = "", colorId: int = 0, colorHash: seq[ColorHashSegment] = @[], + highlight: bool = false, trustStatus: TrustStatus = TrustStatus.Unknown): BaseItem = result = BaseItem() result.setup(id, name, icon, color, emoji, description, `type`, amIChatAdmin, - hasUnreadMessages, notificationsCount, muted, blocked, active, position, categoryId, colorId, colorHash, highlight) + hasUnreadMessages, notificationsCount, muted, blocked, active, position, categoryId, colorId, + colorHash, highlight, trustStatus) proc delete*(self: BaseItem) = discard @@ -148,3 +155,9 @@ method highlight*(self: BaseItem): bool {.inline base.} = method `highlight=`*(self: var BaseItem, value: bool) {.inline base.} = self.highlight = value + +method trustStatus*(self: BaseItem): TrustStatus {.inline base.} = + self.trustStatus + +method `trustStatus=`*(self: var BaseItem, value: TrustStatus) {.inline base.} = + self.trustStatus = value diff --git a/src/app/modules/main/chat_section/chat_content/chat_details.nim b/src/app/modules/main/chat_section/chat_content/chat_details.nim index 7fed85846d..edccd08539 100644 --- a/src/app/modules/main/chat_section/chat_content/chat_details.nim +++ b/src/app/modules/main/chat_section/chat_content/chat_details.nim @@ -1,5 +1,6 @@ import NimQml + QtObject: type ChatDetails* = ref object of QObject # fixed props @@ -17,6 +18,7 @@ QtObject: notificationsCount: int muted: bool position: int + isUntrustworthy: bool isMutualContact: bool proc delete*(self: ChatDetails) = @@ -29,7 +31,7 @@ QtObject: proc setChatDetails*(self: ChatDetails, id: string, `type`: int, belongsToCommunity, isUsersListAvailable: bool, name, icon: string, color, description, emoji: string, hasUnreadMessages: bool, notificationsCount: int, muted: bool, position: int, - isMutualContact: bool = false) = + isUntrustworthy: bool, isMutualContact: bool = false) = self.id = id self.`type` = `type` self.belongsToCommunity = belongsToCommunity @@ -43,6 +45,7 @@ QtObject: self.notificationsCount = notificationsCount self.muted = muted self.position = position + self.isUntrustworthy = isUntrustworthy self.isMutualContact = isMutualContact proc getId(self: ChatDetails): string {.slot.} = @@ -174,3 +177,14 @@ QtObject: proc setIsMutualContact*(self: ChatDetails, value: bool) = # this is not a slot self.isMutualContact = value self.isMutualContactChanged() + + proc isUntrustworthyChanged(self: ChatDetails) {.signal.} + proc getIsUntrustworthy(self: ChatDetails): bool {.slot.} = + return self.isUntrustworthy + QtProperty[bool] isUntrustworthy: + read = getIsUntrustworthy + notify = isUntrustworthyChanged + + proc setIsUntrustworthy*(self: ChatDetails, value: bool) = # this is not a slot + self.isUntrustworthy = value + self.isUntrustworthyChanged() \ No newline at end of file diff --git a/src/app/modules/main/chat_section/chat_content/controller.nim b/src/app/modules/main/chat_section/chat_content/controller.nim index 40674036e3..b96aac0dec 100644 --- a/src/app/modules/main/chat_section/chat_content/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/controller.nim @@ -106,6 +106,18 @@ proc init*(self: Controller) = var args = ContactArgs(e) self.delegate.onContactDetailsUpdated(args.contactId) + self.events.on(SIGNAL_CONTACT_UNTRUSTWORTHY) do(e: Args): + var args = TrustArgs(e) + self.delegate.onContactDetailsUpdated(args.publicKey) + + self.events.on(SIGNAL_CONTACT_TRUSTED) do(e: Args): + var args = TrustArgs(e) + self.delegate.onContactDetailsUpdated(args.publicKey) + + self.events.on(SIGNAL_REMOVED_TRUST_STATUS) do(e: Args): + var args = TrustArgs(e) + self.delegate.onContactDetailsUpdated(args.publicKey) + self.events.on(SIGNAL_CONTACT_UPDATED) do(e: Args): var args = ContactArgs(e) self.delegate.onContactDetailsUpdated(args.contactId) diff --git a/src/app/modules/main/chat_section/chat_content/messages/controller.nim b/src/app/modules/main/chat_section/chat_content/messages/controller.nim index a633bb5eb1..4f9de560b1 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/controller.nim @@ -111,6 +111,18 @@ proc init*(self: Controller) = var args = ContactArgs(e) self.delegate.updateContactDetails(args.contactId) + self.events.on(SIGNAL_CONTACT_UNTRUSTWORTHY) do(e: Args): + var args = TrustArgs(e) + self.delegate.updateContactDetails(args.publicKey) + + self.events.on(SIGNAL_CONTACT_TRUSTED) do(e: Args): + var args = TrustArgs(e) + self.delegate.updateContactDetails(args.publicKey) + + self.events.on(SIGNAL_REMOVED_TRUST_STATUS) do(e: Args): + var args = TrustArgs(e) + self.delegate.updateContactDetails(args.publicKey) + self.events.on(SIGNAL_CONTACT_UPDATED) do(e: Args): var args = ContactArgs(e) self.delegate.updateContactDetails(args.contactId) diff --git a/src/app/modules/main/chat_section/chat_content/messages/module.nim b/src/app/modules/main/chat_section/chat_content/messages/module.nim index 1ce73f53f1..2e15916cb7 100644 --- a/src/app/modules/main/chat_section/chat_content/messages/module.nim +++ b/src/app/modules/main/chat_section/chat_content/messages/module.nim @@ -8,6 +8,7 @@ import ../../../../shared_models/message_reaction_item import ../../../../shared_models/message_transaction_parameters_item import ../../../../../global/global_singleton import ../../../../../core/eventemitter +import ../../../../../../app_service/service/contacts/dto/contacts import ../../../../../../app_service/service/contacts/service as contact_service import ../../../../../../app_service/service/community/service as community_service import ../../../../../../app_service/service/chat/service as chat_service @@ -92,7 +93,8 @@ proc createFetchMoreMessagesItem(self: Module): Item = stickerPack = -1, @[], newTransactionParametersItem("","","","","","",-1,""), - @[] + @[], + TrustStatus.Unknown ) proc createChatIdentifierItem(self: Module): Item = @@ -128,7 +130,8 @@ proc createChatIdentifierItem(self: Module): Item = stickerPack = -1, @[], newTransactionParametersItem("","","","","","",-1,""), - @[] + @[], + TrustStatus.Unknown ) proc checkIfMessageLoadedAndScrollToItIfItIs(self: Module): bool = @@ -198,8 +201,9 @@ method newMessagesLoaded*(self: Module, messages: seq[MessageDto], reactions: se m.transactionParameters.transactionHash, m.transactionParameters.commandState, m.transactionParameters.signature), - m.mentionedUsersPks() - ) + m.mentionedUsersPks(), + sender.details.trustStatus, + ) for r in reactions: if(r.messageId == m.id): @@ -285,7 +289,8 @@ method messageAdded*(self: Module, message: MessageDto) = message.transactionParameters.transactionHash, message.transactionParameters.commandState, message.transactionParameters.signature), - message.mentionedUsersPks + message.mentionedUsersPks, + sender.details.trustStatus, ) self.view.model().insertItemBasedOnTimestamp(item) @@ -398,6 +403,7 @@ method updateContactDetails*(self: Module, contactId: string) = item.senderLocalName = updatedContact.details.localNickname item.senderIcon = updatedContact.icon item.senderIsAdded = updatedContact.details.added + item.senderTrustStatus = updatedContact.details.trustStatus if(item.messageContainsMentions): let (m, _, err) = self.controller.getMessageDetails(item.id) if(err.len == 0): diff --git a/src/app/modules/main/chat_section/chat_content/module.nim b/src/app/modules/main/chat_section/chat_content/module.nim index 842236ba8e..090fc8e432 100644 --- a/src/app/modules/main/chat_section/chat_content/module.nim +++ b/src/app/modules/main/chat_section/chat_content/module.nim @@ -76,17 +76,20 @@ method load*(self: Module) = var chatName = chatDto.name var chatImage = chatDto.icon var isMutualContact = false + var trustStatus = TrustStatus.Unknown if(chatDto.chatType == ChatType.OneToOne): let contactDto = self.controller.getContactById(self.controller.getMyChatId()) chatName = contactDto.userNameOrAlias() isMutualContact = contactDto.isMutualContact + trustStatus = contactDto.trustStatus if(contactDto.image.thumbnail.len > 0): chatImage = contactDto.image.thumbnail self.view.load(chatDto.id, chatDto.chatType.int, self.controller.belongsToCommunity(), self.controller.isUsersListAvailable(), chatName, chatImage, chatDto.color, chatDto.description, chatDto.emoji, hasNotification, notificationsCount, - chatDto.muted, chatDto.position, isMutualContact) + chatDto.muted, chatDto.position, isUntrustworthy = trustStatus == TrustStatus.Untrustworthy, + isMutualContact) self.inputAreaModule.load() self.messagesModule.load() @@ -188,7 +191,8 @@ proc buildPinnedMessageItem(self: Module, messageId: string, actionInitiatedBy: m.transactionParameters.transactionHash, m.transactionParameters.commandState, m.transactionParameters.signature), - m.mentionedUsersPks + m.mentionedUsersPks, + contactDetails.details.trustStatus, ) item.pinned = true item.pinnedBy = actionInitiatedBy @@ -317,6 +321,7 @@ method onContactDetailsUpdated*(self: Module, contactId: string) = item.senderDisplayName = updatedContact.displayName item.senderLocalName = updatedContact.details.localNickname item.senderIcon = updatedContact.icon + item.senderTrustStatus = updatedContact.details.trustStatus if(item.messageContainsMentions): let (m, _, err) = self.controller.getMessageDetails(item.id) if(err.len == 0): @@ -325,6 +330,7 @@ method onContactDetailsUpdated*(self: Module, contactId: string) = if(self.controller.getMyChatId() == contactId): self.view.updateChatDetailsNameAndIcon(updatedContact.displayName, updatedContact.icon) + self.view.updateTrustStatus(updatedContact.details.trustStatus == TrustStatus.Untrustworthy) method onNotificationsUpdated*(self: Module, hasUnreadMessages: bool, notificationCount: int) = self.view.updateChatDetailsNotifications(hasUnreadMessages, notificationCount) @@ -345,3 +351,6 @@ method onMutualContactChanged*(self: Module) = let contactDto = self.controller.getContactById(self.controller.getMyChatId()) let isMutualContact = contactDto.isMutualContact self.view.onMutualContactChanged(isMutualContact) + +method contactTrustStatusChanged*(self: Module, publicKey: string, isUntrustworthy: bool) = + self.view.updateTrustStatus(isUntrustworthy) diff --git a/src/app/modules/main/chat_section/chat_content/users/controller.nim b/src/app/modules/main/chat_section/chat_content/users/controller.nim index cceed6361e..5837c12e9d 100644 --- a/src/app/modules/main/chat_section/chat_content/users/controller.nim +++ b/src/app/modules/main/chat_section/chat_content/users/controller.nim @@ -91,6 +91,18 @@ proc init*(self: Controller) = # Events only for the user list, so not needed in public and one to one chats if(self.isUsersListAvailable): + self.events.on(SIGNAL_CONTACT_UNTRUSTWORTHY) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactUpdated(args.publicKey) + + self.events.on(SIGNAL_CONTACT_TRUSTED) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactUpdated(args.publicKey) + + self.events.on(SIGNAL_REMOVED_TRUST_STATUS) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactUpdated(args.publicKey) + self.events.on(SIGNAL_CONTACTS_STATUS_UPDATED) do(e: Args): let args = ContactsStatusUpdatedArgs(e) self.delegate.contactsStatusUpdated(args.statusUpdates) diff --git a/src/app/modules/main/chat_section/chat_content/users/module.nim b/src/app/modules/main/chat_section/chat_content/users/module.nim index 3260abd1f4..bb956681ac 100644 --- a/src/app/modules/main/chat_section/chat_content/users/module.nim +++ b/src/app/modules/main/chat_section/chat_content/users/module.nim @@ -7,6 +7,7 @@ import ../../../../../global/global_singleton import ../../../../../core/eventemitter import ../../../../../../app_service/common/conversion import ../../../../../../app_service/common/types +import ../../../../../../app_service/service/contacts/dto/contacts 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 @@ -81,6 +82,7 @@ method onNewMessagesLoaded*(self: Module, messages: seq[MessageDto]) = icon = contactDetails.icon, onlineStatus = status, isContact = contactDetails.details.added, + isUntrustworthy = contactDetails.details.trustStatus == TrustStatus.Untrustworthy, ) ) @@ -108,6 +110,7 @@ method contactUpdated*(self: Module, publicKey: string) = alias = contactDetails.details.alias, icon = contactDetails.icon, isContact = contactDetails.details.added, #FIXME + isUntrustworthy = contactDetails.details.trustStatus == TrustStatus.Untrustworthy, ) method loggedInUserImageChanged*(self: Module) = @@ -148,8 +151,9 @@ method addChatMember*(self: Module, member: ChatMember) = onlineStatus = status, isContact = contactDetails.details.added, #FIXME isAdmin = member.admin, - joined = member.joined) - ) + joined = member.joined, + isUntrustworthy = contactDetails.details.trustStatus == TrustStatus.Untrustworthy + )) method onChatMembersAdded*(self: Module, ids: seq[string]) = for id in ids: @@ -184,7 +188,9 @@ method onChatMemberUpdated*(self: Module, publicKey: string, admin: bool, joined icon = contactDetails.icon, isContact = contactDetails.details.added, isAdmin = admin, - joined = joined) + joined = joined, + isUntrustworthy = contactDetails.details.trustStatus == TrustStatus.Untrustworthy, + ) method getMembersPublicKeys*(self: Module): string = let publicKeys = self.controller.getMembersPublicKeys() diff --git a/src/app/modules/main/chat_section/chat_content/view.nim b/src/app/modules/main/chat_section/chat_content/view.nim index c3ec185d4d..0b03fecdfb 100644 --- a/src/app/modules/main/chat_section/chat_content/view.nim +++ b/src/app/modules/main/chat_section/chat_content/view.nim @@ -1,5 +1,7 @@ import NimQml import ../../../shared_models/message_model as pinned_msg_model +import ../../../../../app_service/service/contacts/dto/contacts as contacts_dto + import io_interface import chat_details @@ -32,9 +34,11 @@ QtObject: proc load*(self: View, id: string, `type`: int, belongsToCommunity, isUsersListAvailable: bool, name, icon: string, color, description, emoji: string, hasUnreadMessages: bool, - notificationsCount: int, muted: bool, position: int, isMutualContact: bool) = - self.chatDetails.setChatDetails(id, `type`, belongsToCommunity, isUsersListAvailable, name, icon, - color, description, emoji, hasUnreadMessages, notificationsCount, muted, position, isMutualContact) + notificationsCount: int, muted: bool, position: int, isUntrustworthy: bool, + isMutualContact: bool) = + self.chatDetails.setChatDetails(id, `type`, belongsToCommunity, isUsersListAvailable, name, + icon, color, description, emoji, hasUnreadMessages, notificationsCount, muted, position, + isUntrustworthy, isMutualContact) self.delegate.viewDidLoad() self.chatDetailsChanged() @@ -95,6 +99,9 @@ QtObject: self.chatDetails.setName(name) self.chatDetails.setIcon(icon) + proc updateTrustStatus*(self: View, isUntrustworthy: bool) = + self.chatDetails.setIsUntrustworthy(isUntrustworthy) + proc updateChatDetailsNotifications*(self: View, hasUnreadMessages: bool, notificationCount: int) = self.chatDetails.setHasUnreadMessages(hasUnreadMessages) self.chatDetails.setNotificationCount(notificationCount) diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 1661e673d8..ed36010977 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -180,6 +180,18 @@ proc init*(self: Controller) = var args = ContactArgs(e) self.delegate.onContactDetailsUpdated(args.contactId) + self.events.on(SIGNAL_CONTACT_UNTRUSTWORTHY) do(e: Args): + var args = TrustArgs(e) + self.delegate.onContactDetailsUpdated(args.publicKey) + + self.events.on(SIGNAL_CONTACT_TRUSTED) do(e: Args): + var args = TrustArgs(e) + self.delegate.onContactDetailsUpdated(args.publicKey) + + self.events.on(SIGNAL_REMOVED_TRUST_STATUS) do(e: Args): + var args = TrustArgs(e) + self.delegate.onContactDetailsUpdated(args.publicKey) + self.events.on(SIGNAL_CHAT_RENAMED) do(e: Args): var args = ChatRenameArgs(e) self.delegate.onChatRenamed(args.id, args.newName) diff --git a/src/app/modules/main/chat_section/item.nim b/src/app/modules/main/chat_section/item.nim index 61120b3244..7e52d5f23f 100644 --- a/src/app/modules/main/chat_section/item.nim +++ b/src/app/modules/main/chat_section/item.nim @@ -39,6 +39,7 @@ proc `$`*(self: Item): string = position: {self.position}, categoryId: {self.categoryId}, highlight: {self.highlight}, + trustStatus: {self.trustStatus}, subItems:[ {$self.subItems} ]""" @@ -60,7 +61,8 @@ proc toJsonNode*(self: Item): JsonNode = "active": self.active, "position": self.position, "categoryId": self.categoryId, - "highlight": self.highlight + "highlight": self.highlight, + "trustStatus": self.trustStatus, } proc appendSubItems*(self: Item, items: seq[SubItem]) = diff --git a/src/app/modules/main/chat_section/model.nim b/src/app/modules/main/chat_section/model.nim index bb72ce6c38..d7219d9330 100644 --- a/src/app/modules/main/chat_section/model.nim +++ b/src/app/modules/main/chat_section/model.nim @@ -1,5 +1,6 @@ import NimQml, Tables, strutils, strformat, json, algorithm from ../../../../app_service/service/chat/dto/chat import ChatType +from ../../../../app_service/service/contacts/dto/contacts import TrustStatus import item, sub_item, base_item, sub_model type @@ -24,6 +25,7 @@ type IsCategory CategoryId Highlight + TrustStatus QtObject: type @@ -91,7 +93,8 @@ QtObject: ModelRole.SubItems.int:"subItems", ModelRole.IsCategory.int:"isCategory", ModelRole.CategoryId.int:"categoryId", - ModelRole.Highlight.int:"highlight" + ModelRole.Highlight.int:"highlight", + ModelRole.TrustStatus.int:"trustStatus", }.toTable method data(self: Model, index: QModelIndex, role: int): QVariant = @@ -145,6 +148,8 @@ QtObject: result = newQVariant(item.categoryId) of ModelRole.Highlight: result = newQVariant(item.highlight) + of ModelRole.TrustStatus: + result = newQVariant(item.trustStatus.int) proc appendItem*(self: Model, item: Item) = let parentModelIndex = newQModelIndex() @@ -257,14 +262,16 @@ QtObject: if self.items[i].subItems.blockUnblockItemById(id, blocked): return - proc updateItemDetails*(self: Model, id, name, icon: string) = + proc updateItemDetails*(self: Model, id, name, icon: string, trustStatus: TrustStatus) = ## This updates only first level items, it doesn't update subitems, since subitems cannot have custom icon. for i in 0 ..< self.items.len: if(self.items[i].id == id): self.items[i].BaseItem.name = name self.items[i].BaseItem.icon = icon + self.items[i].BaseItem.trustStatus = trustStatus let index = self.createIndex(i, 0, nil) - self.dataChanged(index, index, @[ModelRole.Name.int, ModelRole.Icon.int]) + self.dataChanged(index, index, @[ModelRole.Name.int, ModelRole.Icon.int, + ModelRole.TrustStatus.int]) return proc renameItem*(self: Model, id: string, name: string) = diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 9201c255b1..ee2a15d81e 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -21,6 +21,7 @@ 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 +import ../../../../app_service/service/contacts/dto/contacts as contacts_dto export io_interface @@ -210,7 +211,7 @@ proc createItemFromPublicKey(self: Module, publicKey: string): UserItem = isContact = contactDetails.details.isMutualContact(), isVerified = contactDetails.details.isContactVerified(), isUntrustworthy = contactDetails.details.isContactUntrustworthy(), - isBlocked = contactDetails.details.isBlocked() + isBlocked = contactDetails.details.isBlocked(), ) proc initContactRequestsModel(self: Module) = @@ -650,7 +651,8 @@ method onContactDetailsUpdated*(self: Module, publicKey: string) = let chatName = contactDetails.displayName let chatImage = contactDetails.icon - self.view.chatsModel().updateItemDetails(publicKey, chatName, chatImage) + let trustStatus = contactDetails.details.trustStatus + self.view.chatsModel().updateItemDetails(publicKey, chatName, chatImage, trustStatus) method onNewMessagesReceived*(self: Module, sectionIdMsgBelongsTo: string, chatIdMsgBelongsTo: string, chatTypeMsgBelongsTo: ChatType, unviewedMessagesCount: int, unviewedMentionsCount: int, message: MessageDto) = diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index 81d73e2864..c1e0907c55 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -174,6 +174,18 @@ proc init*(self: Controller) = var args = ContactArgs(e) self.delegate.contactUpdated(args.contactId) + self.events.on(SIGNAL_CONTACT_UNTRUSTWORTHY) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactUpdated(args.publicKey) + + self.events.on(SIGNAL_CONTACT_TRUSTED) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactUpdated(args.publicKey) + + self.events.on(SIGNAL_REMOVED_TRUST_STATUS) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactUpdated(args.publicKey) + self.events.on(SIGNAL_MNEMONIC_REMOVAL) do(e: Args): self.delegate.mnemonicBackedUp() diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index 1b858b3e31..a74ee4daf6 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -21,6 +21,7 @@ import activity_center/module as activity_center_module import communities/module as communities_module import node_section/module as node_section_module import networks/module as networks_module +import ../../../app_service/service/contacts/dto/contacts import ../../../app_service/service/keychain/service as keychain_service import ../../../app_service/service/chat/service as chat_service @@ -692,7 +693,10 @@ method getContactDetailsAsJson*[T](self: Module[T], publicKey: string): string = "isBlocked":contact.blocked, "requestReceived":contact.hasAddedUs, "isSyncing":contact.isSyncing, - "removed":contact.removed + "removed":contact.removed, + "trustStatus": contact.trustStatus.int, + "verificationStatus": contact.verificationStatus.int, + "hasAddedUs": contact.hasAddedUs } return $jsonObj @@ -736,6 +740,7 @@ method contactUpdated*[T](self: Module[T], publicKey: string) = contactDetails.details.localNickname, contactDetails.details.alias, contactDetails.icon, + isUntrustworthy = contactDetails.details.isContactUntrustworthy(), ) method calculateProfileSectionHasNotification*[T](self: Module[T]): bool = diff --git a/src/app/modules/main/profile_section/contacts/controller.nim b/src/app/modules/main/profile_section/contacts/controller.nim index 9c68d9ea4e..05cd02a5da 100644 --- a/src/app/modules/main/profile_section/contacts/controller.nim +++ b/src/app/modules/main/profile_section/contacts/controller.nim @@ -49,10 +49,38 @@ proc init*(self: Controller) = var args = ContactArgs(e) self.delegate.contactNicknameChanged(args.contactId) + self.events.on(SIGNAL_CONTACT_UNTRUSTWORTHY) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactTrustStatusChanged(args.publicKey, args.isUntrustworthy) + + self.events.on(SIGNAL_CONTACT_TRUSTED) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactTrustStatusChanged(args.publicKey, args.isUntrustworthy) + + self.events.on(SIGNAL_REMOVED_TRUST_STATUS) do(e: Args): + var args = TrustArgs(e) + self.delegate.contactTrustStatusChanged(args.publicKey, args.isUntrustworthy) + self.events.on(SIGNAL_CONTACT_UPDATED) do(e: Args): var args = ContactArgs(e) self.delegate.contactUpdated(args.contactId) + self.events.on(SIGNAL_CONTACT_VERIFICATION_DECLINED) do(e: Args): + var args = ContactArgs(e) + self.delegate.onVerificationRequestDeclined(args.contactId) + + self.events.on(SIGNAL_CONTACT_VERIFICATION_ADDED) do(e: Args): + var args = VerificationRequestArgs(e) + self.delegate.onVerificationRequestUpdatedOrAdded(args.verificationRequest) + + self.events.on(SIGNAL_CONTACT_VERIFICATION_UPDATED) do(e: Args): + var args = VerificationRequestArgs(e) + self.delegate.onVerificationRequestUpdatedOrAdded(args.verificationRequest) + + self.events.on(SIGNAL_CONTACT_VERIFICATION_ACCEPTED) do(e: Args): + var args = VerificationRequestArgs(e) + self.delegate.onVerificationRequestUpdatedOrAdded(args.verificationRequest) + proc getContacts*(self: Controller, group: ContactsGroup): seq[ContactsDto] = return self.contactsService.getContactsByGroup(group) @@ -88,4 +116,40 @@ proc removeContactRequestRejection*(self: Controller, publicKey: string) = self.contactsService.removeContactRequestRejection(publicKey) proc switchToOrCreateOneToOneChat*(self: Controller, chatId: string) = - self.chatService.switchToOrCreateOneToOneChat(chatId, "") \ No newline at end of file + self.chatService.switchToOrCreateOneToOneChat(chatId, "") + +proc markUntrustworthy*(self: Controller, publicKey: string) = + self.contactsService.markUntrustworthy(publicKey) + +proc removeTrustStatus*(self: Controller, publicKey: string) = + self.contactsService.removeTrustStatus(publicKey) + +proc getVerificationRequestSentTo*(self: Controller, publicKey: string): VerificationRequest = + self.contactsService.getVerificationRequestSentTo(publicKey) + +proc getVerificationRequestFrom*(self: Controller, publicKey: string): VerificationRequest = + self.contactsService.getVerificationRequestFrom(publicKey) + +proc sendVerificationRequest*(self: Controller, publicKey: string, challenge: string) = + self.contactsService.sendVerificationRequest(publicKey, challenge) + +proc cancelVerificationRequest*(self: Controller, publicKey: string) = + self.contactsService.cancelVerificationRequest(publicKey) + +proc verifiedTrusted*(self: Controller, publicKey: string) = + self.contactsService.verifiedTrusted(publicKey) + +proc verifiedUntrustworthy*(self: Controller, publicKey: string) = + self.contactsService.verifiedUntrustworthy(publicKey) + +proc acceptVerificationRequest*(self: Controller, publicKey: string, response: string) = + self.contactsService.acceptVerificationRequest(publicKey, response) + +proc declineVerificationRequest*(self: Controller, publicKey: string) = + self.contactsService.declineVerificationRequest(publicKey) + +proc getReceivedVerificationRequests*(self: Controller): seq[VerificationRequest] = + self.contactsService.getReceivedVerificationRequests() + +proc hasReceivedVerificationRequestFrom*(self: Controller, fromId: string): bool = + self.contactsService.hasReceivedVerificationRequestFrom(fromId) diff --git a/src/app/modules/main/profile_section/contacts/io_interface.nim b/src/app/modules/main/profile_section/contacts/io_interface.nim index c0f44de98f..2c2efb68a7 100644 --- a/src/app/modules/main/profile_section/contacts/io_interface.nim +++ b/src/app/modules/main/profile_section/contacts/io_interface.nim @@ -1,4 +1,5 @@ import NimQml +import ../../../../../app_service/service/contacts/dto/contacts as contacts type AccessInterface* {.pure inheritable.} = ref object of RootObj @@ -69,8 +70,53 @@ method contactRemoved*(self: AccessInterface, publicKey: string) {.base.} = method contactNicknameChanged*(self: AccessInterface, publicKey: string) {.base.} = raise newException(ValueError, "No implementation available") +method contactTrustStatusChanged*(self: AccessInterface, publicKey: string, isUntrustworthy: bool) {.base.} = + raise newException(ValueError, "No implementation available") + method contactUpdated*(self: AccessInterface, publicKey: string) {.base.} = raise newException(ValueError, "No implementation available") +method markUntrustworthy*(self: AccessInterface, publicKey: string): void {.base.} = + raise newException(ValueError, "No implementation available") + +method removeTrustStatus*(self: AccessInterface, publicKey: string): void {.base.} = + raise newException(ValueError, "No implementation available") + +method getSentVerificationDetailsAsJson*(self: AccessInterface, publicKey: string): string = + raise newException(ValueError, "No implementation available") + +method getVerificationDetailsFromAsJson*(self: AccessInterface, publicKey: string): string = + raise newException(ValueError, "No implementation available") + +method sendVerificationRequest*(self: AccessInterface, publicKey: string, challenge: string) = + raise newException(ValueError, "No implementation available") + +method cancelVerificationRequest*(self: AccessInterface, publicKey: string) = + raise newException(ValueError, "No implementation available") + +method verifiedTrusted*(self: AccessInterface, publicKey: string): void {.base.} = + raise newException(ValueError, "No implementation available") + +method verifiedUntrustworthy*(self: AccessInterface, publicKey: string): void {.base.} = + raise newException(ValueError, "No implementation available") + +method declineVerificationRequest*(self: AccessInterface, publicKey: string): void {.base.} = + raise newException(ValueError, "No implementation available") + +method acceptVerificationRequest*(self: AccessInterface, publicKey: string, response: string): void {.base.} = + raise newException(ValueError, "No implementation available") + method contactRequestRejectionRemoved*(self: AccessInterface, publicKey: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method getReceivedVerificationRequests*(self: AccessInterface): seq[VerificationRequest] {.base.} = + raise newException(ValueError, "No implementation available") + +method hasReceivedVerificationRequestFrom*(self: AccessInterface, fromId: string): bool {.base.} = + raise newException(ValueError, "No implementation available") + +method onVerificationRequestDeclined*(self: AccessInterface, publicKey: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onVerificationRequestUpdatedOrAdded*(self: AccessInterface, VerificationRequest: VerificationRequest) {.base.} = raise newException(ValueError, "No implementation available") \ No newline at end of file diff --git a/src/app/modules/main/profile_section/contacts/module.nim b/src/app/modules/main/profile_section/contacts/module.nim index 6f4e9ad1a7..3c2abd40cb 100644 --- a/src/app/modules/main/profile_section/contacts/module.nim +++ b/src/app/modules/main/profile_section/contacts/module.nim @@ -1,12 +1,13 @@ import NimQml, chronicles -import io_interface, view, controller +import io_interface, view, controller, json import ../../../shared_models/user_item import ../../../shared_models/user_model import ../io_interface as delegate_interface import ../../../../global/global_singleton import ../../../../core/eventemitter +import ../../../../../app_service/service/contacts/dto/contacts as contacts_dto import ../../../../../app_service/service/contacts/service as contacts_service import ../../../../../app_service/service/chat/service as chat_service @@ -76,6 +77,16 @@ method viewDidLoad*(self: Module) = # Temporary commented until we provide appropriate flags on the `status-go` side to cover all sections. # self.buildModel(self.view.receivedButRejectedContactRequestsModel(), ContactsGroup.IncomingRejectedContactRequests) # self.buildModel(self.view.sentButRejectedContactRequestsModel(), ContactsGroup.IncomingRejectedContactRequests) + + let receivedVerificationRequests = self.controller.getReceivedVerificationRequests() + var receivedVerificationRequestItems: seq[UserItem] = @[] + for receivedVerificationRequest in receivedVerificationRequests: + if receivedVerificationRequest.status == VerificationStatus.Verifying or + receivedVerificationRequest.status == VerificationStatus.Verified: + let contactItem = self.createItemFromPublicKey(receivedVerificationRequest.fromID) + contactItem.incomingVerificationStatus = VerificationRequestStatus(receivedVerificationRequest.status) + receivedVerificationRequestItems.add(contactItem) + self.view.receivedContactRequestsModel().addItems(receivedVerificationRequestItems) self.moduleLoaded = true self.delegate.contactsModuleDidLoad() @@ -172,3 +183,86 @@ method contactNicknameChanged*(self: Module, publicKey: string) = # self.view.receivedButRejectedContactRequestsModel().updateName(publicKey, name) # self.view.sentButRejectedContactRequestsModel().updateName(publicKey, name) self.view.blockedContactsModel().updateName(publicKey, name) + +method contactTrustStatusChanged*(self: Module, publicKey: string, isUntrustworthy: bool) = + self.view.myMutualContactsModel().updateTrustStatus(publicKey, isUntrustworthy) + self.view.blockedContactsModel().updateTrustStatus(publicKey, isUntrustworthy) + +method markUntrustworthy*(self: Module, publicKey: string): void = + self.controller.markUntrustworthy(publicKey) + +method removeTrustStatus*(self: Module, publicKey: string): void = + self.controller.removeTrustStatus(publicKey) + +method getSentVerificationDetailsAsJson*(self: Module, publicKey: string): string = + let verificationRequest = self.controller.getVerificationRequestSentTo(publicKey) + let (name, image, largeImage) = self.controller.getContactNameAndImage(publicKey) + let jsonObj = %* { + "challenge": verificationRequest.challenge, + "response": verificationRequest.response, + "requestedAt": verificationRequest.requestedAt, + "requestStatus": verificationRequest.status.int, + "repliedAt": verificationRequest.repliedAt, + "icon": image, + "largeImage": largeImage, + "displayName": name + } + return $jsonObj + +method getVerificationDetailsFromAsJson*(self: Module, publicKey: string): string = + let verificationRequest = self.controller.getVerificationRequestFrom(publicKey) + let (name, image, largeImage) = self.controller.getContactNameAndImage(publicKey) + let jsonObj = %* { + "from": verificationRequest.fromId, + "challenge": verificationRequest.challenge, + "response": verificationRequest.response, + "requestedAt": verificationRequest.requestedAt, + "requestStatus": verificationRequest.status.int, + "repliedAt": verificationRequest.repliedAt, + "icon": image, + "largeImage": largeImage, + "displayName": name + } + return $jsonObj + +method sendVerificationRequest*(self: Module, publicKey: string, challenge: string) = + self.controller.sendVerificationRequest(publicKey, challenge) + +method cancelVerificationRequest*(self: Module, publicKey: string) = + self.controller.cancelVerificationRequest(publicKey) + +method verifiedTrusted*(self: Module, publicKey: string) = + self.controller.verifiedTrusted(publicKey) + +method verifiedUntrustworthy*(self: Module, publicKey: string) = + self.controller.verifiedUntrustworthy(publicKey) + +method declineVerificationRequest*(self: Module, publicKey: string) = + self.controller.declineVerificationRequest(publicKey) + +method acceptVerificationRequest*(self: Module, publicKey: string, response: string) = + self.controller.acceptVerificationRequest(publicKey, response) + +method getReceivedVerificationRequests*(self: Module): seq[VerificationRequest] = + self.controller.getReceivedVerificationRequests() + +method hasReceivedVerificationRequestFrom*(self: Module, fromId: string): bool = + result = self.controller.hasReceivedVerificationRequestFrom(fromId) + +method onVerificationRequestDeclined*(self: Module, publicKey: string) = + self.view.receivedContactRequestsModel.removeItemById(publicKey) + +method onVerificationRequestUpdatedOrAdded*(self: Module, request: VerificationRequest) = + let item = self.createItemFromPublicKey(request.fromID) + item.incomingVerificationStatus = VerificationRequestStatus(request.status) + if (self.view.receivedContactRequestsModel.containsItemWithPubKey(request.fromID)): + if request.status != VerificationStatus.Verifying and + request.status != VerificationStatus.Verified: + self.view.receivedContactRequestsModel.removeItemById(request.fromID) + return + self.view.receivedContactRequestsModel.updateIncomingRequestStatus( + item.pubKey, + item.incomingVerificationStatus + ) + return + self.view.receivedContactRequestsModel.addItem(item) \ No newline at end of file diff --git a/src/app/modules/main/profile_section/contacts/view.nim b/src/app/modules/main/profile_section/contacts/view.nim index 2979f5cd06..f37e4f6500 100644 --- a/src/app/modules/main/profile_section/contacts/view.nim +++ b/src/app/modules/main/profile_section/contacts/view.nim @@ -33,8 +33,8 @@ QtObject: # Temporary commented until we provide appropriate flags on the `status-go` side to cover all sections. # self.receivedButRejectedContactRequestsModel.delete # self.receivedButRejectedContactRequestsModelVariant.delete - # self.sentButRejectedContactRequestsModel.delete # self.sentButRejectedContactRequestsModelVariant.delete + # self.sentButRejectedContactRequestsModel.delete self.QObject.delete proc newView*(delegate: io_interface.AccessInterface): View = @@ -150,5 +150,37 @@ QtObject: proc removeContact*(self: View, publicKey: string) {.slot.} = self.delegate.removeContact(publicKey) + proc markUntrustworthy*(self: View, publicKey: string) {.slot.} = + self.delegate.markUntrustworthy(publicKey) + + proc removeTrustStatus*(self: View, publicKey: string) {.slot.} = + self.delegate.removeTrustStatus(publicKey) + proc removeContactRequestRejection*(self: View, publicKey: string) {.slot.} = - self.delegate.removeContactRequestRejection(publicKey) \ No newline at end of file + self.delegate.removeContactRequestRejection(publicKey) + proc getSentVerificationDetailsAsJson(self: View, publicKey: string): string {.slot.} = + return self.delegate.getSentVerificationDetailsAsJson(publicKey) + + proc getVerificationDetailsFromAsJson(self: View, publicKey: string): string {.slot.} = + return self.delegate.getVerificationDetailsFromAsJson(publicKey) + + proc sendVerificationRequest*(self: View, publicKey: string, challenge: string) {.slot.} = + self.delegate.sendVerificationRequest(publicKey, challenge) + + proc cancelVerificationRequest*(self: View, publicKey: string) {.slot.} = + self.delegate.cancelVerificationRequest(publicKey) + + proc verifiedTrusted*(self: View, publicKey: string) {.slot.} = + self.delegate.verifiedTrusted(publicKey) + + proc verifiedUntrustworthy*(self: View, publicKey: string) {.slot.} = + self.delegate.verifiedUntrustworthy(publicKey) + + proc declineVerificationRequest*(self: View, publicKey: string) {.slot.} = + self.delegate.declineVerificationRequest(publicKey) + + proc acceptVerificationRequest*(self: View, publicKey: string, response: string) {.slot.} = + self.delegate.acceptVerificationRequest(publicKey, response) + + proc hasReceivedVerificationRequestFrom*(self: View, fromId: string): bool {.slot.} = + result = self.delegate.hasReceivedVerificationRequestFrom(fromId) \ No newline at end of file diff --git a/src/app/modules/shared_models/active_section.nim b/src/app/modules/shared_models/active_section.nim index e69f4179a5..489b20eee5 100644 --- a/src/app/modules/shared_models/active_section.nim +++ b/src/app/modules/shared_models/active_section.nim @@ -1,5 +1,6 @@ import NimQml import section_item +import ../../../app_service/service/contacts/dto/contacts QtObject: type ActiveSection* = ref object of QObject @@ -182,8 +183,8 @@ QtObject: localNickname: string, alias: string, image: string, - ) = - self.item.updateMember(pubkey, name, ensName, localNickname, alias, image) + isUntrustworthy: bool) = + self.item.updateMember(pubkey, name, ensName, localNickname, alias, image, isUntrustworthy) proc pendingRequestsToJoin(self: ActiveSection): QVariant {.slot.} = if (self.item.id == ""): diff --git a/src/app/modules/shared_models/member_item.nim b/src/app/modules/shared_models/member_item.nim index 46cf75f632..5310a00678 100644 --- a/src/app/modules/shared_models/member_item.nim +++ b/src/app/modules/shared_models/member_item.nim @@ -24,8 +24,8 @@ proc initMemberItem*( isUntrustworthy: bool = false, isBlocked: bool = false, contactRequest: ContactRequest = ContactRequest.None, - incomingVerification: VerificationRequest = VerificationRequest.None, - outcomingVerification: VerificationRequest = VerificationRequest.None, + incomingVerificationStatus: VerificationRequestStatus = VerificationRequestStatus.None, + outgoingVerificationStatus: VerificationRequestStatus = VerificationRequestStatus.None, isAdmin: bool = false, joined: bool = false, ): MemberItem = @@ -47,8 +47,8 @@ proc initMemberItem*( isUntrustworthy = isUntrustworthy, isBlocked = isBlocked, contactRequest = contactRequest, - incomingVerification = incomingVerification, - outcomingVerification = outcomingVerification + incomingVerificationStatus = incomingVerificationStatus, + outgoingVerificationStatus = outgoingVerificationStatus ) proc `$`*(self: MemberItem): string = @@ -67,8 +67,8 @@ proc `$`*(self: MemberItem): string = isUntrustworthy: {self.isUntrustworthy}, isBlocked: {self.isBlocked}, contactRequest: {$self.contactRequest.int}, - incomingVerification: {$self.incomingVerification.int}, - outcomingVerification: {$self.outcomingVerification.int}, + incomingVerificationStatus: {$self.incomingVerificationStatus.int}, + outgoingVerificationStatus: {$self.outgoingVerificationStatus.int}, isAdmin: {self.isAdmin}, joined: {self.joined} ]""" diff --git a/src/app/modules/shared_models/member_model.nim b/src/app/modules/shared_models/member_model.nim index f63f078d7c..013bc87f72 100644 --- a/src/app/modules/shared_models/member_model.nim +++ b/src/app/modules/shared_models/member_model.nim @@ -2,6 +2,7 @@ import NimQml, Tables, strformat, sequtils, sugar # TODO: use generics to remove duplication between user_model and member_model +import ../../../app_service/service/contacts/dto/contacts import member_item type @@ -20,8 +21,8 @@ type IsUntrustworthy IsBlocked ContactRequest - IncomingVerification - OutcomingVerification + IncomingVerificationStatus + OutgoingVerificationStatus IsAdmin Joined @@ -81,8 +82,8 @@ QtObject: ModelRole.IsUntrustworthy.int: "isUntrustworthy", ModelRole.IsBlocked.int: "isBlocked", ModelRole.ContactRequest.int: "contactRequest", - ModelRole.IncomingVerification.int: "incomingVerification", - ModelRole.OutcomingVerification.int: "outcomingVerification", + ModelRole.IncomingVerificationStatus.int: "incomingVerificationStatus", + ModelRole.OutgoingVerificationStatus.int: "outgoingVerificationStatus", ModelRole.IsAdmin.int: "isAdmin", ModelRole.Joined.int: "joined", }.toTable @@ -126,10 +127,10 @@ QtObject: result = newQVariant(item.isBlocked) of ModelRole.ContactRequest: result = newQVariant(item.contactRequest.int) - of ModelRole.IncomingVerification: - result = newQVariant(item.incomingVerification.int) - of ModelRole.OutcomingVerification: - result = newQVariant(item.outcomingVerification.int) + of ModelRole.IncomingVerificationStatus: + result = newQVariant(item.incomingVerificationStatus.int) + of ModelRole.OutgoingVerificationStatus: + result = newQVariant(item.outgoingVerificationStatus.int) of ModelRole.IsAdmin: result = newQVariant(item.isAdmin) of ModelRole.Joined: @@ -213,7 +214,8 @@ QtObject: icon: string, isContact: bool = false, isAdmin: bool = false, - joined: bool = false + joined: bool = false, + isUntrustworthy: bool = false, ) = let ind = self.findIndexForMessageId(pubKey) if(ind == -1): @@ -227,6 +229,7 @@ QtObject: self.items[ind].isContact = isContact self.items[ind].isAdmin = isAdmin self.items[ind].joined = joined + self.items[ind].isUntrustworthy = isUntrustworthy let index = self.createIndex(ind, 0, nil) self.dataChanged(index, index, @[ @@ -238,6 +241,7 @@ QtObject: ModelRole.IsContact.int, ModelRole.IsAdmin.int, ModelRole.Joined.int, + ModelRole.IsUntrustworthy.int, ]) proc setOnlineStatus*(self: Model, pubKey: string, diff --git a/src/app/modules/shared_models/message_item.nim b/src/app/modules/shared_models/message_item.nim index 852f2163be..a6ada2e800 100644 --- a/src/app/modules/shared_models/message_item.nim +++ b/src/app/modules/shared_models/message_item.nim @@ -1,5 +1,6 @@ import json, strformat import ../../../app_service/common/types +import ../../../app_service/service/contacts/dto/contacts export types.ContentType import message_reaction_model, message_reaction_item, message_transaction_parameters_item @@ -35,6 +36,7 @@ type links: seq[string] transactionParameters: TransactionParametersItem mentionedUsersPks: seq[string] + senderTrustStatus: TrustStatus proc initItem*( id, @@ -59,6 +61,7 @@ proc initItem*( links: seq[string], transactionParameters: TransactionParametersItem, mentionedUsersPks: seq[string], + senderTrustStatus: TrustStatus ): Item = result = Item() result.id = id @@ -89,6 +92,7 @@ proc initItem*( result.mentionedUsersPks = mentionedUsersPks result.gapFrom = 0 result.gapTo = 0 + result.senderTrustStatus = senderTrustStatus proc `$`*(self: Item): string = result = fmt"""Item( @@ -115,6 +119,7 @@ proc `$`*(self: Item): string = links:{$self.links}, transactionParameters:{$self.transactionParameters}, mentionedUsersPks:{$self.mentionedUsersPks}, + senderTrustStatus:{$self.senderTrustStatus}, )""" proc id*(self: Item): string {.inline.} = @@ -156,6 +161,12 @@ proc senderIsAdded*(self: Item): bool {.inline.} = proc `senderIsAdded=`*(self: Item, value: bool) {.inline.} = self.senderIsAdded = value +proc senderTrustStatus*(self: Item): TrustStatus {.inline.} = + self.senderTrustStatus + +proc `senderTrustStatus=`*(self: Item, value: TrustStatus) {.inline.} = + self.senderTrustStatus = value + proc outgoingStatus*(self: Item): string {.inline.} = self.outgoingStatus diff --git a/src/app/modules/shared_models/message_item_qobject.nim b/src/app/modules/shared_models/message_item_qobject.nim index e9eb569348..cda1eb89d0 100644 --- a/src/app/modules/shared_models/message_item_qobject.nim +++ b/src/app/modules/shared_models/message_item_qobject.nim @@ -34,6 +34,13 @@ QtObject: QtProperty[string] pinnedBy: read = pinnedBy + proc senderTrustStatus*(self: MessageItem): int {.slot.} = + let trustStatus = ?.self.messageItem.senderTrustStatus + return trustStatus.int + + QtProperty[int] senderTrustStatus: + read = senderTrustStatus + proc senderDisplayName*(self: MessageItem): string {.slot.} = result = ?.self.messageItem.senderDisplayName QtProperty[string] senderDisplayName: read = senderDisplayName diff --git a/src/app/modules/shared_models/message_model.nim b/src/app/modules/shared_models/message_model.nim index ed92f709d0..3a00f7d6f2 100644 --- a/src/app/modules/shared_models/message_model.nim +++ b/src/app/modules/shared_models/message_model.nim @@ -36,6 +36,7 @@ type Links TransactionParameters MentionedUsersPks + SenderTrustStatus QtObject: type @@ -108,7 +109,8 @@ QtObject: ModelRole.IsEdited.int: "isEdited", ModelRole.Links.int: "links", ModelRole.TransactionParameters.int: "transactionParameters", - ModelRole.MentionedUsersPks.int: "mentionedUsersPks" + ModelRole.MentionedUsersPks.int: "mentionedUsersPks", + ModelRole.SenderTrustStatus.int: "senderTrustStatus" }.toTable method data(self: Model, index: QModelIndex, role: int): QVariant = @@ -136,6 +138,8 @@ QtObject: result = newQVariant(item.senderId) of ModelRole.SenderDisplayName: result = newQVariant(item.senderDisplayName) + of ModelRole.SenderTrustStatus: + result = newQVariant(item.senderTrustStatus.int) of ModelRole.SenderLocalName: result = newQVariant(item.senderLocalName) of ModelRole.SenderIcon: @@ -365,7 +369,8 @@ QtObject: var roles: seq[int] if(self.items[i].senderId == contactId): - roles = @[ModelRole.SenderDisplayName.int, ModelRole.SenderLocalName.int, ModelRole.SenderIcon.int, ModelRole.SenderIsAdded.int] + roles = @[ModelRole.SenderDisplayName.int, ModelRole.SenderLocalName.int, + ModelRole.SenderIcon.int, ModelRole.SenderIsAdded.int, ModelRole.SenderTrustStatus.int] if(self.items[i].pinnedBy == contactId): roles.add(ModelRole.PinnedBy.int) if(self.items[i].messageContainsMentions): diff --git a/src/app/modules/shared_models/section_item.nim b/src/app/modules/shared_models/section_item.nim index e3c7f9678e..82eb6868dc 100644 --- a/src/app/modules/shared_models/section_item.nim +++ b/src/app/modules/shared_models/section_item.nim @@ -238,8 +238,9 @@ proc updateMember*( ensName: string, nickname: string, alias: string, - image: string) = - self.membersModel.updateItem(pubkey, name, ensName, nickname, alias, image) + image: string, + isUntrustworthy: bool) = + self.membersModel.updateItem(pubkey, name, ensName, nickname, alias, image, isUntrustworthy) proc pendingRequestsToJoin*(self: SectionItem): PendingRequestModel {.inline.} = self.pendingRequestsToJoinModel diff --git a/src/app/modules/shared_models/user_item.nim b/src/app/modules/shared_models/user_item.nim index ed4ca64dde..034981b054 100644 --- a/src/app/modules/shared_models/user_item.nim +++ b/src/app/modules/shared_models/user_item.nim @@ -10,13 +10,16 @@ type None = 0 IncomingPending IncomingRejected - OutcomingPending - OutcomingRejected + OutgoingPending + OutgoingRejected - VerificationRequest* {.pure.} = enum + VerificationRequestStatus* {.pure.} = enum None = 0 Pending Answered + Declined + Canceled + Trusted type UserItem* = ref object of RootObj @@ -34,8 +37,8 @@ type isUntrustworthy: bool isBlocked: bool contactRequest: ContactRequest - incomingVerification: VerificationRequest - outcomingVerification: VerificationRequest + incomingVerificationStatus: VerificationRequestStatus + outgoingVerificationStatus: VerificationRequestStatus proc setup*(self: UserItem, pubKey: string, @@ -52,8 +55,9 @@ proc setup*(self: UserItem, isUntrustworthy: bool, isBlocked: bool, contactRequest: ContactRequest, - incomingVerification: VerificationRequest, - outcomingVerification: VerificationRequest) = + incomingVerificationStatus: VerificationRequestStatus, + outgoingVerificationStatus: VerificationRequestStatus, + ) = self.pubKey = pubKey self.displayName = displayName self.ensName = ensName @@ -68,28 +72,28 @@ proc setup*(self: UserItem, self.isUntrustworthy = isUntrustworthy self.isBlocked = isBlocked self.contactRequest = contactRequest - self.incomingVerification = incomingVerification - self.outcomingVerification = outcomingVerification + self.incomingVerificationStatus = incomingVerificationStatus + self.outgoingVerificationStatus = outgoingVerificationStatus # FIXME: remove defaults proc initUserItem*( - pubKey: string, - displayName: string, - ensName: string = "", - localNickname: string = "", - alias: string = "", - icon: string, - colorId: int = 0, - colorHash: string = "", - onlineStatus: OnlineStatus = OnlineStatus.Inactive, - isContact: bool, - isVerified: bool, - isUntrustworthy: bool, - isBlocked: bool, - contactRequest: ContactRequest = ContactRequest.None, - incomingVerification: VerificationRequest = VerificationRequest.None, - outcomingVerification: VerificationRequest = VerificationRequest.None -): UserItem = + pubKey: string, + displayName: string, + ensName: string = "", + localNickname: string = "", + alias: string = "", + icon: string, + colorId: int = 0, + colorHash: string = "", + onlineStatus: OnlineStatus = OnlineStatus.Inactive, + isContact: bool, + isVerified: bool, + isUntrustworthy: bool, + isBlocked: bool, + contactRequest: ContactRequest = ContactRequest.None, + incomingVerificationStatus: VerificationRequestStatus = VerificationRequestStatus.None, + outgoingVerificationStatus: VerificationRequestStatus = VerificationRequestStatus.None, + ): UserItem = result = UserItem() result.setup( pubKey = pubKey, @@ -106,8 +110,8 @@ proc initUserItem*( isUntrustworthy = isUntrustworthy, isBlocked = isBlocked, contactRequest = contactRequest, - incomingVerification = incomingVerification, - outcomingVerification = outcomingVerification) + incomingVerificationStatus = incomingVerificationStatus, + outgoingVerificationStatus = outgoingVerificationStatus) proc toOnlineStatus*(statusType: StatusType): OnlineStatus = if(statusType == StatusType.AlwaysOnline or statusType == StatusType.Automatic): @@ -131,8 +135,8 @@ proc `$`*(self: UserItem): string = isUntrustworthy: {self.isUntrustworthy}, isBlocked: {self.isBlocked}, contactRequest: {$self.contactRequest.int}, - incomingVerification: {$self.incomingVerification.int}, - outcomingVerification: {$self.outcomingVerification.int}, + incomingVerificationStatus: {$self.incomingVerificationStatus.int}, + outgoingVerificationStatus: {$self.outgoingVerificationStatus.int}, ]""" proc pubKey*(self: UserItem): string {.inline.} = @@ -216,14 +220,14 @@ proc contactRequest*(self: UserItem): ContactRequest {.inline.} = proc `contactRequest=`*(self: UserItem, value: ContactRequest) {.inline.} = self.contactRequest = value -proc incomingVerification*(self: UserItem): VerificationRequest {.inline.} = - self.incomingVerification +proc incomingVerificationStatus*(self: UserItem): VerificationRequestStatus {.inline.} = + self.incomingVerificationStatus -proc `incomingVerification=`*(self: UserItem, value: VerificationRequest) {.inline.} = - self.incomingVerification = value +proc `incomingVerificationStatus=`*(self: UserItem, value: VerificationRequestStatus) {.inline.} = + self.incomingVerificationStatus = value -proc outcomingVerification*(self: UserItem): VerificationRequest {.inline.} = - self.outcomingVerification +proc outgoingVerificationStatus*(self: UserItem): VerificationRequestStatus {.inline.} = + self.outgoingVerificationStatus -proc `outcomingVerification=`*(self: UserItem, value: VerificationRequest) {.inline.} = - self.outcomingVerification = value +proc `outgoingVerificationStatus=`*(self: UserItem, value: VerificationRequestStatus) {.inline.} = + self.outgoingVerificationStatus = value diff --git a/src/app/modules/shared_models/user_model.nim b/src/app/modules/shared_models/user_model.nim index 8126a20b8d..59ef993433 100644 --- a/src/app/modules/shared_models/user_model.nim +++ b/src/app/modules/shared_models/user_model.nim @@ -1,5 +1,4 @@ import NimQml, Tables, strformat, sequtils, sugar - import user_item type @@ -18,8 +17,8 @@ type IsUntrustworthy IsBlocked ContactRequest - IncomingVerification - OutcomingVerification + IncomingVerificationStatus + OutgoingVerificationStatus QtObject: type @@ -75,8 +74,8 @@ QtObject: ModelRole.IsUntrustworthy.int: "isUntrustworthy", ModelRole.IsBlocked.int: "isBlocked", ModelRole.ContactRequest.int: "contactRequest", - ModelRole.IncomingVerification.int: "incomingVerification", - ModelRole.OutcomingVerification.int: "outcomingVerification", + ModelRole.IncomingVerificationStatus.int: "incomingVerificationStatus", + ModelRole.OutgoingVerificationStatus.int: "outgoingVerificationStatus", }.toTable method data(self: Model, index: QModelIndex, role: int): QVariant = @@ -118,10 +117,10 @@ QtObject: result = newQVariant(item.isBlocked) of ModelRole.ContactRequest: result = newQVariant(item.contactRequest.int) - of ModelRole.IncomingVerification: - result = newQVariant(item.incomingVerification.int) - of ModelRole.OutcomingVerification: - result = newQVariant(item.outcomingVerification.int) + of ModelRole.IncomingVerificationStatus: + result = newQVariant(item.incomingVerificationStatus.int) + of ModelRole.OutgoingVerificationStatus: + result = newQVariant(item.outgoingVerificationStatus.int) proc addItems*(self: Model, items: seq[UserItem]) = if(items.len == 0): @@ -163,8 +162,7 @@ QtObject: self.items = @[] self.endResetModel() -# TODO: rename to `findIndexForMessagePubkey` - proc findIndexForMessageId(self: Model, pubKey: string): int = + proc findIndexByPubKey(self: Model, pubKey: string): int = for i in 0 ..< self.items.len: if(self.items[i].pubKey == pubKey): return i @@ -182,11 +180,11 @@ QtObject: # TODO: rename to `containsItem` proc isContactWithIdAdded*(self: Model, id: string): bool = - return self.findIndexForMessageId(id) != -1 + return self.findIndexByPubKey(id) != -1 proc setName*(self: Model, pubKey: string, displayName: string, ensName: string, localNickname: string) = - let ind = self.findIndexForMessageId(pubKey) + let ind = self.findIndexByPubKey(pubKey) if(ind == -1): return @@ -202,7 +200,7 @@ QtObject: ]) proc setIcon*(self: Model, pubKey: string, icon: string) = - let ind = self.findIndexForMessageId(pubKey) + let ind = self.findIndexByPubKey(pubKey) if(ind == -1): return @@ -218,9 +216,10 @@ QtObject: ensName: string, localNickname: string, alias: string, - icon: string + icon: string, + isUntrustworthy: bool = false, ) = - let ind = self.findIndexForMessageId(pubKey) + let ind = self.findIndexByPubKey(pubKey) if(ind == -1): return @@ -228,7 +227,7 @@ QtObject: self.items[ind].ensName = ensName self.items[ind].localNickname = localNickname self.items[ind].alias = alias - self.items[ind].icon = icon + self.items[ind].isUntrustworthy = isUntrustworthy let index = self.createIndex(ind, 0, nil) self.dataChanged(index, index, @[ @@ -237,6 +236,7 @@ QtObject: ModelRole.LocalNickname.int, ModelRole.Alias.int, ModelRole.Icon.int, + ModelRole.IsUntrustworthy.int, ]) proc updateName*( @@ -244,7 +244,7 @@ QtObject: pubKey: string, displayName: string ) = - let ind = self.findIndexForMessageId(pubKey) + let ind = self.findIndexByPubKey(pubKey) if(ind == -1): return @@ -254,10 +254,36 @@ QtObject: self.dataChanged(index, index, @[ ModelRole.DisplayName.int ]) + + proc updateIncomingRequestStatus*( + self: Model, + pubKey: string, + requestStatus: VerificationRequestStatus + ) = + let ind = self.findIndexByPubKey(pubKey) + if(ind == -1): + return + + self.items[ind].incomingVerificationStatus = requestStatus + + let index = self.createIndex(ind, 0, nil) + self.dataChanged(index, index, @[ + ModelRole.IncomingVerificationStatus.int + ]) + + proc updateTrustStatus*(self: Model, pubKey: string, isUntrustworthy: bool) = + let ind = self.findIndexByPubKey(pubKey) + if(ind == -1): + return + + let first = self.createIndex(ind, 0, nil) + let last = self.createIndex(ind, 0, nil) + self.items[ind].isUntrustworthy = isUntrustworthy + self.dataChanged(first, last, @[ModelRole.IsUntrustworthy.int]) proc setOnlineStatus*(self: Model, pubKey: string, onlineStatus: OnlineStatus) = - let ind = self.findIndexForMessageId(pubKey) + let ind = self.findIndexByPubKey(pubKey) if(ind == -1): return @@ -271,7 +297,7 @@ QtObject: # TODO: rename me to removeItemByPubkey proc removeItemById*(self: Model, pubKey: string) = - let ind = self.findIndexForMessageId(pubKey) + let ind = self.findIndexByPubKey(pubKey) if(ind == -1): return @@ -280,3 +306,6 @@ QtObject: # TODO: rename me to getItemsAsPubkeys proc getItemIds*(self: Model): seq[string] = return self.items.map(i => i.pubKey) + + proc containsItemWithPubKey*(self: Model, pubKey: string): bool = + return self.findIndexByPubKey(pubKey) != -1 \ No newline at end of file diff --git a/src/app_service/service/contacts/dto/contacts.nim b/src/app_service/service/contacts/dto/contacts.nim index 5be676f86c..ea5cb4017c 100644 --- a/src/app_service/service/contacts/dto/contacts.nim +++ b/src/app_service/service/contacts/dto/contacts.nim @@ -10,6 +10,28 @@ type thumbnail*: string large*: string +type TrustStatus* {.pure.}= enum + Unknown = 0, + Trusted = 1, + Untrustworthy = 2 + +type VerificationStatus* {.pure.}= enum + Unverified = 0 + Verifying = 1 + Verified = 2 + Declined = 3 + Canceled = 4 + Trusted = 5 + +type VerificationRequest* = object + fromID*: string + toID*: string + challenge*: string + requestedAt*: int64 + response*: string + repliedAt*: int64 + status*: VerificationStatus + type ContactsDto* = object id*: string name*: string @@ -25,6 +47,8 @@ type ContactsDto* = object hasAddedUs*: bool isSyncing*: bool removed*: bool + trustStatus*: TrustStatus + verificationStatus*: VerificationStatus proc `$`(self: Images): string = result = fmt"""Images( @@ -45,11 +69,13 @@ proc `$`*(self: ContactsDto): string = image:[ {$self.image} ], - added:{self.added} - blocked:{self.blocked} - hasAddedUs:{self.hasAddedUs} - isSyncing:{self.isSyncing} - removed:{self.removed} + added:{self.added}, + blocked:{self.blocked}, + hasAddedUs:{self.hasAddedUs}, + isSyncing:{self.isSyncing}, + removed:{self.removed}, + trustStatus:{self.trustStatus}, + verificationStatus:{self.verificationStatus}, )""" proc toImages(jsonObj: JsonNode): Images = @@ -63,6 +89,28 @@ proc toImages(jsonObj: JsonNode): Images = if(jsonObj.getProp("thumbnail", thumbnailObj)): discard thumbnailObj.getProp("uri", result.thumbnail) +proc toTrustStatus*(value: int): TrustStatus = + result = TrustStatus.Unknown + if value >= ord(low(TrustStatus)) or value <= ord(high(TrustStatus)): + result = TrustStatus(value) + +proc toVerificationStatus*(value: int): VerificationStatus = + result = VerificationStatus.Unverified + if value >= ord(low(VerificationStatus)) or value <= ord(high(VerificationStatus)): + result = VerificationStatus(value) + +proc toVerificationRequest*(jsonObj: JsonNode): VerificationRequest = + result = VerificationRequest() + discard jsonObj.getProp("from", result.fromID) + discard jsonObj.getProp("to", result.toID) + discard jsonObj.getProp("challenge", result.challenge) + discard jsonObj.getProp("response", result.response) + discard jsonObj.getProp("requested_at", result.requestedAt) + discard jsonObj.getProp("replied_at", result.repliedAt) + var verificationStatusInt: int + discard jsonObj.getProp("verification_status", verificationStatusInt) + result.status = verificationStatusInt.toVerificationStatus() + proc toContactsDto*(jsonObj: JsonNode): ContactsDto = result = ContactsDto() discard jsonObj.getProp("id", result.id) @@ -73,6 +121,15 @@ proc toContactsDto*(jsonObj: JsonNode): ContactsDto = discard jsonObj.getProp("lastUpdated", result.lastUpdated) discard jsonObj.getProp("lastUpdatedLocally", result.lastUpdatedLocally) discard jsonObj.getProp("localNickname", result.localNickname) + + result.trustStatus = TrustStatus.Unknown + var trustStatusInt: int + discard jsonObj.getProp("trustStatus", trustStatusInt) + result.trustStatus = trustStatusInt.toTrustStatus() + + var verificationStatusInt: int + discard jsonObj.getProp("verificationStatus", verificationStatusInt) + result.verificationStatus = verificationStatusInt.toVerificationStatus() var imageObj: JsonNode if(jsonObj.getProp("images", imageObj)): @@ -122,13 +179,14 @@ proc isMutualContact*(self: ContactsDto): bool = # But for now we consider that contact is mutual contact if I added him and he added me. return self.hasAddedUs and self.added and not self.removed and not self.blocked +proc trustStatus*(self: ContactsDto): TrustStatus = + result = self.trustStatus + proc isContactVerified*(self: ContactsDto): bool = - # TODO not implemented in `status-go` yet - return false + return self.verificationStatus == VerificationStatus.Verified proc isContactUntrustworthy*(self: ContactsDto): bool = - # TODO not implemented in `status-go` yet - return false + return self.trustStatus == TrustStatus.Untrustworthy proc isContactMarked*(self: ContactsDto): bool = return self.isContactVerified() or self.isContactUntrustworthy() \ No newline at end of file diff --git a/src/app_service/service/contacts/service.nim b/src/app_service/service/contacts/service.nim index 1b20efe6d6..d4896801ea 100644 --- a/src/app_service/service/contacts/service.nim +++ b/src/app_service/service/contacts/service.nim @@ -1,4 +1,4 @@ -import NimQml, Tables, json, sequtils, strformat, chronicles, strutils, times, sugar +import NimQml, Tables, json, sequtils, strformat, chronicles, strutils, times, sugar, std/times import ../../../app/global/global_singleton import ../../../app/core/signals/types @@ -27,6 +27,10 @@ type ContactArgs* = ref object of Args contactId*: string + TrustArgs* = ref object of Args + publicKey*: string + isUntrustworthy*: bool + ResolvedContactArgs* = ref object of Args pubkey*: string address*: string @@ -36,6 +40,9 @@ type ContactsStatusUpdatedArgs* = ref object of Args statusUpdates*: seq[StatusUpdateDto] + VerificationRequestArgs* = ref object of Args + verificationRequest*: VerificationRequest + # Local Constants: const CheckStatusIntervalInMilliseconds = 5000 # 5 seconds, this is timeout how often do we check for user status. const FiveMinsOnlineLimitInSeconds = int(5 * 60) # 5 minutes @@ -52,6 +59,16 @@ const SIGNAL_CONTACT_NICKNAME_CHANGED* = "contactNicknameChanged" const SIGNAL_CONTACTS_STATUS_UPDATED* = "contactsStatusUpdated" const SIGNAL_CONTACT_UPDATED* = "contactUpdated" const SIGNAL_LOGGEDIN_USER_IMAGE_CHANGED* = "loggedInUserImageChanged" +const SIGNAL_REMOVED_TRUST_STATUS* = "removedTrustStatus" +const SIGNAL_CONTACT_UNTRUSTWORTHY* = "contactUntrustworthy" +const SIGNAL_CONTACT_TRUSTED* = "contactTrusted" +const SIGNAL_CONTACT_VERIFIED* = "contactVerified" +const SIGNAL_CONTACT_VERIFICATION_SENT* = "contactVerificationRequestSent" +const SIGNAL_CONTACT_VERIFICATION_CANCELLED* = "contactVerificationRequestCancelled" +const SIGNAL_CONTACT_VERIFICATION_DECLINED* = "contactVerificationRequestDeclined" +const SIGNAL_CONTACT_VERIFICATION_ACCEPTED* = "contactVerificationRequestAccepted" +const SIGNAL_CONTACT_VERIFICATION_ADDED* = "contactVerificationRequestAdded" +const SIGNAL_CONTACT_VERIFICATION_UPDATED* = "contactVerificationRequestUpdated" type ContactsGroup* {.pure.} = enum @@ -69,6 +86,7 @@ QtObject: networkService: network_service.Service contacts: Table[string, ContactsDto] # [contact_id, ContactsDto] contactsStatus: Table[string, StatusUpdateDto] # [contact_id, StatusUpdateDto] + receivedIdentityRequests: Table[string, VerificationRequest] # [from_id, VerificationRequest] events: EventEmitter closingApp: bool imageServerUrl: string @@ -77,11 +95,13 @@ QtObject: proc getContactById*(self: Service, id: string): ContactsDto proc saveContact(self: Service, contact: ContactsDto) proc startCheckingContactStatuses(self: Service) + proc fetchReceivedVerificationRequests*(self: Service) : seq[VerificationRequest] proc delete*(self: Service) = self.closingApp = true self.contacts.clear self.contactsStatus.clear + self.receivedIdentityRequests.clear self.QObject.delete proc newService*( @@ -96,6 +116,8 @@ QtObject: result.networkService = networkService result.threadpool = threadpool result.contacts = initTable[string, ContactsDto]() + result.contactsStatus = initTable[string, StatusUpdateDto]() + result.receivedIdentityRequests = initTable[string, VerificationRequest]() proc addContact(self: Service, contact: ContactsDto) = # Private proc, used for adding contacts only. @@ -111,9 +133,13 @@ QtObject: for contact in contacts: self.addContact(contact) + # Identity verifications + for request in self.fetchReceivedVerificationRequests(): + self.receivedIdentityRequests[request.fromId] = request + except Exception as e: let errDesription = e.msg - error "error: ", errDesription + error "error fetching contacts: ", errDesription return proc doConnect(self: Service) = @@ -140,6 +166,31 @@ QtObject: let data = ContactArgs(contactId: c.id) self.events.emit(SIGNAL_CONTACT_UPDATED, data) + let myPubKey = singletonInstance.userProfile.getPubKey() + if(receivedData.verificationRequests.len > 0): + for request in receivedData.verificationRequests: + if request.fromId == myPubKey: + # TODO handle reacting to my own request later + continue + + let data = VerificationRequestArgs(verificationRequest: request) + let alreadyContains = self.receivedIdentityRequests.contains(request.fromId) + self.receivedIdentityRequests[request.fromId] = request + + if alreadyContains: + self.events.emit(SIGNAL_CONTACT_VERIFICATION_UPDATED, data) + + if request.status == VerificationStatus.Trusted: + if self.contacts.hasKey(request.fromId): + self.contacts[request.fromId].trustStatus = TrustStatus.Trusted + self.contacts[request.fromId].verificationStatus = VerificationStatus.Trusted + + self.events.emit(SIGNAL_CONTACT_TRUSTED, + TrustArgs(publicKey: request.fromId, isUntrustworthy: false)) + self.events.emit(SIGNAL_CONTACT_VERIFIED, ContactArgs(contactId: request.fromId)) + else: + self.events.emit(SIGNAL_CONTACT_VERIFICATION_ADDED, data) + proc setImageServerUrl(self: Service) = try: let response = status_contacts.getImageServerURL() @@ -222,6 +273,15 @@ QtObject: return return status_accounts.generateAlias(publicKey).result.getStr + proc getTrustStatus*(self: Service, publicKey: string): TrustStatus = + try: + let t = status_contacts.getTrustStatus(publicKey).result.getInt + return t.toTrustStatus() + except Exception as e: + let errDesription = e.msg + error "error: ", errDesription + return TrustStatus.Unknown + proc getContactById*(self: Service, id: string): ContactsDto = if(id == singletonInstance.userProfile.getPubKey()): # If we try to get the contact details of ourselves, just return our own info @@ -235,7 +295,8 @@ QtObject: image: Images( thumbnail: singletonInstance.userProfile.getThumbnailImage(), large: singletonInstance.userProfile.getLargeImage() - ) + ), + trustStatus: TrustStatus.Trusted ) ## Returns contact details based on passed id (public key) @@ -256,13 +317,15 @@ QtObject: return let alias = self.generateAlias(id) + let trustStatus = self.getTrustStatus(id) result = ContactsDto( id: id, alias: alias, ensVerified: false, added: false, blocked: false, - hasAddedUs: false + hasAddedUs: false, + trustStatus: trustStatus ) self.addContact(result) @@ -390,6 +453,7 @@ QtObject: proc removeContact*(self: Service, publicKey: string) = var contact = self.getContactById(publicKey) contact.removed = true + contact.added = false let response = status_contacts.removeContact(contact.id) if(not response.error.isNil): @@ -465,3 +529,164 @@ QtObject: result.icon = icon result.isCurrentUser = pubKey == singletonInstance.userProfile.getPubKey() result.details = self.getContactById(pubKey) + + proc markUntrustworthy*(self: Service, publicKey: string) = + let response = status_contacts.markUntrustworthy(publicKey) + if(not response.error.isNil): + let msg = response.error.message + error "error marking as untrustworthy ", msg + return + + if self.contacts.hasKey(publicKey): + self.contacts[publicKey].trustStatus = TrustStatus.Untrustworthy + + self.events.emit(SIGNAL_CONTACT_UNTRUSTWORTHY, + TrustArgs(publicKey: publicKey, isUntrustworthy: true)) + + proc verifiedTrusted*(self: Service, publicKey: string) = + let response = status_contacts.verifiedTrusted(publicKey) + if(not response.error.isNil): + let msg = response.error.message + error "error confirming identity ", msg + return + + if self.contacts.hasKey(publicKey): + self.contacts[publicKey].trustStatus = TrustStatus.Trusted + self.contacts[publicKey].verificationStatus = VerificationStatus.Trusted + + self.events.emit(SIGNAL_CONTACT_TRUSTED, + TrustArgs(publicKey: publicKey, isUntrustworthy: false)) + self.events.emit(SIGNAL_CONTACT_VERIFIED, ContactArgs(contactId: publicKey)) + + proc verifiedUntrustworthy*(self: Service, publicKey: string) = + let response = status_contacts.verifiedUntrustworthy(publicKey) + if(not response.error.isNil): + let msg = response.error.message + error "error confirming identity ", msg + return + + if self.contacts.hasKey(publicKey): + self.contacts[publicKey].trustStatus = TrustStatus.Untrustworthy + self.contacts[publicKey].verificationStatus = VerificationStatus.Verified + + self.events.emit(SIGNAL_CONTACT_UNTRUSTWORTHY, + TrustArgs(publicKey: publicKey, isUntrustworthy: true)) + self.events.emit(SIGNAL_CONTACT_VERIFIED, ContactArgs(contactId: publicKey)) + + proc removeTrustStatus*(self: Service, publicKey: string) = + let response = status_contacts.removeTrustStatus(publicKey) + if(not response.error.isNil): + let msg = response.error.message + error "error removing trust status", msg + return + + if self.contacts.hasKey(publicKey): + self.contacts[publicKey].trustStatus = TrustStatus.Unknown + if self.contacts[publicKey].verificationStatus == VerificationStatus.Verified: + self.contacts[publicKey].verificationStatus = VerificationStatus.Unverified + + self.events.emit(SIGNAL_REMOVED_TRUST_STATUS, + TrustArgs(publicKey: publicKey, isUntrustworthy: false)) + + proc getVerificationRequestSentTo*(self: Service, publicKey: string): VerificationRequest = + try: + let response = status_contacts.getVerificationRequestSentTo(publicKey) + return response.result.toVerificationRequest() + except Exception as e: + let errDesription = e.msg + error "error obtaining verification request", errDesription + return + + proc getVerificationRequestFrom*(self: Service, publicKey: string): VerificationRequest = + try: + if (self.receivedIdentityRequests.contains(publicKey)): + return self.receivedIdentityRequests[publicKey] + + let response = status_contacts.getVerificationRequestFrom(publicKey) + result = response.result.toVerificationRequest() + self.receivedIdentityRequests[publicKey] = result + except Exception as e: + let errDesription = e.msg + error "error obtaining verification request", errDesription + + proc fetchReceivedVerificationRequests*(self: Service): seq[VerificationRequest] = + try: + let response = status_contacts.getReceivedVerificationRequests() + + for request in response.result: + result.add(request.toVerificationRequest()) + except Exception as e: + let errDesription = e.msg + error "error obtaining verification requests", errDesription + + proc getReceivedVerificationRequests*(self: Service): seq[VerificationRequest] = + result = toSeq(self.receivedIdentityRequests.values) + + proc hasReceivedVerificationRequestFrom*(self: Service, fromId: string): bool = + result = self.receivedIdentityRequests.contains(fromId) + + proc sendVerificationRequest*(self: Service, publicKey: string, challenge: string) = + try: + let response = status_contacts.sendVerificationRequest(publicKey, challenge) + if(not response.error.isNil): + let msg = response.error.message + error "error sending contact verification request", msg + return + + var contact = self.getContactById(publicKey) + contact.verificationStatus = VerificationStatus.Verifying + self.saveContact(contact) + + self.events.emit(SIGNAL_CONTACT_VERIFICATION_SENT, ContactArgs(contactId: publicKey)) + except Exception as e: + error "Error sending verification request", msg = e.msg + + proc cancelVerificationRequest*(self: Service, publicKey: string) = + try: + let response = status_contacts.cancelVerificationRequest(publicKey) + if(not response.error.isNil): + let msg = response.error.message + error "error sending contact verification request", msg + return + + var contact = self.getContactById(publicKey) + contact.verificationStatus = VerificationStatus.Unverified + self.saveContact(contact) + + self.events.emit(SIGNAL_CONTACT_VERIFICATION_CANCELLED, ContactArgs(contactId: publicKey)) + except Exception as e: + error "Error canceling verification request", msg = e.msg + + proc acceptVerificationRequest*(self: Service, publicKey: string, responseText: string) = + try: + let response = status_contacts.acceptVerificationRequest(publicKey, responseText) + if(not response.error.isNil): + let msg = response.error.message + raise newException(RpcException, msg) + + var request = self.receivedIdentityRequests[publicKey] + request.status = VerificationStatus.Verified + request.response = responseText + request.repliedAt = getTime().toUnix * 1000 + self.receivedIdentityRequests[publicKey] = request + + self.events.emit(SIGNAL_CONTACT_VERIFICATION_ACCEPTED, + VerificationRequestArgs(verificationRequest: request)) + except Exception as e: + error "error accepting contact verification request", msg=e.msg + + proc declineVerificationRequest*(self: Service, publicKey: string) = + try: + let response = status_contacts.declineVerificationRequest(publicKey) + if(not response.error.isNil): + let msg = response.error.message + raise newException(RpcException, msg) + + var request = self.receivedIdentityRequests[publicKey] + request.status = VerificationStatus.Declined + self.receivedIdentityRequests[publicKey] = request + + self.events.emit(SIGNAL_CONTACT_VERIFICATION_DECLINED, ContactArgs(contactId: publicKey)) + except Exception as e: + error "error declining contact verification request", msg=e.msg + \ No newline at end of file diff --git a/src/backend/contacts.nim b/src/backend/contacts.nim index 8c4db60fab..1dc22ff0cb 100644 --- a/src/backend/contacts.nim +++ b/src/backend/contacts.nim @@ -54,3 +54,51 @@ proc sendContactUpdate*(publicKey, ensName, thumbnail: string): RpcResponse[Json proc getImageServerURL*(): RpcResponse[JsonNode] {.raises: [Exception].} = let payload = %* [] result = callPrivateRPC("imageServerURL".prefix, payload) + +proc markUntrustworthy*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("markAsUntrustworthy".prefix, payload) + +proc verifiedTrusted*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("verifiedTrusted".prefix, payload) + +proc verifiedUntrustworthy*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("verifiedUntrustworthy".prefix, payload) + +proc removeTrustStatus*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("removeTrustStatus".prefix, payload) + +proc getTrustStatus*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("getTrustStatus".prefix, payload) + +proc sendVerificationRequest*(pubkey: string, challenge: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey, challenge] + result = callPrivateRPC("sendContactVerificationRequest".prefix, payload) + +proc acceptVerificationRequest*(pubkey: string, response: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey, response] + result = callPrivateRPC("acceptContactVerificationRequest".prefix, payload) + +proc declineVerificationRequest*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("declineContactVerificationRequest".prefix, payload) + +proc getVerificationRequestSentTo*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("getVerificationRequestSentTo".prefix, payload) + +proc getVerificationRequestFrom*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("getVerificationRequestFrom".prefix, payload) + +proc getReceivedVerificationRequests*(): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [] + result = callPrivateRPC("getReceivedVerificationRequests".prefix, payload) + +proc cancelVerificationRequest*(pubkey: string): RpcResponse[JsonNode] {.raises: [Exception].} = + let payload = %* [pubkey] + result = callPrivateRPC("cancelVerificationRequest".prefix, payload) diff --git a/ui/app/AppLayouts/Chat/popups/PinnedMessagesPopup.qml b/ui/app/AppLayouts/Chat/popups/PinnedMessagesPopup.qml index 6b6feec1e8..33bc243974 100644 --- a/ui/app/AppLayouts/Chat/popups/PinnedMessagesPopup.qml +++ b/ui/app/AppLayouts/Chat/popups/PinnedMessagesPopup.qml @@ -129,6 +129,7 @@ ModalPopup { pinnedMessage: model.pinned messagePinnedBy: model.pinnedBy reactionsModel: model.reactions + senderTrustStatus: model.senderTrustStatus linkUrls: model.links isInPinnedPopup: true transactionParams: model.transactionParameters diff --git a/ui/app/AppLayouts/Chat/views/ActivityCenterMessageComponentView.qml b/ui/app/AppLayouts/Chat/views/ActivityCenterMessageComponentView.qml index 88b248f83a..3d53c77b14 100644 --- a/ui/app/AppLayouts/Chat/views/ActivityCenterMessageComponentView.qml +++ b/ui/app/AppLayouts/Chat/views/ActivityCenterMessageComponentView.qml @@ -135,6 +135,7 @@ Item { messageTimestamp: model.timestamp messageOutgoingStatus: model.message.outgoingStatus messageContentType: model.message.contentType + senderTrustStatus: model.message.senderTrustStatus activityCenterMessage: true read: model.read onImageClicked: Global.openImagePopup(image, root.messageContextMenu) diff --git a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml index 9247b72376..72a47c4bb8 100644 --- a/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml +++ b/ui/app/AppLayouts/Chat/views/ChatMessagesView.qml @@ -303,6 +303,7 @@ Item { messageTimestamp: model.timestamp messageOutgoingStatus: model.outgoingStatus messageContentType: model.contentType + senderTrustStatus: model.senderTrustStatus pinnedMessage: model.pinned messagePinnedBy: model.pinnedBy reactionsModel: model.reactions diff --git a/ui/app/AppLayouts/Profile/ProfileLayout.qml b/ui/app/AppLayouts/Profile/ProfileLayout.qml index 9b41f7fcb4..735409f570 100644 --- a/ui/app/AppLayouts/Profile/ProfileLayout.qml +++ b/ui/app/AppLayouts/Profile/ProfileLayout.qml @@ -135,6 +135,7 @@ StatusAppTwoPanelLayout { messagingStore: profileView.store.messagingStore sectionTitle: profileView.store.getNameForSubsection(Constants.settingsSubsection.messaging) + contactsStore: profileView.store.contactsStore contentWidth: d.contentWidth } diff --git a/ui/app/AppLayouts/Profile/panels/ContactPanel.qml b/ui/app/AppLayouts/Profile/panels/ContactPanel.qml index 8ef9bd7763..494bff5968 100644 --- a/ui/app/AppLayouts/Profile/panels/ContactPanel.qml +++ b/ui/app/AppLayouts/Profile/panels/ContactPanel.qml @@ -31,6 +31,7 @@ StatusListItem { property bool isBlocked: false property bool isVerified: false property bool isUntrustworthy: false + property int verificationRequestStatus: 0 property string searchStr: "" @@ -44,12 +45,24 @@ StatusListItem { signal openProfilePopup(string publicKey) signal openChangeNicknamePopup(string publicKey) signal sendMessageActionTriggered(string publicKey) + signal showVerificationRequest(string publicKey) signal contactRequestAccepted(string publicKey) signal contactRequestRejected(string publicKey) signal rejectionRemoved(string publicKey) signal textClicked(string publicKey) components: [ + StatusFlatButton { + visible: verificationRequestStatus === Constants.verificationStatus.verifying || + verificationRequestStatus === Constants.verificationStatus.verified + width: visible ? implicitWidth : 0 + height: visible ? implicitHeight : 0 + text: verificationRequestStatus === Constants.verificationStatus.verifying ? + qsTr("Respond to ID Request") : + qsTr("See ID Request") + size: StatusBaseButton.Size.Small + onClicked: container.showVerificationRequest(container.publicKey) + }, StatusFlatRoundButton { visible: showSendMessageButton width: visible ? 32 : 0 diff --git a/ui/app/AppLayouts/Profile/panels/ContactsListPanel.qml b/ui/app/AppLayouts/Profile/panels/ContactsListPanel.qml index 736c52c0e9..faec97c03f 100644 --- a/ui/app/AppLayouts/Profile/panels/ContactsListPanel.qml +++ b/ui/app/AppLayouts/Profile/panels/ContactsListPanel.qml @@ -27,6 +27,7 @@ Item { signal contactClicked(string publicKey) signal openProfilePopup(string publicKey) signal sendMessageActionTriggered(string publicKey) + signal showVerificationRequest(string publicKey) signal openChangeNicknamePopup(string publicKey) signal contactRequestAccepted(string publicKey) signal contactRequestRejected(string publicKey) @@ -115,19 +116,20 @@ Item { isBlocked: model.isBlocked isVerified: model.isVerified isUntrustworthy: model.isUntrustworthy + verificationRequestStatus: model.incomingVerificationStatus searchStr: contactListRoot.searchString showSendMessageButton: model.isContact showRejectContactRequestButton: { - if (contactListRoot.panelUsage === Constants.contactsPanelUsage.receivedContactRequest) { + if (contactListRoot.panelUsage === Constants.contactsPanelUsage.receivedContactRequest && !model.verificationRequestStatus) { return true } return false } showAcceptContactRequestButton: { - if (contactListRoot.panelUsage === Constants.contactsPanelUsage.receivedContactRequest) { + if (contactListRoot.panelUsage === Constants.contactsPanelUsage.receivedContactRequest && !model.verificationRequestStatus) { return true } @@ -162,6 +164,7 @@ Item { onContactRequestRejected: contactListRoot.contactRequestRejected(publicKey) onRejectionRemoved: contactListRoot.rejectionRemoved(publicKey) onTextClicked: contactListRoot.textClicked(publicKey) + onShowVerificationRequest: contactListRoot.showVerificationRequest(publicKey) visible: searchString === "" || panelDelegate.name.toLowerCase().includes(lowerCaseSearchString) || diff --git a/ui/app/AppLayouts/Profile/panels/MenuPanel.qml b/ui/app/AppLayouts/Profile/panels/MenuPanel.qml index e1ea7e0ac2..b82f385c58 100644 --- a/ui/app/AppLayouts/Profile/panels/MenuPanel.qml +++ b/ui/app/AppLayouts/Profile/panels/MenuPanel.qml @@ -11,7 +11,7 @@ Column { spacing: 4 property var privacyStore - property var messagingStore + property var contactsStore property alias mainMenuItems: mainMenuItems.model property alias settingsMenuItems: settingsMenuItems.model property alias extraMenuItems: extraMenuItems.model @@ -71,7 +71,7 @@ Column { badge.value: { switch (model.subsection) { case Constants.settingsSubsection.messaging: - return root.messagingStore.contactRequestsModel.count + return root.contactsStore.receivedContactRequestsModel.count default: return "" } } diff --git a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml index f1008f1521..78f452eddd 100644 --- a/ui/app/AppLayouts/Profile/stores/ContactsStore.qml +++ b/ui/app/AppLayouts/Profile/stores/ContactsStore.qml @@ -80,4 +80,50 @@ QtObject { function removeContactRequestRejection(pubKey) { root.contactsModule.removeContactRequestRejection(pubKey) } + + function markUntrustworthy(pubKey) { + root.contactsModule.markUntrustworthy(pubKey) + } + + function removeTrustStatus(pubKey) { + root.contactsModule.removeTrustStatus(pubKey) + } + + function sendVerificationRequest(pubKey, challenge) { + root.contactsModule.sendVerificationRequest(pubKey, challenge); + } + + function cancelVerificationRequest(pubKey) { + root.contactsModule.cancelVerificationRequest(pubKey); + } + + function declineVerificationRequest(pubKey) { + root.contactsModule.declineVerificationRequest(pubKey); + } + + function acceptVerificationRequest(pubKey, response) { + root.contactsModule.acceptVerificationRequest(pubKey, response); + } + + function getVerificationDetailsFromAsJson(pubKey) { + let resp = root.contactsModule.getVerificationDetailsFromAsJson(pubKey); + return JSON.parse(resp); + } + + function getSentVerificationDetailsAsJson(pubKey) { + let resp = root.contactsModule.getSentVerificationDetailsAsJson(pubKey); + return JSON.parse(resp); + } + + function hasReceivedVerificationRequestFrom(pubKey) { + return root.contactsModule.hasReceivedVerificationRequestFrom(pubKey); + } + + function verifiedTrusted(pubKey) { + root.contactsModule.verifiedTrusted(pubKey); + } + + function verifiedUntrustworthy(pubKey) { + root.contactsModule.verifiedUntrustworthy(pubKey); + } } diff --git a/ui/app/AppLayouts/Profile/stores/MessagingStore.qml b/ui/app/AppLayouts/Profile/stores/MessagingStore.qml index 189bf99868..67b8dabbbd 100644 --- a/ui/app/AppLayouts/Profile/stores/MessagingStore.qml +++ b/ui/app/AppLayouts/Profile/stores/MessagingStore.qml @@ -10,11 +10,6 @@ QtObject { property int profilePicturesVisibility: privacyModule.profilePicturesVisibility property int profilePicturesShowTo: privacyModule.profilePicturesShowTo - // TODO move contact requests back to the contacts module since we need them in the Profile - // also, having them in the chat section creates some waste, since no community has it - property var chatSectionModule: mainModule.getChatSectionModule() - property var contactRequestsModel: chatSectionModule.contactRequestsModel - property var mailservers: syncModule.model property bool useMailservers: syncModule.useMailservers diff --git a/ui/app/AppLayouts/Profile/views/ContactsView.qml b/ui/app/AppLayouts/Profile/views/ContactsView.qml index d356a6b27f..6134e9a9bb 100644 --- a/ui/app/AppLayouts/Profile/views/ContactsView.qml +++ b/ui/app/AppLayouts/Profile/views/ContactsView.qml @@ -17,8 +17,6 @@ import shared.controls 1.0 import "../stores" import "../panels" import "../popups" -// TODO remove this import when the ContactRequestPanel is moved to the the Profile completely -import "../../Chat/panels" SettingsContentBase { id: root @@ -181,6 +179,23 @@ SettingsContentBase { onContactRequestRejected: { root.contactsStore.rejectContactRequest(publicKey) } + + onShowVerificationRequest: { + try { + let request = root.contactsStore.getVerificationDetailsFromAsJson(publicKey) + Global.openPopup(contactVerificationRequestPopupComponent, { + senderPublicKey: request.from, + senderDisplayName: request.displayName, + senderIcon: request.icon, + challengeText: request.challenge, + responseText: request.response, + messageTimestamp: request.requestedAt, + responseTimestamp: request.repliedAt + }) + } catch (e) { + console.error("Error getting or parsing verification data", e) + } + } } ContactsListPanel { @@ -304,6 +319,18 @@ SettingsContentBase { root.contactsStore.removeContact(removeContactConfirmationDialog.value); } removeContactConfirmationDialog.close() + } + } + + Component { + id: contactVerificationRequestPopupComponent + ContactVerificationRequestPopup { + onResponseSent: { + root.contactsStore.acceptVerificationRequest(senderPublicKey, response) + } + onVerificationRefused: { + root.contactsStore.declineVerificationRequest(senderPublicKey) + } } } diff --git a/ui/app/AppLayouts/Profile/views/LeftTabView.qml b/ui/app/AppLayouts/Profile/views/LeftTabView.qml index 45eb81cc03..379dec4000 100644 --- a/ui/app/AppLayouts/Profile/views/LeftTabView.qml +++ b/ui/app/AppLayouts/Profile/views/LeftTabView.qml @@ -38,7 +38,7 @@ Item { MenuPanel { id: profileMenu privacyStore: store.privacyStore - messagingStore: store.messagingStore + contactsStore: store.contactsStore mainMenuItems: store.mainMenuItems settingsMenuItems: store.settingsMenuItems extraMenuItems: store.extraMenuItems diff --git a/ui/app/AppLayouts/Profile/views/MessagingView.qml b/ui/app/AppLayouts/Profile/views/MessagingView.qml index 65c34113fe..07bfd5afbd 100644 --- a/ui/app/AppLayouts/Profile/views/MessagingView.qml +++ b/ui/app/AppLayouts/Profile/views/MessagingView.qml @@ -24,6 +24,7 @@ SettingsContentBase { id: root property MessagingStore messagingStore + property ContactsStore contactsStore ColumnLayout { id: generalColumn @@ -214,7 +215,7 @@ SettingsContentBase { StatusContactRequestsIndicatorListItem { Layout.fillWidth: true title: qsTr("Contacts, Requests, and Blocked Users") - requestsCount: root.messagingStore.contactRequestsModel.count + requestsCount: root.contactsStore.receivedContactRequestsModel.count sensor.onClicked: Global.changeAppSectionBySectionType(Constants.appSection.profile, Constants.settingsSubsection.contacts) } diff --git a/ui/app/mainui/AppMain.qml b/ui/app/mainui/AppMain.qml index 89f202adbd..b57be18920 100644 --- a/ui/app/mainui/AppMain.qml +++ b/ui/app/mainui/AppMain.qml @@ -107,6 +107,10 @@ Item { onDisplayToastMessage: { appMain.rootStore.mainModuleInst.displayEphemeralNotification(title, subTitle, icon, loading, ephNotifType, url); } + onOpenEditDisplayNamePopup: { + var popup = displayNamePopupComponent.createObject(appMain) + popup.open() + } } function changeAppSectionBySectionId(sectionId) { @@ -151,6 +155,13 @@ Item { privacyStore: appMain.rootStore.profileSectionStore.privacyStore } + property Component displayNamePopupComponent: DisplayNamePopup { + profileStore: appMain.rootStore.profileSectionStore.profileStore + onClosed: { + destroy() + } + } + Component { id: downloadPageComponent DownloadPage { diff --git a/ui/imports/assets/icons/contact.svg b/ui/imports/assets/icons/contact.svg new file mode 100644 index 0000000000..10a8c22721 --- /dev/null +++ b/ui/imports/assets/icons/contact.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/ui/imports/assets/icons/untrustworthy.svg b/ui/imports/assets/icons/untrustworthy.svg new file mode 100644 index 0000000000..2c50e489de --- /dev/null +++ b/ui/imports/assets/icons/untrustworthy.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/imports/assets/icons/verified.svg b/ui/imports/assets/icons/verified.svg new file mode 100644 index 0000000000..4eb91e85f1 --- /dev/null +++ b/ui/imports/assets/icons/verified.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/imports/shared/controls/Input.qml b/ui/imports/shared/controls/Input.qml index 45c8617040..85eaec2a1d 100644 --- a/ui/imports/shared/controls/Input.qml +++ b/ui/imports/shared/controls/Input.qml @@ -125,12 +125,7 @@ Item { anchors.fill: parent acceptedButtons: Qt.RightButton | Qt.LeftButton onClicked: { - if((mouse.button === Qt.RightButton) && inputValue.canPaste) { - rightClickContextMenu.popup() - } - else { - inputValue.forceActiveFocus(Qt.MouseFocusReason) - } + inputValue.forceActiveFocus(Qt.MouseFocusReason) } } } @@ -183,9 +178,7 @@ Item { if (inputBox.copyToClipboard) { RootStore.copyToClipboard(inputBox.textToCopy ? inputBox.textToCopy : inputValue.text) } else { - if (inputValue.canPaste) { - inputValue.paste() - } + inputValue.paste() } copyBtn.copied = true @@ -211,22 +204,6 @@ Item { color: validationErrorColor wrapMode: TextEdit.Wrap } - - StatusPopupMenu { - id: rightClickContextMenu - - StatusMenuItem { - enabled: inputValue.canPaste - text: qsTrId("Paste") - onTriggered: { - inputValue.paste() - } - } - - onClosed: { - inputValue.forceActiveFocus(Qt.MouseFocusReason) - } - } } /*##^## diff --git a/ui/imports/shared/controls/chat/ProfileHeader.qml b/ui/imports/shared/controls/chat/ProfileHeader.qml index dc6421b60d..cf7fc322ff 100644 --- a/ui/imports/shared/controls/chat/ProfileHeader.qml +++ b/ui/imports/shared/controls/chat/ProfileHeader.qml @@ -18,18 +18,24 @@ Item { Big } + property var store property string displayName property string pubkey property string icon + property int trustStatus + property bool isContact: false property int imageSize: ProfileHeader.ImageSize.Compact property bool displayNameVisible: true + property bool displayNamePlusIconsVisible: false property bool pubkeyVisible: true + property bool pubkeyVisibleWithCopy: false property bool emojiHashVisible: true property bool editImageButtonVisible: false readonly property bool compact: root.imageSize == ProfileHeader.ImageSize.Compact signal clicked() + signal editClicked() height: visible ? contentContainer.height : 0 implicitHeight: contentContainer.implicitHeight @@ -108,9 +114,56 @@ Item { } } + Row { + width: 380 + spacing: Style.current.halfPadding + Layout.alignment: Qt.AlignHCenter + visible: root.displayNamePlusIconsVisible + StyledText { + text: root.displayName + font { + weight: Font.Medium + pixelSize: Style.current.primaryTextFontSize + } + } + + Loader { + sourceComponent: SVGImage { + height: 16 + width: 16 + source: Style.svg("contact") + } + active: isContact + } + + Loader { + sourceComponent: VerificationLabel { + id: trustStatus + trustStatus: root.trustStatus + height: 16 + width: 16 + } + active: root.trustStatus !== Constants.trustStatus.unknown + } + + SVGImage { + height: 16 + width: 16 + source: Style.svg("edit-message") + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + acceptedButtons: Qt.LeftButton + onClicked: { + root.editClicked() + } + } + } + + } + StyledText { Layout.fillWidth: true - visible: root.pubkeyVisible text: Utils.getElidedCompressedPk(pubkey) @@ -120,6 +173,31 @@ Item { color: Style.current.secondaryText } + Row { + width: 380 + Layout.alignment: Qt.AlignHCenter + visible: root.pubkeyVisibleWithCopy + StyledText { + id: txtChatKey + text: qsTr("Chatkey:%1...").arg(pubkey.substring(0, 32)) + horizontalAlignment: Text.AlignHCenter + font.pixelSize: Style.current.primaryTextFontSize + color: Style.current.secondaryText + width: 360 + } + + + CopyToClipBoardButton { + id: copyBtn + width: 20 + height: 20 + color: Style.current.transparent + textToCopy: pubkey + store: root.store + } + + } + EmojiHash { id: emojiHash Layout.alignment: Qt.AlignHCenter diff --git a/ui/imports/shared/controls/chat/VerificationLabel.qml b/ui/imports/shared/controls/chat/VerificationLabel.qml new file mode 100644 index 0000000000..00f1b22a9b --- /dev/null +++ b/ui/imports/shared/controls/chat/VerificationLabel.qml @@ -0,0 +1,25 @@ +import QtQuick 2.3 +import shared.controls 1.0 +import shared 1.0 +import shared.panels 1.0 + +import utils 1.0 + +SVGImage { + id: root + width: 10 + height: 10 + + property int trustStatus: Constants.trustStatus.unknown + + source: { + switch(trustStatus) { + case Constants.trustStatus.trusted: + return Style.svg("verified"); + case Constants.trustStatus.untrustworthy: + return Style.svg("untrustworthy"); + default: + return ""; + } + } +} \ No newline at end of file diff --git a/ui/imports/shared/controls/chat/qmldir b/ui/imports/shared/controls/chat/qmldir index 08a4866415..0b873cd121 100644 --- a/ui/imports/shared/controls/chat/qmldir +++ b/ui/imports/shared/controls/chat/qmldir @@ -13,3 +13,4 @@ GasSelectorButton 1.0 GasSelectorButton.qml MessageBorder 1.0 MessageBorder.qml EmojiReaction 1.0 EmojiReaction.qml ProfileHeader 1.0 ProfileHeader.qml +VerificationLabel 1.0 VerificationLabel.qml diff --git a/ui/imports/shared/panels/Separator.qml b/ui/imports/shared/panels/Separator.qml index 10355f4b88..277956258a 100644 --- a/ui/imports/shared/panels/Separator.qml +++ b/ui/imports/shared/panels/Separator.qml @@ -6,7 +6,8 @@ Item { id: root property color color: Style.current.separator width: parent.width - height: root.visible ? 1 : 0 + implicitHeight: 1 + height: root.visible ? implicitHeight : 0 anchors.topMargin: Style.current.padding Rectangle { id: separator diff --git a/ui/imports/shared/popups/ContactVerificationRequestPopup.qml b/ui/imports/shared/popups/ContactVerificationRequestPopup.qml new file mode 100644 index 0000000000..ccea785787 --- /dev/null +++ b/ui/imports/shared/popups/ContactVerificationRequestPopup.qml @@ -0,0 +1,151 @@ +import QtQuick 2.13 +import QtQuick.Controls 2.13 +import QtQuick.Dialogs 1.3 + +import utils 1.0 + +import StatusQ.Core 0.1 +import StatusQ.Core.Theme 0.1 +import StatusQ.Controls 0.1 +import StatusQ.Popups 0.1 + +import shared.controls 1.0 +import shared.views.chat 1.0 + +StatusModal { + id: root + + property string senderPublicKey: "" + property string senderDisplayName: "" + property string senderIcon: "" + property string challengeText: "" + property string responseText: "" + property string messageTimestamp: "" + property string responseTimestamp: "" + + signal verificationRefused(string senderPublicKey) + signal responseSent(string senderPublicKey, string response) + + header.title: qsTr("%1 is asking you to verify your identity").arg(root.senderDisplayName) + + x: Math.round(((parent ? parent.width : 0) - width) / 2) + y: Math.round(((parent ? parent.height : 0) - height) / 2) + + width: 480 + height: 230 + verificationMessage.height + verificationResponse.height + + onOpened: { + verificationResponse.input.edit.forceActiveFocus(Qt.MouseFocusReason) + } + onClosed: { + root.destroy(); + } + + contentItem: Item { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: Style.current.padding + anchors.rightMargin: Style.current.padding + + StatusBaseText { + id: description + width: parent.width + color: Theme.palette.directColor1 + wrapMode: Text.WordWrap + anchors.top: parent.top + anchors.topMargin: Style.current.padding + text: qsTr("%1 would like to verify your identity. Answer the question to prove your identity to %2") + .arg(root.senderDisplayName).arg(root.senderDisplayName) + font.pixelSize: 15 + } + + MessageView { + id: verificationMessage + anchors.top: description.bottom + anchors.topMargin: Style.current.padding + isMessage: true + shouldRepeatHeader: true + messageTimestamp: root.messageTimestamp + senderDisplayName: root.senderDisplayName + senderIcon: root.senderIcon + message: root.challengeText + messageContentType: Constants.messageContentType.messageType + placeholderMessage: true + } + + StatusInput { + id: verificationResponse + visible: !responseText + anchors.top: verificationMessage.bottom + anchors.topMargin: 5 + input.multiline: true + input.placeholderText: qsTr("Provide answer to verification request from this contact.") + input.implicitHeight: 152 + width: parent.width + input.verticalAlignment: TextEdit.AlignTop + leftPadding: 0 + rightPadding: 0 + charLimit: 280 + } + + MessageView { + id: responseMessage + visible: !!root.responseText + anchors.top: verificationMessage.bottom + isMessage: true + shouldRepeatHeader: true + messageTimestamp: root.responseTimestamp + senderDisplayName: userProfile.name + senderIcon: userProfile.icon + message: root.responseText + messageContentType: Constants.messageContentType.messageType + placeholderMessage: true + } + + StatusBaseText { + id: responseSent + visible: !!root.responseText + width: parent.width + color: Theme.palette.baseColor1 + wrapMode: Text.WordWrap + anchors.top: responseMessage.bottom + anchors.topMargin: 58 + text: qsTr("You're answer has been sent to %1.").arg(root.senderDisplayName) + font.pixelSize: 13 + horizontalAlignment: Text.AlignHCenter + } + } + + rightButtons: [ + StatusButton { + visible: !root.responseText + text: qsTr("Refuse Verification") + onClicked: { + root.verificationRefused(root.senderPublicKey) + root.close(); + } + }, + StatusButton { + text: qsTr("Send Answer") + visible: !root.responseText + enabled: verificationResponse.text !== "" + onClicked: { + root.responseSent(root.senderPublicKey, Utils.escapeHtml(verificationResponse.text)) + root.responseText = verificationResponse.text + root.responseTimestamp = Date.now() + } + }, + StatusFlatButton { + visible: root.responseText + text: qsTr("Change answer") + onClicked: { + root.responseText = "" + } + }, + StatusButton { + visible: root.responseText + text: qsTr("Close") + onClicked: root.close() + } + ] +} diff --git a/ui/app/AppLayouts/Profile/views/DisplayNamePopup.qml b/ui/imports/shared/popups/DisplayNamePopup.qml similarity index 100% rename from ui/app/AppLayouts/Profile/views/DisplayNamePopup.qml rename to ui/imports/shared/popups/DisplayNamePopup.qml diff --git a/ui/imports/shared/popups/ProfilePopup.qml b/ui/imports/shared/popups/ProfilePopup.qml index 6d3a8f1c00..a8595e7cb3 100644 --- a/ui/imports/shared/popups/ProfilePopup.qml +++ b/ui/imports/shared/popups/ProfilePopup.qml @@ -9,6 +9,7 @@ import shared.popups 1.0 import shared.stores 1.0 import shared.views 1.0 as SharedViews import shared.controls.chat 1.0 +import shared.panels 1.0 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -30,11 +31,40 @@ StatusModal { property string userNickname: "" property string userEnsName: "" property string userIcon: "" + property int userTrustStatus: Constants.trustStatus.unknown + property int verificationStatus: Constants.verificationStatus.unverified + property string text: "" + property string challenge: "" + property string response: "" property bool userIsEnsVerified: false property bool userIsBlocked: false + property bool userIsUntrustworthy: false + property bool userTrustIsUnknown: false property bool isCurrentUser: false property bool isAddedContact: false + property bool isMutualContact: false + property bool isVerificationSent: false + property bool isVerified: false + property bool isTrusted: false + property bool hasReceivedVerificationRequest: false + + property bool showRemoveVerified: false + property bool showVerifyIdentitySection: false + property bool showVerificationPendingSection: false + property bool showIdentityVerified: false + property bool showIdentityVerifiedUntrustworthy: false + + property string verificationChallenge: "" + property string verificationResponse: "" + property string verificationResponseDisplayName: "" + property string verificationResponseIcon: "" + property string verificationRequestedAt: "" + property string verificationRepliedAt: "" + + signal blockButtonClicked(name: string, address: string) + signal unblockButtonClicked(name: string, address: string) + signal removeButtonClicked(address: string) signal contactUnblocked(publicKey: string) signal contactBlocked(publicKey: string) @@ -51,6 +81,32 @@ StatusModal { userIsEnsVerified = contactDetails.ensVerified; userIsBlocked = contactDetails.isBlocked; isAddedContact = contactDetails.isContact; + isMutualContact = contactDetails.isContact && contactDetails.hasAddedUs + userTrustStatus = contactDetails.trustStatus + userTrustIsUnknown = contactDetails.trustStatus === Constants.trustStatus.unknown + userIsUntrustworthy = contactDetails.trustStatus === Constants.trustStatus.untrustworthy + verificationStatus = contactDetails.verificationStatus + isVerificationSent = verificationStatus !== Constants.verificationStatus.unverified + + if (isMutualContact && popup.contactsStore.hasReceivedVerificationRequestFrom(publicKey)) { + popup.hasReceivedVerificationRequest = true + } + + if(isMutualContact && isVerificationSent) { + let verificationDetails = popup.contactsStore.getSentVerificationDetailsAsJson(publicKey); + + verificationStatus = verificationDetails.requestStatus; + verificationChallenge = verificationDetails.challenge; + verificationResponse = verificationDetails.response; + verificationResponseDisplayName = verificationDetails.displayName; + verificationResponseIcon = verificationDetails.icon; + verificationRequestedAt = verificationDetails.requestedAt; + verificationRepliedAt = verificationDetails.repliedAt; + } + isTrusted = verificationStatus === Constants.verificationStatus.trusted + isVerified = verificationStatus === Constants.verificationStatus.verified + + text = ""; // this is most likely unneeded isCurrentUser = popup.profileStore.pubkey === publicKey; showFooter = !isCurrentUser; @@ -79,19 +135,19 @@ StatusModal { profileView.unblockContactConfirmationDialog.open(); } - header.title: userDisplayName + qsTr("'s Profile") + width: 700 + + header.title: { + if(showVerifyIdentitySection || showVerificationPendingSection){ + return qsTr("Verify %1's Identity").arg(userIsEnsVerified ? userName : userDisplayName) + } + return qsTr("%1's Profile").arg(userIsEnsVerified ? userName : userDisplayName) + } header.subTitle: userIsEnsVerified ? userName : Utils.getElidedCompressedPk(userPublicKey) header.subTitleElide: Text.ElideMiddle padding: 8 - QtObject { - id: d - - readonly property int contentSpacing: 5 - readonly property int contentMargins: 16 - } - - headerActionButton: StatusFlatRoundButton { + headerActionButton: StatusFlatRoundButton { type: StatusFlatRoundButton.Type.Secondary width: 32 height: 32 @@ -120,6 +176,33 @@ StatusModal { isAddedContact: popup.isAddedContact isCurrentUser: popup.isCurrentUser + isMutualContact: popup.isMutualContact + isVerificationSent: popup.isVerificationSent + isVerified: popup.isVerified + isTrusted: popup.isTrusted + hasReceivedVerificationRequest: popup.hasReceivedVerificationRequest + + userTrustStatus: popup.userTrustStatus + verificationStatus: popup.verificationStatus + + showVerifyIdentitySection: popup.showVerifyIdentitySection + showVerificationPendingSection: popup.showVerificationPendingSection + showIdentityVerified: popup.showIdentityVerified + showIdentityVerifiedUntrustworthy: popup.showIdentityVerifiedUntrustworthy + + challenge: popup.challenge + response: popup.response + + userIsUntrustworthy: popup.userIsUntrustworthy + userTrustIsUnknown: popup.userTrustIsUnknown + + verificationChallenge: popup.verificationChallenge + verificationResponse: popup.verificationResponse + verificationResponseDisplayName: popup.verificationResponseDisplayName + verificationResponseIcon: popup.verificationResponseIcon + verificationRequestedAt: popup.verificationRequestedAt + verificationRepliedAt: popup.verificationRepliedAt + onContactUnblocked: { popup.close() popup.contactUnblocked(publicKey) @@ -149,7 +232,6 @@ StatusModal { id: sendContactRequestModal anchors.centerIn: parent width: popup.width - height: popup.height visible: false header.title: qsTr("Send Contact Request to") + " " + userDisplayName userPublicKey: popup.userPublicKey @@ -158,6 +240,17 @@ StatusModal { onAccepted: popup.contactsStore.sendContactRequest(userPublicKey, message) onClosed: popup.close() } + + leftButtons:[ + StatusButton { + text: qsTr("Cancel verification") + visible: !isVerified && isMutualContact && isVerificationSent && showVerificationPendingSection + onClicked: { + popup.contactsStore.cancelVerificationRequest(userPublicKey); + popup.close() + } + } + ] rightButtons: [ StatusFlatButton { @@ -165,11 +258,12 @@ StatusModal { qsTr("Unblock User") : qsTr("Block User") type: StatusBaseButton.Type.Danger + visible: !isAddedContact onClicked: userIsBlocked ? unblockUser() : blockUser() }, StatusFlatButton { - visible: !userIsBlocked && isAddedContact + visible: !showRemoveVerified && !showIdentityVerified && !showVerifyIdentitySection && !showVerificationPendingSection && !userIsBlocked && isAddedContact type: StatusBaseButton.Type.Danger text: qsTr('Remove Contact') onClicked: { @@ -182,6 +276,160 @@ StatusModal { text: qsTr("Send Contact Request") visible: !userIsBlocked && !isAddedContact onClicked: sendContactRequestModal.open() + }, + + StatusButton { + text: qsTr("Mark Untrustworthy") + visible: !showIdentityVerifiedUntrustworthy && !showIdentityVerified && !showVerifyIdentitySection && userTrustIsUnknown + enabled: !showVerificationPendingSection || verificationResponse !== "" + type: StatusBaseButton.Type.Danger + onClicked: { + if (showVerificationPendingSection) { + popup.showIdentityVerified = false; + popup.showIdentityVerifiedUntrustworthy = true; + popup.showVerificationPendingSection = false; + popup.showVerifyIdentitySection = false; + profileView.stepsListModel.setProperty(2, "stepCompleted", true); + popup.contactsStore.verifiedUntrustworthy(userPublicKey); + } else { + popup.contactsStore.markUntrustworthy(userPublicKey); + popup.close(); + } + } + }, + + StatusButton { + text: qsTr("Remove 'Identity Verified' status") + visible: isTrusted && !showIdentityVerified && !showRemoveVerified + type: StatusBaseButton.Type.Danger + onClicked: { + showRemoveVerified = true + } + }, + + StatusButton { + text: qsTr("No") + visible: showRemoveVerified + type: StatusBaseButton.Type.Danger + onClicked: { + showRemoveVerified = false + } + }, + + StatusButton { + text: qsTr("Yes") + visible: showRemoveVerified + onClicked: { + popup.contactsStore.removeTrustStatus(userPublicKey); + popup.close(); + } + }, + + StatusButton { + text: qsTr("Remove Untrustworthy Mark") + visible: userIsUntrustworthy + onClicked: { + popup.contactsStore.removeTrustStatus(userPublicKey); + popup.close(); + } + }, + + StatusButton { + text: qsTr("Verify Identity") + visible: !showIdentityVerifiedUntrustworthy && !showIdentityVerified && + !showVerifyIdentitySection && isMutualContact && !isVerificationSent + && !hasReceivedVerificationRequest + onClicked: { + popup.showVerifyIdentitySection = true + } + }, + + StatusButton { + text: qsTr("Verify Identity pending...") + visible: (!showIdentityVerifiedUntrustworthy && !showIdentityVerified && !isTrusted + && isMutualContact && isVerificationSent && !showVerificationPendingSection) || + (hasReceivedVerificationRequest && !isTrusted) + onClicked: { + if (hasReceivedVerificationRequest) { + try { + let request = popup.contactsStore.getVerificationDetailsFromAsJson(popup.userPublicKey) + Global.openPopup(contactVerificationRequestPopupComponent, { + senderPublicKey: request.from, + senderDisplayName: request.displayName, + senderIcon: request.icon, + challengeText: request.challenge, + responseText: request.response, + messageTimestamp: request.requestedAt, + responseTimestamp: request.repliedAt + }) + } catch (e) { + console.error("Error getting or parsing verification data", e) + } + } else { + popup.showVerificationPendingSection = true + profileView.wizardAnimation.running = true + } + } + }, + + + StatusButton { + text: qsTr("Send verification request") + visible: showVerifyIdentitySection && isMutualContact && !isVerificationSent + onClicked: { + popup.contactsStore.sendVerificationRequest(userPublicKey, Utils.escapeHtml(profileView.challengeTxt.input.text)); + profileView.stepsListModel.setProperty(1, "stepCompleted", true); + Global.displayToastMessage(qsTr("Verification request sent"), + "", + "checkmark-circle", + false, + Constants.ephemeralNotificationType.normal, + ""); + popup.close(); + } + }, + + StatusButton { + text: qsTr("Confirm Identity") + visible: isMutualContact && isVerificationSent && !isTrusted && showVerificationPendingSection + enabled: verificationChallenge !== "" && verificationResponse !== "" + onClicked: { + popup.showIdentityVerified = true; + popup.showIdentityVerifiedUntrustworthy = false; + popup.showVerificationPendingSection = false; + popup.showVerifyIdentitySection = false; + profileView.stepsListModel.setProperty(2, "stepCompleted", true); + popup.contactsStore.verifiedTrusted(userPublicKey); + popup.isTrusted = true + } + }, + + StatusButton { + visible: showIdentityVerified || showIdentityVerifiedUntrustworthy + text: qsTr("Rename") + onClicked: { + nicknamePopup.open() + } + }, + + StatusButton { + visible: showIdentityVerified || showIdentityVerifiedUntrustworthy + text: qsTr("Close") + onClicked: { + popup.close(); + } } ] + + Component { + id: contactVerificationRequestPopupComponent + ContactVerificationRequestPopup { + onResponseSent: { + popup.contactsStore.acceptVerificationRequest(senderPublicKey, response) + } + onVerificationRefused: { + popup.contactsStore.declineVerificationRequest(senderPublicKey) + } + } + } } diff --git a/ui/imports/shared/popups/SendContactRequestModal.qml b/ui/imports/shared/popups/SendContactRequestModal.qml index 5a586bb329..553021d873 100644 --- a/ui/imports/shared/popups/SendContactRequestModal.qml +++ b/ui/imports/shared/popups/SendContactRequestModal.qml @@ -71,7 +71,7 @@ StatusModal { enabled: messageInput.valid text: qsTr("Send Contact Request") onClicked: { - root.accepted(messageInput.text); + root.accepted(Utils.escapeHtml(messageInput.text)); root.close(); } } diff --git a/ui/imports/shared/popups/qmldir b/ui/imports/shared/popups/qmldir index d5f38e46e9..a3ed9ea506 100644 --- a/ui/imports/shared/popups/qmldir +++ b/ui/imports/shared/popups/qmldir @@ -3,6 +3,7 @@ BlockContactConfirmationDialog 1.0 BlockContactConfirmationDialog.qml SettingsDirtyToastMessage 1.0 SettingsDirtyToastMessage.qml ConfirmationDialog 1.0 ConfirmationDialog.qml CommunityIntroDialog 1.0 CommunityIntroDialog.qml +ContactVerificationRequestPopup 1.0 ContactVerificationRequestPopup.qml DownloadModal 1.0 DownloadModal.qml DownloadPage 1.0 DownloadPage.qml ImageCropperModal 1.0 ImageCropperModal.qml @@ -20,3 +21,4 @@ SelectAccountModal 1.0 SelectAccountModal.qml ProfilePopup 1.0 ProfilePopup.qml ImageCropWorkflow 1.0 ImageCropWorkflow.qml ImportCommunityPopup 1.0 ImportCommunityPopup.qml +DisplayNamePopup 1.0 DisplayNamePopup.qml diff --git a/ui/imports/shared/views/ProfileView.qml b/ui/imports/shared/views/ProfileView.qml index 1cf8a30e57..f9a2121c46 100644 --- a/ui/imports/shared/views/ProfileView.qml +++ b/ui/imports/shared/views/ProfileView.qml @@ -7,7 +7,9 @@ import utils 1.0 import shared 1.0 import shared.popups 1.0 import shared.stores 1.0 +import shared.views.chat 1.0 import shared.controls.chat 1.0 +import shared.panels 1.0 import StatusQ.Core 0.1 import StatusQ.Core.Theme 0.1 @@ -36,10 +38,42 @@ Rectangle { property bool isCurrentUser: profileStore.pubkey === userPublicKey property bool isAddedContact: false + property int userTrustStatus: Constants.trustStatus.unknown + property int verificationStatus: Constants.verificationStatus.unverified + + property string challenge: "" + property string response: "" + + property bool userIsUntrustworthy: false + property bool userTrustIsUnknown: false + property bool isMutualContact: false + property bool isVerificationSent: false + property bool isVerified: false + property bool isTrusted: false + property bool hasReceivedVerificationRequest: false + + property bool showRemoveVerified: false + property bool showVerifyIdentitySection: false + property bool showVerificationPendingSection: false + property bool showIdentityVerified: false + property bool showIdentityVerifiedUntrustworthy: false + + property string verificationChallenge: "" + property string verificationResponse: "" + property string verificationResponseDisplayName: "" + property string verificationResponseIcon: "" + property string verificationRequestedAt: "" + property string verificationRepliedAt: "" + readonly property alias qrCodePopup: qrCodePopup readonly property alias unblockContactConfirmationDialog: unblockContactConfirmationDialog readonly property alias blockContactConfirmationDialog: blockContactConfirmationDialog readonly property alias removeContactConfirmationDialog: removeContactConfirmationDialog + readonly property alias wizardAnimation: wizardAnimation + readonly property alias challengeTxt: challengeTxt + readonly property alias stepsListModel: stepsListModel + + readonly property int animationDuration: 500 signal contactUnblocked(publicKey: string) signal contactBlocked(publicKey: string) @@ -59,13 +93,66 @@ Rectangle { readonly property int subTitleElide: Text.ElideMiddle } + SequentialAnimation { + id: wizardAnimation + ScriptAction { + id: step1 + property int loadingTime: 0 + Behavior on loadingTime { NumberAnimation { duration: animationDuration }} + onLoadingTimeChanged: { + if (isVerificationSent) { + stepsListModel.setProperty(1, "loadingTime", step1.loadingTime); + } + } + script: { + step1.loadingTime = animationDuration; + stepsListModel.setProperty(0, "loadingTime", step1.loadingTime); + + if (isVerificationSent) { + stepsListModel.setProperty(0, "stepCompleted", true); + } + } + } + PauseAnimation { + duration: animationDuration + 100 + } + ScriptAction { + id: step2 + property int loadingTime: 0 + Behavior on loadingTime { NumberAnimation { duration: animationDuration } } + onLoadingTimeChanged: { + if (isVerificationSent && !!verificationResponse) { + stepsListModel.setProperty(2, "loadingTime", step2.loadingTime); + } + } + script: { + if (isVerificationSent && !!verificationChallenge) { + step2.loadingTime = animationDuration; + if (isVerificationSent && !!verificationResponse) { + stepsListModel.setProperty(1, "stepCompleted", true); + } + } + } + } + PauseAnimation { + duration: animationDuration + 100 + } + ScriptAction { + script: { + if (verificationStatus === Constants.verificationStatus.trusted) { + stepsListModel.setProperty(2, "stepCompleted", true); + } + } + } + } + ColumnLayout { id: modalContent anchors.fill: parent Item { Layout.fillWidth: true - implicitHeight: 16 + implicitHeight: 32 } ProfileHeader { @@ -74,11 +161,23 @@ Rectangle { displayName: root.userDisplayName pubkey: root.userPublicKey icon: root.isCurrentUser ? root.profileStore.icon : root.userIcon + trustStatus: root.userTrustStatus + isContact: root.isAddedContact + store: root.profileStore displayNameVisible: false + displayNamePlusIconsVisible: true + pubkeyVisibleWithCopy: true pubkeyVisible: false imageSize: ProfileHeader.ImageSize.Middle editImageButtonVisible: root.isCurrentUser + onEditClicked: { + if(!isCurrentUser){ + nicknamePopup.open() + } else { + Global.openEditDisplayNamePopup() + } + } } StatusBanner { @@ -88,26 +187,9 @@ Rectangle { statusText: qsTr("Blocked") } - Item { - Layout.fillWidth: true - implicitHeight: 16 - } - - StatusDescriptionListItem { - Layout.fillWidth: true - title: root.userIsEnsVerified ? qsTr("ENS username") : qsTr("Username") - subTitle: root.userIsEnsVerified ? root.userEnsName : root.userName - tooltip.text: qsTr("Copied to clipboard") - tooltip.timeout: 1000 - icon.name: "copy" - iconButton.onClicked: { - globalUtils.copyToClipboard(subTitle) - tooltip.open(); - } - } - StatusDescriptionListItem { Layout.fillWidth: true + visible: !showVerifyIdentitySection && !showVerificationPendingSection && !showIdentityVerified title: qsTr("Chat key") subTitle: Utils.getCompressedPk(root.userPublicKey) subTitleComponent.elide: Text.ElideMiddle @@ -124,6 +206,7 @@ Rectangle { StatusDescriptionListItem { Layout.fillWidth: true + visible: !showVerifyIdentitySection && !showVerificationPendingSection && !showIdentityVerified title: qsTr("Share Profile URL") subTitle: { let user = "" @@ -154,22 +237,164 @@ Rectangle { } } - StatusDescriptionListItem { - Layout.fillWidth: true - visible: !isCurrentUser - title: qsTr("Chat settings") - subTitle: qsTr("Nickname") - value: userNickname ? userNickname : qsTr("None") - sensor.enabled: true - sensor.onClicked: { - nicknamePopup.open() - } + ListModel { + id: stepsListModel + ListElement {description:"Send Request"; loadingTime: 0; stepCompleted: false} + ListElement {description:"Receive Response"; loadingTime: 0; stepCompleted: false} + ListElement {description:"Confirm Identity"; loadingTime: 0; stepCompleted: false} + } + + StatusWizardStepper { + id: wizardStepper + maxDuration: animationDuration + visible: showVerifyIdentitySection || showVerificationPendingSection || showIdentityVerified || showIdentityVerifiedUntrustworthy + width: parent.width + stepsModel: stepsListModel + } + + Separator { + visible: wizardStepper.visible + implicitHeight: 32 + } + + StatusBaseText { + id: confirmLbl + visible: showIdentityVerified + text: qsTr("You have confirmed %1's identity. From now on this verification emblem will always be displayed alongside %1's nickname.").arg(userIsEnsVerified ? userEnsName : userDisplayName) + font.pixelSize: Style.current.additionalTextSize + horizontalAlignment : Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 363 + wrapMode: Text.WordWrap + color: Theme.palette.baseColor1 + } + + StatusBaseText { + id: confirmUntrustworthyLbl + visible: showIdentityVerifiedUntrustworthy + text: qsTr("You have marked %1 as Untrustworthy. From now on this Untrustworthy emblem will always be displayed alongside %1's nickname.").arg(userIsEnsVerified ? userEnsName : userDisplayName) + font.pixelSize: Style.current.additionalTextSize + horizontalAlignment : Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 363 + wrapMode: Text.WordWrap + color: Theme.palette.baseColor1 } Item { + visible: checkboxIcon.visible || dangerIcon.visible + Layout.fillWidth: true + implicitHeight: visible ? 16 : 0 + } + + StatusRoundIcon { + id: checkboxIcon + visible: confirmLbl.visible + icon.name: "checkbox" + icon.width: 16 + icon.height: 16 + icon.color: Theme.palette.white + Layout.alignment: Qt.AlignHCenter + color: Theme.palette.primaryColor1 + width: 32 + height: 32 + } + + StatusDescriptionListItem { + Layout.fillWidth: true + visible: !showVerifyIdentitySection && !showVerificationPendingSection && !showIdentityVerified + title: root.userIsEnsVerified ? qsTr("ENS username") : qsTr("Username") + subTitle: root.userIsEnsVerified ? root.userEnsName : root.userName + tooltip.text: qsTr("Copied to clipboard") + tooltip.timeout: 1000 + icon.name: "copy" + iconButton.onClicked: { + globalUtils.copyToClipboard(subTitle) + tooltip.open(); + } + } + + StatusRoundIcon { + id: dangerIcon + visible: confirmUntrustworthyLbl.visible + icon.name: "tiny/subtract" + icon.width: 5 + icon.height: 21 + icon.color: Theme.palette.white + Layout.alignment: Qt.AlignHCenter + color: Theme.palette.dangerColor1 + width: 32 + height: 32 + } + + Item { + visible: checkboxIcon.visible || dangerIcon.visible + height: visible ? 16 : 0 + Layout.fillWidth: true + } + + StatusInput { + id: challengeTxt + visible: showVerifyIdentitySection + charLimit: 280 + input.text: root.challenge + Layout.fillWidth: true + Layout.rightMargin: d.contentMargins + Layout.leftMargin: d.contentMargins + input.multiline: true + input.implicitHeight: 152 + input.placeholderText: qsTr("Ask a question that only the real %1 will be able to answer e.g. a question about a shared experience, or ask Mark to enter a code or phrase you have sent to them via a different communication channel (phone, post, etc...).").arg(userIsEnsVerified ? userEnsName : userDisplayName) + } + + MessageView { + id: challengeMessage + visible: root.showVerificationPendingSection + Layout.fillWidth: true + isMessage: true + shouldRepeatHeader: true + messageTimestamp: root.verificationRequestedAt + senderDisplayName: userProfile.name + senderIcon: userProfile.icon + message: root.verificationChallenge + messageContentType: Constants.messageContentType.messageType + placeholderMessage: true + } + + MessageView { + id: responseMessage + visible: root.showVerificationPendingSection && !!root.verificationResponse + width: parent.width + isMessage: true + shouldRepeatHeader: true + messageTimestamp: root.verificationRepliedAt + senderDisplayName: root.verificationResponseDisplayName + senderIcon: root.verificationResponseIcon + message: root.verificationResponse + messageContentType: Constants.messageContentType.messageType + placeholderMessage: true + } + + Item { + visible: waitingForText.visible + height: 32 + Layout.fillWidth: true + } + + StatusBaseText { + id: waitingForText + visible: showVerificationPendingSection && !verificationResponse + text: qsTr("Waiting for %1's response...").arg(userIsEnsVerified ? userEnsName : userDisplayName) + font.pixelSize: Style.current.additionalTextSize + horizontalAlignment : Text.AlignHCenter + Layout.alignment: Qt.AlignHCenter + Layout.preferredWidth: 363 + wrapMode: Text.WordWrap + color: Theme.palette.baseColor1 + } + + Item { + height: 32 Layout.fillWidth: true - visible: !isCurrentUser - implicitHeight: 16 } } diff --git a/ui/imports/shared/views/chat/CompactMessageView.qml b/ui/imports/shared/views/chat/CompactMessageView.qml index 163c6f4a95..446fea0b34 100644 --- a/ui/imports/shared/views/chat/CompactMessageView.qml +++ b/ui/imports/shared/views/chat/CompactMessageView.qml @@ -28,6 +28,7 @@ Item { property int contentType property bool isChatBlocked: false property bool isActiveChannel: false + property int senderTrustStatus property int chatHorizontalPadding: Style.current.halfPadding property int chatVerticalPadding: 7 @@ -72,8 +73,9 @@ Item { + (dateGroupLbl.visible ? dateGroupLbl.height + dateGroupLbl.anchors.topMargin : 0) Connections { - target: !!root.messageStore && root.messageStore.messageModule? root.messageStore.messageModule : null - enabled: responseTo !== "" + target: !!root.messageStore && root.messageStore.messageModule ? + root.messageStore.messageModule : null + enabled: !!root.messageStore && !!root.messageStore.messageModule && responseTo !== "" onRefreshAMessageUserRespondedTo: { if(msgId === messageId) chatReply.resetOriginalMessage() @@ -383,11 +385,21 @@ Item { } } + VerificationLabel { + id: trustStatus + anchors.left: chatName.right + anchors.leftMargin: 4 + anchors.bottom: chatName.bottom + anchors.bottomMargin: 4 + visible: !root.amISender && chatName.visible + trustStatus: senderTrustStatus + } + ChatTimePanel { id: chatTime visible: !editModeOn && headerRepeatCondition anchors.verticalCenter: chatName.verticalCenter - anchors.left: chatName.right + anchors.left: trustStatus.right anchors.leftMargin: 4 color: Style.current.secondaryText timestamp: messageTimestamp diff --git a/ui/imports/shared/views/chat/MessageView.qml b/ui/imports/shared/views/chat/MessageView.qml index 2ef719c304..cbc2af5bd9 100644 --- a/ui/imports/shared/views/chat/MessageView.qml +++ b/ui/imports/shared/views/chat/MessageView.qml @@ -41,6 +41,7 @@ Column { property string senderIcon: "" property bool amISender: false property bool senderIsAdded: false + property int senderTrustStatus: Constants.trustStatus.unknown readonly property string senderIconToShow: { if ((!senderIsAdded && Global.privacyModuleInst.profilePicturesVisibility !== @@ -340,6 +341,7 @@ Column { isChatBlocked: root.isChatBlocked isActiveChannel: root.isActiveChannel emojiPopup: root.emojiPopup + senderTrustStatus: root.senderTrustStatus communityId: root.communityId stickersLoaded: root.stickersLoaded @@ -353,7 +355,7 @@ Column { linkUrls: root.linkUrls isInPinnedPopup: root.isInPinnedPopup pinnedMessage: root.pinnedMessage - canPin: messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins + canPin: !!messageStore && messageStore.getNumberOfPinnedMessages() < Constants.maxNumberOfPins transactionParams: root.transactionParams diff --git a/ui/imports/utils/Constants.qml b/ui/imports/utils/Constants.qml index c72421838b..fbe1f812d1 100644 --- a/ui/imports/utils/Constants.qml +++ b/ui/imports/utils/Constants.qml @@ -100,12 +100,21 @@ QtObject { readonly property int noOne: 3 } - readonly property QtObject contactVerificationState: QtObject { - readonly property int notMarked: 0 - readonly property int verified: 1 + readonly property QtObject trustStatus: QtObject { + readonly property int unknown: 0 + readonly property int trusted: 1 readonly property int untrustworthy: 2 } + readonly property QtObject verificationStatus: QtObject { + readonly property int unverified: 0 + readonly property int verifying: 1 + readonly property int verified: 2 + readonly property int declined: 3 + readonly property int canceled: 4 + readonly property int trusted: 5 + } + readonly property QtObject contactsPanelUsage: QtObject { readonly property int unknownPosition: -1 readonly property int mutualContacts: 0 diff --git a/ui/imports/utils/Global.qml b/ui/imports/utils/Global.qml index a1f2300502..ff32b5a76e 100644 --- a/ui/imports/utils/Global.qml +++ b/ui/imports/utils/Global.qml @@ -28,6 +28,7 @@ QtObject { signal openProfilePopupRequested(string publicKey, var parentPopup, string state) signal openChangeProfilePicPopup() signal displayToastMessage(string title, string subTitle, string icon, bool loading, int ephNotifType, string url) + signal openEditDisplayNamePopup() function openProfilePopup(publicKey, parentPopup, state = "") { openProfilePopupRequested(publicKey, parentPopup, state); diff --git a/ui/imports/utils/Utils.qml b/ui/imports/utils/Utils.qml index fad3d327b3..d0b42745e0 100644 --- a/ui/imports/utils/Utils.qml +++ b/ui/imports/utils/Utils.qml @@ -640,7 +640,9 @@ QtObject { isBlocked: false, requestReceived: false, isSyncing: false, - removed: false + removed: false, + trustStatus: Constants.trustStatus.unknown, + verificationStatus: Constants.verificationStatus.unverified } } } diff --git a/vendor/status-go b/vendor/status-go index 7ad0057003..0322ac497b 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 7ad0057003d968423b7ee482502cbf0e0b6716d4 +Subproject commit 0322ac497bf9e4852b99780f4ff08377a8c3f267