diff --git a/src/app/core/notifications/details.nim b/src/app/core/notifications/details.nim index cb7b625a06..ff25b282da 100644 --- a/src/app/core/notifications/details.nim +++ b/src/app/core/notifications/details.nim @@ -16,7 +16,13 @@ type NewMessage, NewMessageWithPersonalMention, NewMessageWithGlobalMention, - IdentityVerificationRequest + IdentityVerificationRequest, + CommunityTokenPermissionCreated, + CommunityTokenPermissionUpdated, + CommunityTokenPermissionDeleted, + CommunityTokenPermissionCreationFailed, + CommunityTokenPermissionUpdateFailed, + CommunityTokenPermissionDeletionFailed NotificationDetails* = object notificationType*: NotificationType # the default value is `UnknownNotification` diff --git a/src/app/core/notifications/notifications_manager.nim b/src/app/core/notifications/notifications_manager.nim index 9cb0d2c801..36cc866d12 100644 --- a/src/app/core/notifications/notifications_manager.nim +++ b/src/app/core/notifications/notifications_manager.nim @@ -85,6 +85,12 @@ QtObject: self, "onMeMentionedIconBadgeNotification(int)", 2) signalConnect(singletonInstance.globalEvents, "showAcceptedContactRequest(QString, QString, QString)", self, "onShowAcceptedContactRequest(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionCreatedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionCreatedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionUpdatedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionUpdatedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionDeletedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionDeletedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionCreationFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionCreationFailedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionUpdateFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionUpdateFailedNotification(QString, QString, QString)", 2) + signalConnect(singletonInstance.globalEvents, "showCommunityTokenPermissionDeletionFailedNotification(QString, QString, QString)", self, "onShowCommunityTokenPermissionDeletionFailedNotification(QString, QString, QString)", 2) self.notificationSetUp = true @@ -140,6 +146,30 @@ QtObject: messageId: messageId) self.processNotification(title, message, details) + proc onShowCommunityTokenPermissionCreatedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionCreated, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionUpdatedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionUpdated, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionDeletedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionDeleted, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionCreationFailedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionCreationFailed, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionUpdateFailedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionUpdateFailed, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + + proc onShowCommunityTokenPermissionDeletionFailedNotification*(self: NotificationsManager, sectionId: string, title: string, message: string) {.slot.} = + let details = NotificationDetails(notificationType: NotificationType.CommunityTokenPermissionDeletionFailed, sectionId: sectionId, isCommunitySection: true) + self.processNotification(title, message, details) + proc onShowNewContactRequestNotification*(self: NotificationsManager, title: string, message: string, sectionId: string) {.slot.} = let details = NotificationDetails(notificationType: NotificationType.NewContactRequest, sectionId: sectionId) @@ -306,4 +336,4 @@ QtObject: # In all other cases (TestNotification, AcceptedContactRequest, JoinCommunityRequest, MyRequestToJoinCommunityAccepted, # MyRequestToJoinCommunityRejected) else: - self.notificationCheck(title, message, details, "") \ No newline at end of file + self.notificationCheck(title, message, details, "") diff --git a/src/app/global/global_events.nim b/src/app/global/global_events.nim index 1a3bcc8333..2a67404c5c 100644 --- a/src/app/global/global_events.nim +++ b/src/app/global/global_events.nim @@ -15,6 +15,18 @@ QtObject: proc showTestNotification*(self: GlobalEvents, title: string, message: string) {.signal.} + proc showCommunityTokenPermissionCreatedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionUpdatedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionDeletedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionCreationFailedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionUpdateFailedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + + proc showCommunityTokenPermissionDeletionFailedNotification*(self: GlobalEvents, sectionId: string, title: string, message: string) {.signal.} + proc showMessageNotification*(self: GlobalEvents, title: string, message: string, sectionId: string, isCommunitySection: bool, isSectionActive: bool, chatId: string, isChatActive: bool, messageId: string, notificationType: int, isOneToOne: bool, isGroupChat: bool) {.signal.} @@ -34,4 +46,4 @@ QtObject: proc showAcceptedContactRequest*(self: GlobalEvents, title: string, message: string, sectionId: string) {.signal.} - proc meMentionedIconBadgeNotification*(self: GlobalEvents, allMentions: int) {.signal.} \ No newline at end of file + proc meMentionedIconBadgeNotification*(self: GlobalEvents, allMentions: int) {.signal.} diff --git a/src/app/modules/main/chat_section/controller.nim b/src/app/modules/main/chat_section/controller.nim index 49a39cc1f4..37a8fe957e 100644 --- a/src/app/modules/main/chat_section/controller.nim +++ b/src/app/modules/main/chat_section/controller.nim @@ -10,6 +10,9 @@ import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/gif/service as gif_service import ../../../../app_service/service/mailservers/service as mailservers_service +import ../../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../../app_service/service/token/service as token_service +import ../../../../app_service/service/community_tokens/service as community_tokens_service import ../../../../app_service/service/visual_identity/service as procs_from_visual_identity_service import ../../../core/signals/types @@ -33,12 +36,18 @@ type messageService: message_service.Service gifService: gif_service.Service mailserversService: mailservers_service.Service + walletAccountService: wallet_account_service.Service + tokenService: token_service.Service + communityTokensService: community_tokens_service.Service proc newController*(delegate: io_interface.AccessInterface, sectionId: string, isCommunity: bool, events: EventEmitter, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, contactService: contact_service.Service, chatService: chat_service.Service, communityService: community_service.Service, messageService: message_service.Service, gifService: gif_service.Service, - mailserversService: mailservers_service.Service): Controller = + mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service): Controller = result = Controller() result.delegate = delegate result.sectionId = sectionId @@ -53,6 +62,9 @@ proc newController*(delegate: io_interface.AccessInterface, sectionId: string, i result.messageService = messageService result.gifService = gifService result.mailserversService = mailserversService + result.walletAccountService = walletAccountService + result.tokenService = tokenService + result.communityTokensService = communityTokensService proc delete*(self: Controller) = discard @@ -202,6 +214,40 @@ proc init*(self: Controller) = if (args.communityId == self.sectionId): self.delegate.onCategoryUnmuted(args.categoryId) + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionCreated(args.communityId, args.tokenPermission) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATION_FAILED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionCreationFailed(args.communityId) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionUpdated(args.communityId, args.tokenPermission) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATE_FAILED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionUpdateFailed(args.communityId) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED) do(e: Args): + let args = CommunityTokenPermissionRemovedArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionDeleted(args.communityId, args.permissionId) + + self.events.on(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETION_FAILED) do(e: Args): + let args = CommunityTokenPermissionArgs(e) + if (args.communityId == self.sectionId): + self.delegate.onCommunityTokenPermissionDeletionFailed(args.communityId) + + self.events.on(SIGNAL_WALLET_ACCOUNT_TOKENS_REBUILT) do(e: Args): + self.delegate.onWalletAccountTokensRebuilt() + + self.events.on(SIGNAL_CONTACT_NICKNAME_CHANGED) do(e: Args): var args = ContactArgs(e) self.delegate.onContactDetailsUpdated(args.contactId) @@ -258,8 +304,11 @@ proc getMySectionId*(self: Controller): string = proc isCommunity*(self: Controller): bool = return self.isCommunitySection +proc getCommunityByIdFromAllCommunities*(self: Controller, communityId: string): CommunityDto = + return self.communityService.getCommunityByIdFromAllCommunities(communityId) + proc getMyCommunity*(self: Controller): CommunityDto = - return self.communityService.getCommunityById(self.sectionId) + return self.getCommunityByIdFromAllCommunities(self.sectionId) proc getCommunityById*(self: Controller, communityId: string): CommunityDto = return self.communityService.getCommunityById(communityId) @@ -502,3 +551,25 @@ proc getColorHash*(self: Controller, pubkey: string): ColorHashDto = proc getColorId*(self: Controller, pubkey: string): int = procs_from_visual_identity_service.colorIdOf(pubkey) + +proc createOrEditCommunityTokenPermission*(self: Controller, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + self.communityService.createOrEditCommunityTokenPermission(communityId, tokenPermission) + +proc deleteCommunityTokenPermission*(self: Controller, communityId: string, permissionId: string) = + self.communityService.deleteCommunityTokenPermission(communityId, permissionId) + +proc allAccountsTokenBalance*(self: Controller, symbol: string): float64 = + return self.walletAccountService.allAccountsTokenBalance(symbol) + +proc getTokenList*(self: Controller): seq[TokenDto] = + return self.tokenService.getTokenList() + +proc getContractAddressesForToken*(self: Controller, symbol: string): Table[int, string] = + var contractAddresses = self.tokenService.getContractAddressesForToken(symbol) + let communityToken = self.communityTokensService.getCommunityTokenBySymbol(self.getMySectionId(), symbol) + if communityToken.address != "": + contractAddresses[communityToken.chainId] = communityToken.address + return contractAddresses + +proc getCommunityTokenList*(self: Controller): seq[CommunityTokenDto] = + return self.communityTokensService.getCommunityTokens(self.getMySectionId()) diff --git a/src/app/modules/main/chat_section/io_interface.nim b/src/app/modules/main/chat_section/io_interface.nim index c9c5bb4a80..78ca0b22c9 100644 --- a/src/app/modules/main/chat_section/io_interface.nim +++ b/src/app/modules/main/chat_section/io_interface.nim @@ -8,10 +8,13 @@ import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/gif/service as gif_service import ../../../../app_service/service/mailservers/service as mailservers_service +import ../../../../app_service/service/token/service as token_service import model as chats_model import ../../../core/eventemitter +import ../../shared_models/token_list_item + type AccessInterface* {.pure inheritable.} = ref object of RootObj @@ -325,3 +328,30 @@ method switchToChannel*(self: AccessInterface, channelName: string) = method joinSpectatedCommunity*(self: AccessInterface) = raise newException(ValueError, "No implementation available") + +method createOrEditCommunityTokenPermission*(self: AccessInterface, communityId: string, permissionId: string, permissionType: int, tokenCriteriaJson: string, isPrivate: bool) {.base.} = + raise newException(ValueError, "No implementation available") + +method deleteCommunityTokenPermission*(self: AccessInterface, communityId: string, permissionId: string) {.base.} = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionCreated*(self: AccessInterface, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionCreationFailed*(self: AccessInterface, communityId: string) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionUpdated*(self: AccessInterface, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionUpdateFailed*(self: AccessInterface, communityId: string) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionDeleted*(self: AccessInterface, communityId: string, permissionId: string) = + raise newException(ValueError, "No implementation available") + +method onCommunityTokenPermissionDeletionFailed*(self: AccessInterface, communityId: string) = + raise newException(ValueError, "No implementation available") + +method onWalletAccountTokensRebuilt*(self: AccessInterface) = + raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/chat_section/module.nim b/src/app/modules/main/chat_section/module.nim index 57c40cbb65..6df419914a 100644 --- a/src/app/modules/main/chat_section/module.nim +++ b/src/app/modules/main/chat_section/module.nim @@ -7,6 +7,11 @@ import model as chats_model import item as chat_item import ../../shared_models/user_item as user_item import ../../shared_models/user_model as user_model +import ../../shared_models/token_permissions_model +import ../../shared_models/token_permission_item +import ../../shared_models/token_criteria_item +import ../../shared_models/token_criteria_model +import ../../shared_models/token_list_item import chat_content/module as chat_content_module import chat_content/users/module as users_module @@ -24,8 +29,12 @@ import ../../../../app_service/service/community/service as community_service import ../../../../app_service/service/message/service as message_service import ../../../../app_service/service/mailservers/service as mailservers_service import ../../../../app_service/service/gif/service as gif_service +import ../../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../../app_service/service/token/service as token_service +import ../../../../app_service/service/community_tokens/service as community_tokens_service import ../../../../app_service/service/visual_identity/service as visual_identity import ../../../../app_service/service/contacts/dto/contacts as contacts_dto +import ../../../../app_service/service/community/dto/community as community_dto export io_interface @@ -55,6 +64,8 @@ proc buildChatSectionUI(self: Module, gifService: gif_service.Service, mailserversService: mailservers_service.Service) +proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPermissionDto): TokenPermissionItem + proc newModule*( delegate: delegate_interface.AccessInterface, events: EventEmitter, @@ -68,12 +79,15 @@ proc newModule*( communityService: community_service.Service, messageService: message_service.Service, gifService: gif_service.Service, - mailserversService: mailservers_service.Service + mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service ): Module = result = Module() result.delegate = delegate result.controller = controller.newController(result, sectionId, isCommunity, events, settingsService, nodeConfigurationService, - contactService, chatService, communityService, messageService, gifService, mailserversService) + contactService, chatService, communityService, messageService, gifService, mailserversService, walletAccountService, tokenService, communityTokensService) result.view = view.newView(result) result.viewVariant = newQVariant(result.view) result.moduleLoaded = false @@ -288,6 +302,67 @@ proc initContactRequestsModel(self: Module) = self.view.contactRequestsModel().addItems(contactsWhoAddedMe) +proc rebuildCommunityTokenPermissionsModel(self: Module) = + let community = self.controller.getMyCommunity() + var tokenPermissionsItems: seq[TokenPermissionItem] = @[] + var allTokenRequirementsMet = false + + for id, tokenPermission in community.tokenPermissions: + # TODO: for startes we only deal with "become member" permissions + if tokenPermission.`type` == TokenPermissionType.BecomeMember: + let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission) + + # multiple permissions of the same type act as logical OR + # so if at least one of them is fulfilled we can mark the view + # as all lights green + if tokenPermissionItem.tokenCriteriaMet: + allTokenRequirementsMet = true + + tokenPermissionsItems.add(tokenPermissionItem) + + self.view.tokenPermissionsModel().setItems(tokenPermissionsItems) + self.view.setAllTokenRequirementsMet(allTokenRequirementsMet) + +proc initCommunityTokenPermissionsModel(self: Module) = + self.rebuildCommunityTokenPermissionsModel() + +proc buildTokenList(self: Module) = + + var tokenListItems: seq[TokenListItem] + var collectiblesListItems: seq[TokenListItem] + + let erc20Tokens = self.controller.getTokenList() + let communityTokens = self.controller.getCommunityTokenList() + + for token in erc20Tokens: + let tokenListItem = initTokenListItem( + key = token.symbol, + name = token.name, + symbol = token.symbol, + color = token.color, + image = "", + category = ord(TokenListItemCategory.General) + ) + + tokenListItems.add(tokenListItem) + + for token in communityTokens: + let tokenListItem = initTokenListItem( + key = token.symbol, + name = token.name, + symbol = token.symbol, + color = "", # community tokens don't have `color` + image = token.image, + category = ord(TokenListItemCategory.Community) + ) + collectiblesListItems.add(tokenListItem) + + self.view.setTokenListItems(tokenListItems) + self.view.setCollectiblesListItems(collectiblesListItems) + +method onWalletAccountTokensRebuilt*(self: Module) = + self.rebuildCommunityTokenPermissionsModel() + proc convertPubKeysToJson(self: Module, pubKeys: string): seq[string] = return map(parseJson(pubKeys).getElems(), proc(x:JsonNode):string = x.getStr) @@ -327,6 +402,8 @@ method load*( self.initContactRequestsModel() else: self.usersModule.load() + self.initCommunityTokenPermissionsModel() + self.buildTokenList() let activeChatId = self.controller.getActiveChatId() let isCurrentSectionActive = self.controller.getIsCurrentSectionActive() @@ -664,6 +741,49 @@ method onChatMuted*(self: Module, chatId: string) = method onChatUnmuted*(self: Module, chatId: string) = self.view.chatsModel().changeMutedOnItemById(chatId, muted=false) +method onCommunityTokenPermissionDeleted*(self: Module, communityId: string, permissionId: string) = + self.view.tokenPermissionsModel().removeItemWithId(permissionId) + singletonInstance.globalEvents.showCommunityTokenPermissionDeletedNotification(communityId, "Community permission deleted", "A token permission has been removed") + +method onCommunityTokenPermissionCreated*(self: Module, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + if tokenPermission.`type` == TokenPermissionType.BecomeMember: + let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission) + self.view.tokenPermissionsModel.addItem(tokenPermissionItem) + if tokenPermissionItem.tokenCriteriaMet: + self.view.setAllTokenRequirementsMet(true) + singletonInstance.globalEvents.showCommunityTokenPermissionCreatedNotification(communityId, "Community permission created", "A token permission has been added") + +method onCommunityTokenPermissionUpdated*(self: Module, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + if tokenPermission.`type` == TokenPermissionType.BecomeMember: + let tokenPermissionItem = self.buildTokenPermissionItem(tokenPermission) + self.view.tokenPermissionsModel.updateItem(tokenPermission.id, tokenPermissionItem) + if tokenPermissionItem.tokenCriteriaMet: + self.view.setAllTokenRequirementsMet(true) + return + + # we now need to check whether any other permission criteria where met. + let community = self.controller.getMyCommunity() + for id, permission in community.tokenPermissions: + if id != tokenPermission.id: + for tc in permission.tokenCriteria: + let balance = self.controller.allAccountsTokenBalance(tc.symbol) + let amount = tc.amount.parseFloat + let tokenCriteriaMet = balance >= amount + if tokenCriteriaMet: + return + + self.view.setAllTokenRequirementsMet(false) + singletonInstance.globalEvents.showCommunityTokenPermissionUpdatedNotification(communityId, "Community permission updated", "A token permission has been updated") + +method onCommunityTokenPermissionCreationFailed*(self: Module, communityId: string) = + singletonInstance.globalEvents.showCommunityTokenPermissionCreationFailedNotification(communityId, "Failed to create community permission", "Something went wrong") + +method onCommunityTokenPermissionUpdateFailed*(self: Module, communityId: string) = + singletonInstance.globalEvents.showCommunityTokenPermissionUpdateFailedNotification(communityId, "Failed to update community permission", "Something went wrong") + +method onCommunityTokenPermissionDeletionFailed*(self: Module, communityId: string) = + singletonInstance.globalEvents.showCommunityTokenPermissionDeletionFailedNotification(communityId, "Failed to delete community permission", "Something went wrong") + method onMarkAllMessagesRead*(self: Module, chatId: string) = self.updateBadgeNotifications(chatId, hasUnreadMessages=false, unviewedMentionsCount=0) let chatDetails = self.controller.getChatDetails(chatId) @@ -995,3 +1115,65 @@ method contactsStatusUpdated*(self: Module, statusUpdates: seq[StatusUpdateDto]) method joinSpectatedCommunity*(self: Module) = if self.usersModule != nil: self.usersModule.updateMembersList() + +method createOrEditCommunityTokenPermission*(self: Module, communityId: string, permissionId: string, permissionType: int, tokenCriteriaJson: string, isPrivate: bool) = + var tokenPermission = CommunityTokenPermissionDto() + tokenPermission.id = permissionId + tokenPermission.isPrivate = isPrivate + tokenPermission.`type` = TokenPermissionType(permissionType) + + let tokenCriteriaJsonObj = tokenCriteriaJson.parseJson + for tokenCriteria in tokenCriteriaJsonObj: + + let viewAmount = tokenCriteria{"amount"}.getFloat + var tokenCriteriaDto = tokenCriteria.toTokenCriteriaDto + let contractAddresses = self.controller.getContractAddressesForToken(tokenCriteriaDto.symbol) + if contractAddresses.len == 0 and tokenCriteriaDto.`type` != TokenCriteriaType.ENS: + if permissionId == "": + self.onCommunityTokenPermissionCreationFailed(communityId) + return + self.onCommunityTokenPermissionUpdateFailed(communityId) + return + + tokenCriteriaDto.amount = viewAmount.formatBiggestFloat(ffDecimal) + tokenCriteriaDto.contractAddresses = contractAddresses + tokenPermission.tokenCriteria.add(tokenCriteriaDto) + + self.controller.createOrEditCommunityTokenPermission(communityId, tokenPermission) + +method deleteCommunityTokenPermission*(self: Module, communityId: string, permissionId: string) = + self.controller.deleteCommunityTokenPermission(communityId, permissionId) + +proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPermissionDto): TokenPermissionItem = + var tokenCriteriaItems: seq[TokenCriteriaItem] = @[] + var allTokenCriteriaMet = true + + for tc in tokenPermission.tokenCriteria: + + let balance = self.controller.allAccountsTokenBalance(tc.symbol) + let amount = tc.amount.parseFloat + let tokenCriteriaMet = balance >= amount + let tokenCriteriaItem = initTokenCriteriaItem( + tc.symbol, + tc.name, + amount, + tc.`type`.int, + tc.ensPattern, + tokenCriteriaMet + ) + if not tokenCriteriaMet: + allTokenCriteriaMet = false + + tokenCriteriaItems.add(tokenCriteriaItem) + + let tokenPermissionItem = initTokenPermissionItem( + tokenPermission.id, + tokenPermission.`type`.int, + tokenCriteriaItems, + @[], # TODO: handle chat list items + tokenPermission.isPrivate, + allTokenCriteriaMet + ) + + return tokenPermissionItem + diff --git a/src/app/modules/main/chat_section/view.nim b/src/app/modules/main/chat_section/view.nim index 2dfde6f6be..fe5d017ca7 100644 --- a/src/app/modules/main/chat_section/view.nim +++ b/src/app/modules/main/chat_section/view.nim @@ -2,6 +2,11 @@ import NimQml, json, sequtils import model as chats_model import item, active_item import ../../shared_models/user_model as user_model +import ../../shared_models/token_permissions_model +import ../../shared_models/token_permission_item +import ../../shared_models/token_list_model +import ../../shared_models/token_list_item +import ../../../../app_service/service/token/dto import io_interface QtObject: @@ -20,6 +25,13 @@ QtObject: editCategoryChannelsModel: chats_model.Model editCategoryChannelsVariant: QVariant loadingHistoryMessagesInProgress: bool + tokenPermissionsModel: TokenPermissionsModel + tokenPermissionsVariant: QVariant + tokenListModel: TokenListModel + tokenListModelVariant: QVariant + collectiblesListModel: TokenListModel + collectiblesListModelVariant: QVariant + allTokenRequirementsMet: bool proc delete*(self: View) = self.model.delete @@ -32,6 +44,13 @@ QtObject: self.listOfMyContactsVariant.delete self.editCategoryChannelsModel.delete self.editCategoryChannelsVariant.delete + self.tokenPermissionsModel.delete + self.tokenPermissionsVariant.delete + self.tokenListModel.delete + self.tokenListModelVariant.delete + self.collectiblesListModel.delete + self.collectiblesListModelVariant.delete + self.QObject.delete proc newView*(delegate: io_interface.AccessInterface): View = @@ -49,6 +68,12 @@ QtObject: result.listOfMyContacts = user_model.newModel() result.listOfMyContactsVariant = newQVariant(result.listOfMyContacts) result.loadingHistoryMessagesInProgress = false + result.tokenPermissionsModel = newTokenPermissionsModel() + result.tokenPermissionsVariant = newQVariant(result.tokenPermissionsModel) + result.tokenListModel = newTokenListModel() + result.tokenListModelVariant = newQVariant(result.tokenListModel) + result.collectiblesListModel = newTokenListModel() + result.collectiblesListModelVariant = newQVariant(result.collectiblesListModel) proc load*(self: View) = self.delegate.viewDidLoad() @@ -316,3 +341,57 @@ QtObject: proc downloadMessages*(self: View, chatId: string, filePath: string) {.slot.} = self.delegate.downloadMessages(chatId, filePath) + + proc tokenListModel*(self: View): TokenListModel = + result = self.tokenListModel + + proc getTokenListModel(self: View): QVariant{.slot.} = + return self.tokenListModelVariant + + QtProperty[QVariant] tokenList: + read = getTokenListModel + + proc setTokenListItems*(self: View, tokenListItems: seq[TokenListItem]) = + self.tokenListModel.setItems(tokenListItems) + + proc collectiblesListModel*(self: View): TokenListModel = + result = self.collectiblesListModel + + proc getCollectiblesListModel(self: View): QVariant{.slot.} = + return self.collectiblesListModelVariant + + QtProperty[QVariant] collectiblesModel: + read = getCollectiblesListModel + + proc setCollectiblesListItems*(self: View, tokenListItems: seq[TokenListItem]) = + self.collectiblesListModel.setItems(tokenListItems) + + proc tokenPermissionsModel*(self: View): TokenPermissionsModel = + result = self.tokenPermissionsModel + + proc getTokenPermissionsModel(self: View): QVariant{.slot.} = + return self.tokenPermissionsVariant + + QtProperty[QVariant] permissionsModel: + read = getTokenPermissionsModel + + proc createOrEditCommunityTokenPermission*(self: View, communityId: string, permissionId: string, permissionType: int, tokenCriteriaJson: string, isPrivate: bool) {.slot.} = + self.delegate.createOrEditCommunityTokenPermission(communityId, permissionId, permissionType, tokenCriteriaJson, isPrivate) + + proc deleteCommunityTokenPermission*(self: View, communityId: string, permissionId: string) {.slot.} = + self.delegate.deleteCommunityTokenPermission(communityId, permissionId) + + proc getAllTokenRequirementsMet*(self: View): bool {.slot.} = + return self.allTokenRequirementsMet + + proc allTokenRequirementsMetChanged*(self: View) {.signal.} + + proc setAllTokenRequirementsMet*(self: View, value: bool) = + if (value == self.allTokenRequirementsMet): + return + self.allTokenRequirementsMet = value + self.allTokenRequirementsMetChanged() + + QtProperty[bool] allTokenRequirementsMet: + read = getAllTokenRequirementsMet + notify = allTokenRequirementsMetChanged diff --git a/src/app/modules/main/controller.nim b/src/app/modules/main/controller.nim index fa4078cd93..7b5b814017 100644 --- a/src/app/modules/main/controller.nim +++ b/src/app/modules/main/controller.nim @@ -18,6 +18,8 @@ import ../../../app_service/service/mailservers/service as mailservers_service import ../../../app_service/service/privacy/service as privacy_service import ../../../app_service/service/node/service as node_service import ../../../app_service/service/community_tokens/service as community_tokens_service +import ../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../app_service/service/token/service as token_service import ../shared_models/section_item, io_interface import ../shared_modules/keycard_popup/io_interface as keycard_shared_module @@ -46,6 +48,8 @@ type communityTokensService: community_tokens_service.Service activeSectionId: string authenticateUserFlowRequestedBy: string + walletAccountService: wallet_account_service.Service + tokenService: token_service.Service # Forward declaration proc setActiveSection*(self: Controller, sectionId: string, skipSavingInSettings: bool = false) @@ -64,6 +68,8 @@ proc newController*(delegate: io_interface.AccessInterface, mailserversService: mailservers_service.Service, nodeService: node_service.Service, communityTokensService: community_tokens_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service ): Controller = result = Controller() @@ -81,6 +87,8 @@ proc newController*(delegate: io_interface.AccessInterface, result.nodeService = nodeService result.mailserversService = mailserversService result.communityTokensService = communityTokensService + result.walletAccountService = walletAccountService + result.tokenService = tokenService proc delete*(self: Controller) = discard @@ -103,6 +111,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService ) self.events.on(SIGNAL_CHATS_LOADING_FAILED) do(e:Args): @@ -135,6 +146,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService, setActive = args.fromUserAction ) @@ -151,6 +165,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService, setActive = args.fromUserAction ) @@ -171,6 +188,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService, setActive = true ) @@ -189,6 +209,9 @@ proc init*(self: Controller) = self.messageService, self.gifService, self.mailserversService, + self.walletAccountService, + self.tokenService, + self.communityTokensService, setActive = false ) diff --git a/src/app/modules/main/io_interface.nim b/src/app/modules/main/io_interface.nim index daec4a1c42..41bb1a8b9b 100644 --- a/src/app/modules/main/io_interface.nim +++ b/src/app/modules/main/io_interface.nim @@ -8,7 +8,10 @@ import ../../../app_service/service/community/service as community_service import ../../../app_service/service/message/service as message_service import ../../../app_service/service/gif/service as gif_service import ../../../app_service/service/mailservers/service as mailservers_service -import ../../../app_service/service/community_tokens/service as token_service +import ../../../app_service/service/community_tokens/service as community_token_service +import ../../../app_service/service/wallet_account/service as wallet_account_service +import ../../../app_service/service/token/service as token_service +import ../../../app_service/service/community_tokens/service as community_tokens_service from ../../../app_service/common/types import StatusType import ../../global/app_signals @@ -80,7 +83,10 @@ method onChatsLoaded*( communityService: community_service.Service, messageService: message_service.Service, gifService: gif_service.Service, - mailserversService: mailservers_service.Service) + mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service) {.base.} = raise newException(ValueError, "No implementation available") @@ -121,6 +127,9 @@ method communityJoined*(self: AccessInterface, community: CommunityDto, events: messageService: message_service.Service, gifService: gif_service.Service, mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service, setActive: bool = false,) {.base.} = raise newException(ValueError, "No implementation available") diff --git a/src/app/modules/main/module.nim b/src/app/modules/main/module.nim index a0083a3282..04a1480f9d 100644 --- a/src/app/modules/main/module.nim +++ b/src/app/modules/main/module.nim @@ -157,7 +157,9 @@ proc newModule*[T]( privacyService, mailserversService, nodeService, - communityTokensService + communityTokensService, + walletAccountService, + tokenService ) result.moduleLoaded = false @@ -512,6 +514,9 @@ method onChatsLoaded*[T]( messageService: message_service.Service, gifService: gif_service.Service, mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service ) = var activeSection: SectionItem var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection() @@ -531,7 +536,10 @@ method onChatsLoaded*[T]( communityService, messageService, gifService, - mailserversService + mailserversService, + walletAccountService, + tokenService, + communityTokensService ) let channelGroupItem = self.createChannelGroupItem(channelGroup) self.view.model().addItem(channelGroupItem) @@ -768,6 +776,9 @@ method communityJoined*[T]( messageService: message_service.Service, gifService: gif_service.Service, mailserversService: mailservers_service.Service, + walletAccountService: wallet_account_service.Service, + tokenService: token_service.Service, + communityTokensService: community_tokens_service.Service, setActive: bool = false, ) = var firstCommunityJoined = false @@ -785,7 +796,10 @@ method communityJoined*[T]( communityService, messageService, gifService, - mailserversService + mailserversService, + walletAccountService, + tokenService, + communityTokensService ) let channelGroup = community.toChannelGroupDto() self.channelGroupModules[community.id].load(channelGroup, events, settingsService, nodeConfigurationService, contactsService, @@ -960,6 +974,12 @@ method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle: string, details: NotificationDetails) = if(details.notificationType == NotificationType.NewMessage or details.notificationType == NotificationType.NewMessageWithPersonalMention or + details.notificationType == NotificationType.CommunityTokenPermissionCreated or + details.notificationType == NotificationType.CommunityTokenPermissionUpdated or + details.notificationType == NotificationType.CommunityTokenPermissionDeleted or + details.notificationType == NotificationType.CommunityTokenPermissionCreationFailed or + details.notificationType == NotificationType.CommunityTokenPermissionUpdateFailed or + details.notificationType == NotificationType.CommunityTokenPermissionDeletionFailed or details.notificationType == NotificationType.NewMessageWithGlobalMention): self.displayEphemeralNotification(title, subTitle, "", false, EphemeralNotificationType.Default.int, "", details) diff --git a/src/app/modules/shared_models/token_criteria_item.nim b/src/app/modules/shared_models/token_criteria_item.nim new file mode 100644 index 0000000000..04cf30d547 --- /dev/null +++ b/src/app/modules/shared_models/token_criteria_item.nim @@ -0,0 +1,53 @@ +import strformat + +type + TokenCriteriaItem* = object + symbol*: string + name*: string + amount*: float64 + `type`*: int + ensPattern*: string + criteriaMet*: bool + +proc initTokenCriteriaItem*( + symbol: string, + name: string, + amount: float64, + `type`: int, + ensPattern: string, + criteriaMet: bool +): TokenCriteriaItem = + result.symbol = symbol + result.name = name + result.`type` = `type` + result.ensPattern = ensPattern + result.amount = amount + result.criteriaMet = criteriaMet + +proc `$`*(self: TokenCriteriaItem): string = + result = fmt"""TokenCriteriaItem( + symbol: {self.symbol}, + name: {self.name}, + amount: {self.amount}, + type: {self.type}, + ensPattern: {self.ensPattern}, + criteriaMet: {self.criteriaMet} + ]""" + +proc getType*(self: TokenCriteriaItem): int = + return self.`type` + +proc getSymbol*(self: TokenCriteriaItem): string = + return self.symbol + +proc getName*(self: TokenCriteriaItem): string = + return self.name + +proc getAmount*(self: TokenCriteriaItem): float64 = + return self.amount + +proc getEnsPattern*(self: TokenCriteriaItem): string = + return self.ensPattern + +proc getCriteriaMet*(self: TokenCriteriaItem): bool = + return self.criteriaMet diff --git a/src/app/modules/shared_models/token_criteria_model.nim b/src/app/modules/shared_models/token_criteria_model.nim new file mode 100644 index 0000000000..334fd71ff4 --- /dev/null +++ b/src/app/modules/shared_models/token_criteria_model.nim @@ -0,0 +1,87 @@ +import NimQml, Tables +import token_criteria_item +import ../../../app_service/service/community/dto/community + +type + ModelRole {.pure.} = enum + Key = UserRole + 1 + Type + Symbol + ShortName + Name + Amount + CriteriaMet + +QtObject: + type TokenCriteriaModel* = ref object of QAbstractListModel + items*: seq[TokenCriteriaItem] + + proc setup(self: TokenCriteriaModel) = + self.QAbstractListModel.setup + + proc delete(self: TokenCriteriaModel) = + self.items = @[] + self.QAbstractListModel.delete + + proc newTokenCriteriaModel*(): TokenCriteriaModel = + new(result, delete) + result.setup + + method roleNames(self: TokenCriteriaModel): Table[int, string] = + { + ModelRole.Key.int:"key", + ModelRole.Type.int:"type", + ModelRole.Symbol.int:"symbol", + ModelRole.ShortName.int:"shortName", + ModelRole.Name.int:"name", + ModelRole.Amount.int:"amount", + ModelRole.CriteriaMet.int:"available", + }.toTable + + proc countChanged(self: TokenCriteriaModel) {.signal.} + proc getCount(self: TokenCriteriaModel): int {.slot.} = + self.items.len + QtProperty[int] count: + read = getCount + notify = countChanged + + method rowCount(self: TokenCriteriaModel, index: QModelIndex = nil): int = + return self.items.len + + method data(self: TokenCriteriaModel, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Key: + + if item.getType() == ord(TokenCriteriaType.ENS): + result = newQVariant(item.getEnsPattern()) + else: + result = newQVariant(item.getSymbol()) + of ModelRole.Type: + result = newQVariant(item.getType()) + of ModelRole.Symbol: + result = newQVariant(item.getSymbol()) + of ModelRole.ShortName: + result = newQVariant(item.getSymbol()) + of ModelRole.Name: + result = newQVariant(item.getSymbol()) + of ModelRole.Amount: + result = newQVariant(item.getAmount()) + of ModelRole.CriteriaMet: + result = newQVariant(item.getCriteriaMet()) + + proc getItems*(self: TokenCriteriaModel): seq[TokenCriteriaItem] = + return self.items + + proc addItem*(self: TokenCriteriaModel, item: TokenCriteriaItem) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + self.beginInsertRows(parentModelIndex, self.items.len, self.items.len) + self.items.add(item) + self.endInsertRows() + self.countChanged() diff --git a/src/app/modules/shared_models/token_list_item.nim b/src/app/modules/shared_models/token_list_item.nim new file mode 100644 index 0000000000..22a452ddfb --- /dev/null +++ b/src/app/modules/shared_models/token_list_item.nim @@ -0,0 +1,56 @@ +import strformat + +type TokenListItemCategory* {.pure.}= enum + Community = 0, + Own = 1, + General = 2 + +type + TokenListItem* = object + key*: string + name*: string + symbol*: string + color*: string + image*: string + category*: int + +proc initTokenListItem*( + key: string, + name: string, + symbol: string, + color: string, + image: string, + category: int +): TokenListItem = + result.key = key + result.symbol = symbol + result.name = name + result.color = color + result.image = image + result.category = category + +proc `$`*(self: TokenListItem): string = + result = fmt"""TokenListItem( + key: {self.key}, + name: {self.name}, + color: {self.color}, + symbol: {self.symbol} + ]""" + +proc getKey*(self: TokenListItem): string = + return self.key + +proc getSymbol*(self: TokenListItem): string = + return self.symbol + +proc getName*(self: TokenListItem): string = + return self.name + +proc getColor*(self: TokenListItem): string = + return self.color + +proc getImage*(self: TokenListItem): string = + return self.image + +proc getCategory*(self: TokenListItem): int = + return self.category diff --git a/src/app/modules/shared_models/token_list_model.nim b/src/app/modules/shared_models/token_list_model.nim new file mode 100644 index 0000000000..ada91c330d --- /dev/null +++ b/src/app/modules/shared_models/token_list_model.nim @@ -0,0 +1,92 @@ +import NimQml, Tables +import token_list_item + +type + ModelRole {.pure.} = enum + Key = UserRole + 1 + Name + Shortname + Symbol + Color + Image + Category + +QtObject: + type TokenListModel* = ref object of QAbstractListModel + items*: seq[TokenListItem] + + proc setup(self: TokenListModel) = + self.QAbstractListModel.setup + + proc delete(self: TokenListModel) = + self.items = @[] + self.QAbstractListModel.delete + + proc newTokenListModel*(): TokenListModel = + new(result, delete) + result.setup + + proc countChanged(self: TokenListModel) {.signal.} + + proc getCount(self: TokenListModel): int {.slot.} = + self.items.len + + QtProperty[int] count: + read = getCount + notify = countChanged + + proc setItems*(self: TokenlistModel, items: seq[TokenListItem]) = + self.beginResetModel() + self.items = items + self.endResetModel() + self.countChanged() + + proc addItems*(self: TokenlistModel, items: seq[TokenListItem]) = + if(items.len == 0): + return + + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + let first = self.items.len + let last = first + items.len - 1 + self.beginInsertRows(parentModelIndex, first, last) + self.items.add(items) + self.endInsertRows() + self.countChanged() + + method roleNames(self: TokenListModel): Table[int, string] = + { + ModelRole.Key.int:"key", + ModelRole.Name.int:"name", + ModelRole.Symbol.int:"symbol", + ModelRole.Shortname.int:"shortName", + ModelRole.Color.int:"color", + ModelRole.Image.int:"iconSource", + ModelRole.Category.int:"category", + }.toTable + + method rowCount(self: TokenlistModel, index: QModelIndex = nil): int = + return self.items.len + + method data(self: TokenListModel, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Key: + result = newQVariant(item.getKey()) + of ModelRole.Name: + result = newQVariant(item.getName()) + of ModelRole.Symbol: + result = newQVariant(item.getSymbol()) + of ModelRole.Shortname: + result = newQVariant(item.getSymbol()) + of ModelRole.Color: + result = newQVariant(item.getColor()) + of ModelRole.Image: + result = newQVariant(item.getImage()) + of ModelRole.Category: + result = newQVariant(item.getCategory()) diff --git a/src/app/modules/shared_models/token_permission_chat_list_item.nim b/src/app/modules/shared_models/token_permission_chat_list_item.nim new file mode 100644 index 0000000000..0eb4f362b5 --- /dev/null +++ b/src/app/modules/shared_models/token_permission_chat_list_item.nim @@ -0,0 +1,25 @@ +import strformat + +type + TokenPermissionChatListItem* = object + key: string + name: string + +proc `$`*(self: TokenPermissionChatListItem): string = + result = fmt"""TokenPermissionChatListItem( + key: {self.key}, + name: {self.name}, + ]""" + +proc initTokenPermissionChatListItem*( + key: string, + name: string, +): TokenPermissionChatListItem = + result.key = key + result.name = name + +proc getKey*(self: TokenPermissionChatListItem): string = + return self.key + +proc getName*(self: TokenPermissionChatListItem): string = + return self.name diff --git a/src/app/modules/shared_models/token_permission_chat_list_model.nim b/src/app/modules/shared_models/token_permission_chat_list_model.nim new file mode 100644 index 0000000000..def062502b --- /dev/null +++ b/src/app/modules/shared_models/token_permission_chat_list_model.nim @@ -0,0 +1,59 @@ +import NimQml, Tables +import token_permission_chat_list_item + +type + ModelRole {.pure.} = enum + Key = UserRole + 1 + Name + +QtObject: + type TokenPermissionChatListModel* = ref object of QAbstractListModel + items*: seq[TokenPermissionChatListItem] + + proc setup(self: TokenPermissionChatListModel) = + self.QAbstractListModel.setup + + proc delete(self: TokenPermissionChatListModel) = + self.items = @[] + self.QAbstractListModel.delete + + proc newTokenPermissionChatListModel*(): TokenPermissionChatListModel = + new(result, delete) + result.setup + + method roleNames(self: TokenPermissionChatListModel): Table[int, string] = + { + ModelRole.Key.int:"key", + ModelRole.Name.int:"name", + }.toTable + + proc countChanged(self: TokenPermissionChatListModel) {.signal.} + proc getCount(self: TokenPermissionChatListModel): int {.slot.} = + self.items.len + QtProperty[int] count: + read = getCount + notify = countChanged + + method rowCount(self: TokenPermissionChatListModel, index: QModelIndex = nil): int = + return self.items.len + + method data(self: TokenPermissionChatListModel, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Key: + result = newQVariant(item.getKey()) + of ModelRole.Name: + result = newQVariant(item.getName()) + + proc addItem*(self: TokenPermissionChatListModel, item: TokenPermissionChatListItem) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + self.beginInsertRows(parentModelIndex, self.items.len, self.items.len) + self.items.add(item) + self.endInsertRows() + self.countChanged() diff --git a/src/app/modules/shared_models/token_permission_item.nim b/src/app/modules/shared_models/token_permission_item.nim new file mode 100644 index 0000000000..be325f080b --- /dev/null +++ b/src/app/modules/shared_models/token_permission_item.nim @@ -0,0 +1,64 @@ +import strformat +import ../../../app_service/service/community/dto/community +import ../../../app_service/service/chat/dto/chat +import token_criteria_model +import token_criteria_item +import token_permission_chat_list_model +import token_permission_chat_list_item + +type + TokenPermissionItem* = object + id*: string + `type`*: int + tokenCriteria*: TokenCriteriaModel + chatList*: TokenPermissionChatListModel + isPrivate*: bool + tokenCriteriaMet*: bool + +proc `$`*(self: TokenPermissionItem): string = + result = fmt"""TokenPermissionItem( + id: {self.id}, + type: {self.type}, + ]""" + +proc initTokenPermissionItem*( + id: string, + `type`: int, + tokenCriteria: seq[TokenCriteriaItem], + chatList: seq[TokenPermissionChatListItem], + isPrivate: bool, + tokenCriteriaMet: bool +): TokenPermissionItem = + result.id = id + result.`type` = `type` + result.tokenCriteria = newTokenCriteriaModel() + result.chatList = newTokenPermissionChatListModel() + result.isPrivate = isPrivate + result.tokenCriteriaMet = tokenCriteriaMet + + for tcItem in tokenCriteria: + result.tokenCriteria.addItem(tcItem) + + for clItem in chatList: + result.chatList.addItem(clItem) + +proc addTokenCriteria*(self: TokenPermissionItem, tokenCriteria: TokenCriteriaItem) = + self.tokenCriteria.addItem(tokenCriteria) + +proc getId*(self: TokenPermissionItem): string = + return self.id + +proc getType*(self: TokenPermissionItem): int = + return self.`type` + +proc getTokenCriteria*(self: TokenPermissionItem): TokenCriteriaModel = + return self.tokenCriteria + +proc getChatList*(self: TokenPermissionItem): TokenPermissionChatListModel = + return self.chatList + +proc getIsPrivate*(self: TokenPermissionItem): bool = + return self.isPrivate + +proc getTokenCriteriaMet*(self: TokenPermissionItem): bool = + return self.tokenCriteriaMet diff --git a/src/app/modules/shared_models/token_permissions_model.nim b/src/app/modules/shared_models/token_permissions_model.nim new file mode 100644 index 0000000000..f47f5cd63e --- /dev/null +++ b/src/app/modules/shared_models/token_permissions_model.nim @@ -0,0 +1,123 @@ +import NimQml, Tables +import token_permission_item + +type + ModelRole {.pure.} = enum + Id = UserRole + 1 + Key + Type + TokenCriteria + ChatList + IsPrivate + +QtObject: + type TokenPermissionsModel* = ref object of QAbstractListModel + items*: seq[TokenPermissionItem] + + proc setup(self: TokenPermissionsModel) = + self.QAbstractListModel.setup + + proc delete(self: TokenPermissionsModel) = + self.items = @[] + self.QAbstractListModel.delete + + proc newTokenPermissionsModel*(): TokenPermissionsModel = + new(result, delete) + result.setup + + method roleNames(self: TokenPermissionsModel): Table[int, string] = + { + ModelRole.Id.int:"id", + ModelRole.Key.int:"key", + ModelRole.Type.int:"permissionType", + ModelRole.TokenCriteria.int:"holdingsListModel", + ModelRole.ChatList.int:"channelsModel", + ModelRole.IsPrivate.int:"isPrivate", + }.toTable + + proc countChanged(self: TokenPermissionsModel) {.signal.} + proc getCount(self: TokenPermissionsModel): int {.slot.} = + self.items.len + QtProperty[int] count: + read = getCount + notify = countChanged + + method rowCount(self: TokenPermissionsModel, index: QModelIndex = nil): int = + return self.items.len + + method data(self: TokenPermissionsModel, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + if index.row < 0 or index.row >= self.items.len: + return + let item = self.items[index.row] + let enumRole = role.ModelRole + case enumRole: + of ModelRole.Id: + result = newQVariant(item.getId()) + of ModelRole.Key: + result = newQVariant(item.getId()) + of ModelRole.Type: + result = newQVariant(item.getType()) + of ModelRole.TokenCriteria: + result = newQVariant(item.getTokenCriteria()) + of ModelRole.ChatList: + result = newQVariant(item.getChatList()) + of ModelRole.IsPrivate: + result = newQVariant(item.getIsPrivate()) + + proc addItem*(self: TokenPermissionsModel, item: TokenPermissionItem) = + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + self.beginInsertRows(parentModelIndex, self.items.len, self.items.len) + self.items.add(item) + self.endInsertRows() + self.countChanged() + + proc setItems*(self: TokenPermissionsModel, items: seq[TokenPermissionItem]) = + self.beginResetModel() + self.items = items + self.endResetModel() + self.countChanged() + + proc getItems*(self: TokenPermissionsModel): seq[TokenPermissionItem] = + return self.items + + proc findIndexById(self: TokenPermissionsModel, id: string): int = + for i in 0 ..< self.items.len: + if(self.items[i].getId() == id): + return i + return -1 + + proc removeItemWithId*(self: TokenPermissionsModel, permissionId: string) = + let idx = self.findIndexById(permissionId) + if(idx == -1): + return + + let parentModelIndex = newQModelIndex() + defer: parentModelIndex.delete + + self.beginRemoveRows(parentModelIndex, idx, idx) + self.items.delete(idx) + self.endRemoveRows() + self.countChanged() + + proc updateItem*(self: TokenPermissionsModel, permissionId: string, item: TokenPermissionItem) = + let idx = self.findIndexById(permissionId) + if(idx == -1): + return + + self.items[idx].id = permissionId + self.items[idx].`type` = item.`type` + self.items[idx].tokenCriteria = item.tokenCriteria + self.items[idx].isPrivate = item.isPrivate + + let index = self.createIndex(idx, 0, nil) + self.dataChanged(index, index, @[ + ModelRole.Id.int, + ModelRole.Key.int, + ModelRole.Type.int, + ModelRole.TokenCriteria.int, + ModelRole.IsPrivate.int + ]) + diff --git a/src/app_service/service/community/dto/community.nim b/src/app_service/service/community/dto/community.nim index 82a3a1bf1b..4036883dba 100644 --- a/src/app_service/service/community/dto/community.nim +++ b/src/app_service/service/community/dto/community.nim @@ -1,6 +1,6 @@ {.used.} -import json, sequtils, sugar, tables +import json, sequtils, sugar, tables, strutils, json_serialization import ../../../../backend/communities include ../../../common/json_utils @@ -29,6 +29,34 @@ type CommunitySettingsDto* = object type CommunityAdminSettingsDto* = object pinMessageAllMembersEnabled*: bool +type TokenPermissionType* {.pure.}= enum + Unknown = 0, + BecomeAdmin = 1, + BecomeMember = 2 + +type TokenCriteriaType* {.pure.}= enum + Unknown = 0, + ERC20 = 1, + ERC721 = 2, + ENS = 3 + +type TokenCriteriaDto* = object + contractAddresses* {.serializedFieldName("contract_addresses").}: Table[int, string] + `type`* {.serializedFieldName("type").}: TokenCriteriaType + symbol* {.serializedFieldName("symbol").}: string + name* {.serializedFieldName("name").}: string + amount* {.serializedFieldName("amount").}: string + decimals* {.serializedFieldName("decimals").}: int + tokenIds* {.serializedFieldName("tokenIds").}: seq[string] + ensPattern* {.serializedFieldName("ens_pattern").}: string + +type CommunityTokenPermissionDto* = object + id*: string + `type`*: TokenPermissionType + tokenCriteria*: seq[TokenCriteriaDto] + chatIds*: seq[string] + isPrivate*: bool + type CommunityDto* = object id*: string admin*: bool @@ -60,6 +88,7 @@ type CommunityDto* = object declinedRequestsToJoin*: seq[CommunityMembershipRequestDto] encrypted*: bool canceledRequestsToJoin*: seq[CommunityMembershipRequestDto] + tokenPermissions*: Table[string, CommunityTokenPermissionDto] type CuratedCommunity* = object available*: bool @@ -132,6 +161,62 @@ proc toDiscordImportTaskProgress*(jsonObj: JsonNode): DiscordImportTaskProgress let importError = error.toDiscordImportError() result.errors.add(importError) +proc toTokenCriteriaDto*(jsonObj: JsonNode): TokenCriteriaDto = + result = TokenCriteriaDto() + discard jsonObj.getProp("amount", result.amount) + discard jsonObj.getProp("decimals", result.decimals) + discard jsonObj.getProp("symbol", result.symbol) + discard jsonObj.getProp("name", result.name) + discard jsonObj.getProp("ens_pattern", result.ensPattern) + + var typeInt: int + discard jsonObj.getProp("type", typeInt) + if (typeInt >= ord(low(TokenCriteriaType)) and typeInt <= ord(high(TokenCriteriaType))): + result.`type` = TokenCriteriaType(typeInt) + + var contractAddressesObj: JsonNode + if(jsonObj.getProp("contractAddresses", contractAddressesObj) and contractAddressesObj.kind == JObject): + result.contractAddresses = initTable[int, string]() + for chainId, contractAddress in contractAddressesObj: + result.contractAddresses[parseInt(chainId)] = contractAddress.getStr + + var tokenIdsObj: JsonNode + if(jsonObj.getProp("tokenIds", tokenIdsObj) and tokenIdsObj.kind == JArray): + for tokenId in tokenIdsObj: + result.tokenIds.add(tokenId.getStr) + + # When `toTokenCriteriaDto` is called with data coming from + # the front-end, there's a key field we have to account for + if jsonObj.hasKey("key"): + if result.`type` == TokenCriteriaType.ENS: + discard jsonObj.getProp("key", result.ensPattern) + else: + discard jsonObj.getProp("key", result.symbol) + +proc toCommunityTokenPermissionDto*(jsonObj: JsonNode): CommunityTokenPermissionDto = + result = CommunityTokenPermissionDto() + discard jsonObj.getProp("id", result.id) + discard jsonObj.getProp("isPrivate", result.isPrivate) + var tokenPermissionTypeInt: int + discard jsonObj.getProp("type", tokenPermissionTypeInt) + if (tokenPermissionTypeInt >= ord(low(TokenPermissionType)) or tokenPermissionTypeInt <= ord(high(TokenPermissionType))): + result.`type` = TokenPermissionType(tokenPermissionTypeInt) + + var tokenCriteriaObj: JsonNode + if(jsonObj.getProp("token_criteria", tokenCriteriaObj)): + for tokenCriteria in tokenCriteriaObj: + result.tokenCriteria.add(tokenCriteria.toTokenCriteriaDto) + + var chatIdsObj: JsonNode + if(jsonObj.getProp("chatIds", chatIdsObj) and chatIdsObj.kind == JArray): + for chatId in chatIdsObj: + result.chatIds.add(chatId.getStr) + + # When `toTokenPermissionDto` is called with data coming from + # the front-end, there's a key field we have to account for + if jsonObj.hasKey("key"): + discard jsonObj.getProp("key", result.id) + proc toCommunityDto*(jsonObj: JsonNode): CommunityDto = result = CommunityDto() discard jsonObj.getProp("id", result.id) @@ -165,6 +250,12 @@ proc toCommunityDto*(jsonObj: JsonNode): CommunityDto = if(jsonObj.getProp("permissions", permissionObj)): result.permissions = toPermission(permissionObj) + var tokenPermissionsObj: JsonNode + if(jsonObj.getProp("tokenPermissions", tokenPermissionsObj) and tokenPermissionsObj.kind == JObject): + result.tokenPermissions = initTable[string, CommunityTokenPermissionDto]() + for tokenPermissionId, tokenPermission in tokenPermissionsObj: + result.tokenPermissions[tokenPermissionId] = toCommunityTokenPermissionDto(tokenPermission) + var adminSettingsObj: JsonNode if(jsonObj.getProp("adminSettings", adminSettingsObj)): result.adminSettings = toCommunityAdminSettingsDto(adminSettingsObj) diff --git a/src/app_service/service/community/service.nim b/src/app_service/service/community/service.nim index 05744d1ef3..814f08a1aa 100644 --- a/src/app_service/service/community/service.nim +++ b/src/app_service/service/community/service.nim @@ -1,4 +1,5 @@ import NimQml, Tables, json, sequtils, std/sets, std/algorithm, strformat, strutils, chronicles, json_serialization, sugar +import json_serialization/std/tables as ser_tables import ./dto/community as community_dto @@ -84,6 +85,15 @@ type communityId*: string categoryId*: string + CommunityTokenPermissionArgs* = ref object of Args + communityId*: string + tokenPermission*: CommunityTokenPermissionDto + error*: string + + CommunityTokenPermissionRemovedArgs* = ref object of Args + communityId*: string + permissionId*: string + DiscordCategoriesAndChannelsArgs* = ref object of Args categories*: seq[DiscordCategoryDto] channels*: seq[DiscordChannelDto] @@ -140,11 +150,20 @@ const SIGNAL_COMMUNITY_HISTORY_ARCHIVES_DOWNLOAD_FINISHED* = "communityHistoryAr const SIGNAL_DISCORD_CATEGORIES_AND_CHANNELS_EXTRACTED* = "discordCategoriesAndChannelsExtracted" const SIGNAL_DISCORD_COMMUNITY_IMPORT_FINISHED* = "discordCommunityImportFinished" const SIGNAL_DISCORD_COMMUNITY_IMPORT_PROGRESS* = "discordCommunityImportProgress" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED* = "communityTokenPermissionCreated" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATION_FAILED* = "communityTokenPermissionCreationFailed" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED* = "communityTokenPermissionUpdated" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATE_FAILED* = "communityTokenPermissionUpdateFailed" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED* = "communityTokenPermissionDeleted" +const SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETION_FAILED* = "communityTokenPermissionDeletionFailed" const SIGNAL_CURATED_COMMUNITIES_LOADING* = "curatedCommunitiesLoading" const SIGNAL_CURATED_COMMUNITIES_LOADED* = "curatedCommunitiesLoaded" const SIGNAL_CURATED_COMMUNITIES_LOADING_FAILED* = "curatedCommunitiesLoadingFailed" +const TOKEN_PERMISSIONS_ADDED = "tokenPermissionsAdded" +const TOKEN_PERMISSIONS_MODIFIED = "tokenPermissionsModified" + QtObject: type Service* = ref object of QObject @@ -319,6 +338,14 @@ QtObject: return idx return -1 + proc findIndexBySymbol(symbol: string, tokenCriteria: seq[TokenCriteriaDto]): int = + var idx = -1 + for tc in tokenCriteria: + inc idx + if(tc.symbol == symbol): + return idx + return -1 + proc findIndexById(id: string, categories: seq[Category]): int = var idx = -1 for category in categories: @@ -487,6 +514,49 @@ QtObject: self.allCommunities[community.id] = community self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[community])) + # tokenPermission was added + if community.tokenPermissions.len > prev_community.tokenPermissions.len: + for id, tokenPermission in community.tokenPermissions: + if not prev_community.tokenPermissions.hasKey(id): + self.allCommunities[community.id].tokenPermissions[id] = tokenPermission + + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED, + CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: tokenPermission)) + elif community.tokenPermissions.len < prev_community.tokenPermissions.len: + for id, prvTokenPermission in prev_community.tokenPermissions: + if not community.tokenPermissions.hasKey(id): + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED, + CommunityTokenPermissionRemovedArgs(communityId: community.id, permissionId: id)) + + else: + for id, tokenPermission in community.tokenPermissions: + if not prev_community.tokenPermissions.hasKey(id): + continue + + let prevTokenPermission = prev_community.tokenPermissions[id] + + var permissionUpdated = false + + if tokenPermission.tokenCriteria.len != prevTokenPermission.tokenCriteria.len or + tokenPermission.isPrivate != prevTokenPermission.isPrivate or + tokenPermission.`type` != prevTokenPermission.`type`: + + permissionUpdated = true + + for tc in tokenPermission.tokenCriteria: + let index = findIndexBySymbol(tc.symbol, prevTokenPermission.tokenCriteria) + if index == -1: + continue + + let prevTc = prevTokenPermission.tokenCriteria[index] + if tc.amount != prevTc.amount or tc.ensPattern != prevTc.ensPattern: + permissionUpdated = true + break + + if permissionUpdated: + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED, + CommunityTokenPermissionArgs(communityId: community.id, tokenPermission: tokenPermission)) + if(not self.joinedCommunities.hasKey(community.id)): if (community.joined and community.isMember): self.joinedCommunities[community.id] = community @@ -568,6 +638,13 @@ QtObject: proc getCuratedCommunities*(self: Service): seq[CuratedCommunity] = return toSeq(self.curatedCommunities.values) + proc getCommunityByIdFromAllCommunities*(self: Service, communityId: string): CommunityDto = + if(not self.allCommunities.hasKey(communityId)): + error "error: requested community doesn't exists", communityId + return + + return self.allCommunities[communityId] + proc getCommunityById*(self: Service, communityId: string): CommunityDto = if(not self.joinedCommunities.hasKey(communityId)): error "error: requested community doesn't exists", communityId @@ -1557,3 +1634,55 @@ QtObject: except Exception as e: error "Error extracting discord channels and categories", msg = e.msg + proc createOrEditCommunityTokenPermission*(self: Service, communityId: string, tokenPermission: CommunityTokenPermissionDto) = + try: + let editing = tokenPermission.id != "" + + var response: RpcResponse[JsonNode] + + if editing: + response = status_go.editCommunityTokenPermission(communityId, tokenPermission.id, int(tokenPermission.`type`), Json.encode(tokenPermission.tokenCriteria), tokenPermission.isPrivate) + else: + response = status_go.createCommunityTokenPermission(communityId, int(tokenPermission.`type`), Json.encode(tokenPermission.tokenCriteria), tokenPermission.isPrivate) + + if response.result != nil and response.result.kind != JNull: + var changesField = TOKEN_PERMISSIONS_ADDED + if editing: + changesField = TOKEN_PERMISSIONS_MODIFIED + + for permissionId, permission in response.result["communityChanges"].getElems()[0][changesField].pairs(): + let p = permission.toCommunityTokenPermissionDto() + self.allCommunities[communityId].tokenPermissions[permissionId] = p + self.joinedCommunities[communityId].tokenPermissions[permissionId] = p + + var signal = SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATED + if editing: + signal = SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATED + + self.events.emit(signal, CommunityTokenPermissionArgs(communityId: communityId, tokenPermission: p)) + return + + var signal = SIGNAL_COMMUNITY_TOKEN_PERMISSION_CREATION_FAILED + if editing: + signal = SIGNAL_COMMUNITY_TOKEN_PERMISSION_UPDATE_FAILED + + self.events.emit(signal, CommunityTokenPermissionArgs(communityId: communityId, tokenPermission: tokenPermission)) + except Exception as e: + error "Error creating/editing community token permission", msg = e.msg + + proc deleteCommunityTokenPermission*(self: Service, communityId: string, permissionId: string) = + try: + let response = status_go.deleteCommunityTokenPermission(communityId, permissionId) + if response.result != nil and response.result.kind != JNull: + for permissionId in response.result["communityChanges"].getElems()[0]["tokenPermissionsRemoved"].getElems(): + if self.allCommunities[communityId].tokenPermissions.hasKey(permissionId.getStr()): + self.allCommunities[communityId].tokenPermissions.del(permissionId.getStr()) + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETED, + CommunityTokenPermissionRemovedArgs(communityId: communityId, permissionId: permissionId.getStr)) + return + var tokenPermission = CommunityTokenPermissionDto() + tokenPermission.id = permissionId + self.events.emit(SIGNAL_COMMUNITY_TOKEN_PERMISSION_DELETION_FAILED, + CommunityTokenPermissionArgs(communityId: communityId, tokenPermission: tokenPermission)) + except Exception as e: + error "Error deleting community token permission", msg = e.msg diff --git a/src/app_service/service/community_tokens/service.nim b/src/app_service/service/community_tokens/service.nim index e5a7ed652e..15e77a079d 100644 --- a/src/app_service/service/community_tokens/service.nim +++ b/src/app_service/service/community_tokens/service.nim @@ -125,3 +125,10 @@ QtObject: return parseCommunityTokens(response) except RpcException: error "Error getting community tokens", message = getCurrentExceptionMsg() + + proc getCommunityTokenBySymbol*(self: Service, communityId: string, symbol: string): CommunityTokenDto = + let communityTokens = self.getCommunityTokens(communityId) + for token in communityTokens: + if token.symbol == symbol: + return token + diff --git a/src/app_service/service/token/service.nim b/src/app_service/service/token/service.nim index 5bcdceae1e..a5a9d71c83 100644 --- a/src/app_service/service/token/service.nim +++ b/src/app_service/service/token/service.nim @@ -47,6 +47,8 @@ QtObject: threadpool: ThreadPool networkService: network_service.Service tokens: Table[int, seq[TokenDto]] + tokenList: seq[TokenDto] + tokensToAddressesMap: Table[string, Table[int, string]] priceCache: TimedCache[float64] proc updateCachedTokenPrice(self: Service, crypto: string, fiat: string, price: float64) @@ -67,6 +69,8 @@ QtObject: result.networkService = networkService result.tokens = initTable[int, seq[TokenDto]]() result.priceCache = newTimedCache[float64]() + result.tokenList = @[] + result.tokensToAddressesMap = initTable[string, Table[int, string]]() proc loadData*(self: Service) = if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()): @@ -92,6 +96,32 @@ QtObject: self.tokens[network.chainId] = default_tokens.filter( proc(x: TokenDto): bool = x.chainId == network.chainId ) + + let nativeToken = newTokenDto( + name = network.nativeCurrencyName, + chainId = network.chainId, + address = Address.fromHex("0x0000000000000000000000000000000000000000"), + symbol = network.nativeCurrencySymbol, + decimals = network.nativeCurrencyDecimals, + hasIcon = false, + pegSymbol = "" + ) + + if not self.tokensToAddressesMap.hasKey(network.nativeCurrencySymbol): + self.tokenList.add(nativeToken) + self.tokensToAddressesMap[nativeToken.symbol] = initTable[int, string]() + + if not self.tokensToAddressesMap[nativeToken.symbol].hasKey(nativeToken.chainId): + self.tokensToAddressesMap[nativeToken.symbol][nativeToken.chainId] = $nativeToken.address + + for token in default_tokens: + if not self.tokensToAddressesMap.hasKey(token.symbol): + self.tokenList.add(token) + self.tokensToAddressesMap[token.symbol] = initTable[int, string]() + + if not self.tokensToAddressesMap[token.symbol].hasKey(token.chainId): + self.tokensToAddressesMap[token.symbol][token.chainId] = $token.address + except Exception as e: error "Tokens init error", errDesription = e.msg @@ -99,6 +129,16 @@ QtObject: signalConnect(singletonInstance.localAccountSensitiveSettings, "isWalletEnabledChanged()", self, "onIsWalletEnabledChanged()", 2) self.loadData() + proc getTokenList*(self: Service): seq[TokenDto] = + return self.tokenList + + proc hasContractAddressesForToken*(self: Service, symbol: string): bool = + return self.tokensToAddressesMap.hasKey(symbol) + + proc getContractAddressesForToken*(self: Service, symbol: string): Table[int, string] = + if self.hasContractAddressesForToken(symbol): + return self.tokensToAddressesMap[symbol] + proc onIsWalletEnabledChanged*(self: Service) {.slot.} = self.loadData() diff --git a/src/app_service/service/wallet_account/dto.nim b/src/app_service/service/wallet_account/dto.nim index 41dc93fbcb..8dca054482 100644 --- a/src/app_service/service/wallet_account/dto.nim +++ b/src/app_service/service/wallet_account/dto.nim @@ -147,6 +147,13 @@ proc getAddress*(self: WalletTokenDto): string = return "" +proc getTotalBalanceOfSupportedChains*(self: WalletTokenDto): float64 = + var sum = 0.0 + for chainId, balanceDto in self.balancesPerChain: + sum += balanceDto.balance + + return sum + proc getBalances*(self: WalletTokenDto, chainIds: seq[int]): seq[BalanceDto] = for chainId in chainIds: if self.balancesPerChain.hasKey(chainId): diff --git a/src/app_service/service/wallet_account/service.nim b/src/app_service/service/wallet_account/service.nim index 564cfd378f..7c2af84ff2 100644 --- a/src/app_service/service/wallet_account/service.nim +++ b/src/app_service/service/wallet_account/service.nim @@ -828,4 +828,14 @@ QtObject: if keycardsState.len == 0: return let data = KeycardActivityArgs(success: true) - self.events.emit(SIGNAL_KEYCARDS_SYNCHRONIZED, data) \ No newline at end of file + self.events.emit(SIGNAL_KEYCARDS_SYNCHRONIZED, data) + + proc allAccountsTokenBalance*(self: Service, symbol: string): float64 = + var totalTokenBalance = 0.0 + for walletAccount in self.getWalletAccounts: + if walletAccount.walletType != WalletTypeWatch: + for token in walletAccount.tokens: + if token.symbol == symbol: + totalTokenBalance += token.getTotalBalanceOfSupportedChains() + + return totalTokenBalance diff --git a/src/backend/communities.nim b/src/backend/communities.nim index aac27dac50..89eebce7e0 100644 --- a/src/backend/communities.nim +++ b/src/backend/communities.nim @@ -174,6 +174,29 @@ proc requestImportDiscordCommunity*( "encrypted": encrypted, }]) +proc createCommunityTokenPermission*(communityId: string, permissionType: int, tokenCriteria: string, isPrivate: bool): RpcResponse[JsonNode] {.raises: [Exception].} = + result = callPrivateRPC("createCommunityTokenPermission".prefix, %*[{ + "communityId": communityId, + "type": permissionType, + "tokenCriteria": parseJson(tokenCriteria), + "isPrivate": isPrivate + }]) + +proc editCommunityTokenPermission*(communityId: string, permissionId: string, permissionType: int, tokenCriteria: string, isPrivate: bool): RpcResponse[JsonNode] {.raises: [Exception].} = + result = callPrivateRPC("editCommunityTokenPermission".prefix, %*[{ + "communityId": communityId, + "permissionId": permissionId, + "type": permissionType, + "tokenCriteria": parseJson(tokenCriteria), + "isPrivate": isPrivate + }]) + +proc deleteCommunityTokenPermission*(communityId: string, permissionId: string): RpcResponse[JsonNode] {.raises: [Exception].} = + result = callPrivateRPC("deleteCommunityTokenPermission".prefix, %*[{ + "communityId": communityId, + "permissionId": permissionId + }]) + proc requestCancelDiscordCommunityImport*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} = result = callPrivateRPC("requestCancelDiscordCommunityImport".prefix, %*[communityId]) diff --git a/ui/app/AppLayouts/Chat/controls/community/HoldingTypes.qml b/ui/app/AppLayouts/Chat/controls/community/HoldingTypes.qml index 9b01f1bce5..ca3796d12c 100644 --- a/ui/app/AppLayouts/Chat/controls/community/HoldingTypes.qml +++ b/ui/app/AppLayouts/Chat/controls/community/HoldingTypes.qml @@ -2,7 +2,7 @@ import QtQml 2.14 QtObject { enum Type { - Asset, Collectible, Ens + Unknown, Asset, Collectible, Ens } enum Mode { diff --git a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml index c2f4597de6..262a55770c 100644 --- a/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml +++ b/ui/app/AppLayouts/Chat/panels/communities/CommunityPermissionsSettingsPanel.qml @@ -36,16 +36,16 @@ SettingsPageLayout { readonly property string newPermissionViewState: "NEW_PERMISSION" readonly property string permissionsViewState: "PERMISSIONS" readonly property string editPermissionViewState: "EDIT_PERMISSION" - readonly property bool permissionsExist: store.permissionsModel.count > 0 + readonly property bool permissionsExist: permissionsModel.count > 0 signal saveChanges signal resetChanges property string permissionKeyToEdit - property ListModel holdingsToEditModel: ListModel {} + property var holdingsToEditModel property int permissionTypeToEdit: PermissionTypes.Type.None - property ListModel channelsToEditModel: ListModel {} + property var channelsToEditModel property bool isPrivateToEditValue: false onPermissionsExistChanged: { @@ -55,7 +55,7 @@ SettingsPageLayout { } } - readonly property string initialState: root.store.permissionsModel.count > 0 + readonly property string initialState: root.rootStore.permissionsModel.count > 0 ? d.permissionsViewState : d.welcomeViewState function initializeData() { @@ -169,7 +169,7 @@ SettingsPageLayout { channelsTracker.revision communityNewPermissionView.dirtyValues.permissionType communityNewPermissionView.dirtyValues.isPrivate - const model = root.store.permissionsModel + const model = root.rootStore.permissionsModel const count = model.rowCount() for (let i = 0; i < count; i++) { @@ -273,7 +273,7 @@ SettingsPageLayout { store: root.store function setInitialValuesFromIndex(index) { - const item = ModelUtils.get(root.store.permissionsModel, index) + const item = ModelUtils.get(root.rootStore.permissionsModel, index) d.holdingsToEditModel = item.holdingsListModel d.channelsToEditModel = item.channelsListModel @@ -284,7 +284,7 @@ SettingsPageLayout { onEditPermissionRequested: { setInitialValuesFromIndex(index) d.permissionKeyToEdit = ModelUtils.get( - root.store.permissionsModel, index, "key") + root.rootStore.permissionsModel, index, "key") root.state = d.editPermissionViewState } @@ -294,7 +294,7 @@ SettingsPageLayout { } onRemovePermissionRequested: { - const key = ModelUtils.get(root.store.permissionsModel, index, "key") + const key = ModelUtils.get(root.rootStore.permissionsModel, index, "key") root.store.removePermission(key) } } diff --git a/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml b/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml index 05580cb864..ec4ddcac67 100644 --- a/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml +++ b/ui/app/AppLayouts/Chat/stores/CommunitiesStore.qml @@ -7,11 +7,12 @@ import StatusQ.Core.Utils 0.1 QtObject { id: root + property var mainModuleInst: mainModule + property var communitiesModuleInst: communitiesModule readonly property bool isOwner: false property var mintingModuleInst: mintingModule ?? null - property var permissionsModel: ListModel {} // Backend permissions list object model assignment. Please check the current expected data in qml defined in `createPermissions` method property var permissionConflict: QtObject { // Backend conflicts object model assignment. Now mocked data. property bool exists: false property string holdings: qsTr("1 ETH") @@ -20,112 +21,8 @@ QtObject { } - // TODO: Replace to real data, now dummy model - property var assetsModel: ListModel { - Component.onCompleted: { - append([ - { - key: "socks", - iconSource: "qrc:imports/assets/png/tokens/SOCKS.png", - name: "Unisocks", - shortName: "SOCKS", - category: TokenCategories.Category.Community - }, - { - key: "zrx", - iconSource: "qrc:imports/assets/png/tokens/ZRX.png", - name: "Ox", - shortName: "ZRX", - category: TokenCategories.Category.Own - }, - { - key: "1inch", - iconSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png", - name: "1inch", - shortName: "ZRX", - category: TokenCategories.Category.Own - }, - { - key: "Aave", - iconSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png", - name: "Aave", - shortName: "AAVE", - category: TokenCategories.Category.Own - }, - { - key: "Amp", - iconSource: "qrc:imports/assets/png/tokens/CUSTOM-TOKEN.png", - name: "Amp", - shortName: "AMP", - category: TokenCategories.Category.Own - } - ]) - } - } - - // TODO: Replace to real data, now dummy model - property var collectiblesModel: ListModel { - Component.onCompleted: { - append([ - { - key: "Anniversary", - iconSource: "qrc:imports/assets/png/collectibles/Anniversary.png", - name: "Anniversary", - category: TokenCategories.Category.Community - }, - { - key: "CryptoKitties", - iconSource: "qrc:imports/assets/png/collectibles/CryptoKitties.png", - name: "CryptoKitties", - category: TokenCategories.Category.Own, - subItems: [ - { - key: "Kitty1", - iconSource: "qrc:imports/assets/png/collectibles/Furbeard.png", - imageSource: "qrc:imports/assets/png/collectibles/FurbeardBig.png", - name: "Furbeard" - }, - { - key: "Kitty2", - iconSource: "qrc:imports/assets/png/collectibles/Magicat.png", - imageSource: "qrc:imports/assets/png/collectibles/MagicatBig.png", - name: "Magicat" - }, - { - key: "Kitty3", - iconSource: "qrc:imports/assets/png/collectibles/HappyMeow.png", - imageSource: "qrc:imports/assets/png/collectibles/HappyMeowBig.png", - name: "Happy Meow" - }, - { - key: "Kitty4", - iconSource: "qrc:imports/assets/png/collectibles/Furbeard.png", - imageSource: "qrc:imports/assets/png/collectibles/FurbeardBig.png", - name: "Furbeard-2" - }, - { - key: "Kitty5", - iconSource: "qrc:imports/assets/png/collectibles/Magicat.png", - imageSource: "qrc:imports/assets/png/collectibles/MagicatBig.png", - name: "Magicat-3" - } - ] - }, - { - key: "SuperRare", - iconSource: "qrc:imports/assets/png/collectibles/SuperRare.png", - name: "SuperRare", - category: TokenCategories.Category.Own - }, - { - key: "Custom", - iconSource: "qrc:imports/assets/png/collectibles/SNT.png", - name: "Custom Collectible", - category: TokenCategories.Category.General - } - ]) - } - } + property var assetsModel: chatCommunitySectionModule.tokenList + property var collectiblesModel: chatCommunitySectionModule.collectiblesModel // TODO: Replace to real data, now dummy model property var channelsModel: ListModel { @@ -151,29 +48,20 @@ QtObject { } function createPermission(holdings, permissionType, isPrivate, channels, index = null) { - // TO BE REPLACED: It shold just be a call to the backend sharing - // `holdings`, `permissions`, `channels` and `isPrivate` properties. - const permissionEntry = d.createPermissionEntry( holdings, permissionType, isPrivate, channels) - - permissionEntry.key = "" + d.keyCounter++ - root.permissionsModel.append(permissionEntry) + chatCommunitySectionModule.createOrEditCommunityTokenPermission(root.mainModuleInst.activeSection.id, "", permissionEntry.permissionType, JSON.stringify(permissionEntry.holdingsListModel), permissionEntry.isPrivate) } function editPermission(key, holdings, permissionType, channels, isPrivate) { - // TO BE REPLACED: Call to backend - const permissionEntry = d.createPermissionEntry( holdings, permissionType, isPrivate, channels) - const index = ModelUtils.indexOf(root.permissionsModel, "key", key) - root.permissionsModel.set(index, permissionEntry) + chatCommunitySectionModule.createOrEditCommunityTokenPermission(root.mainModuleInst.activeSection.id, key, permissionEntry.permissionType, JSON.stringify(permissionEntry.holdingsListModel), permissionEntry.isPrivate) } function removePermission(key) { - const index = ModelUtils.indexOf(root.permissionsModel, "key", key) - root.permissionsModel.remove(index) + chatCommunitySectionModule.deleteCommunityTokenPermission(root.mainModuleInst.activeSection.id, key) } // Minting tokens: diff --git a/ui/app/AppLayouts/Chat/stores/RootStore.qml b/ui/app/AppLayouts/Chat/stores/RootStore.qml index 9a85fa7533..b1ed05770c 100644 --- a/ui/app/AppLayouts/Chat/stores/RootStore.qml +++ b/ui/app/AppLayouts/Chat/stores/RootStore.qml @@ -165,6 +165,10 @@ QtObject { stickersModule: stickersModuleInst } + property var permissionsModel: chatCommunitySectionModule.permissionsModel + property var assetsModel: chatCommunitySectionModule.tokenList + property var collectiblesModel: chatCommunitySectionModule.collectiblesModel + function sendSticker(channelId, hash, replyTo, pack, url) { stickersModuleInst.send(channelId, hash, replyTo, pack, url) } diff --git a/ui/app/AppLayouts/Chat/views/communities/CommunityPermissionsView.qml b/ui/app/AppLayouts/Chat/views/communities/CommunityPermissionsView.qml index 80bd52cdfe..3275240581 100644 --- a/ui/app/AppLayouts/Chat/views/communities/CommunityPermissionsView.qml +++ b/ui/app/AppLayouts/Chat/views/communities/CommunityPermissionsView.qml @@ -53,7 +53,7 @@ StatusScrollView { } Repeater { - model: root.store.permissionsModel + model: root.rootStore.permissionsModel delegate: PermissionItem { Layout.preferredWidth: root.viewWidth diff --git a/vendor/status-go b/vendor/status-go index 7c7c3a1f13..596660c110 160000 --- a/vendor/status-go +++ b/vendor/status-go @@ -1 +1 @@ -Subproject commit 7c7c3a1f1335ea166b7839217d9ec37b74ad0191 +Subproject commit 596660c1106a5d991784a1372ba69a9f1e9b1f7e