feat(Communities): allow for creating community permissions

This commit is the first of implementing community permissions.
**It is not implementing the complete feature**, rather does it
introduce the first pieces, such that we can get code reviewed and
merged before it grows too big.

To review these features, please make sure to

1. Enable wallet (Settings -> Advanced -> Wallet toggle)
2. Enable community permissions (Settings -> Advanced -> Community
   Permissions toggle)

You'll have to restart the app after doing so.

The commit introduces the following:

**UI, API calls and view models to CRUD community permissions**

After creating a community, the user can go to the community
settings and create new token permissions. The user can also update
and delete existing permissions.

**Asset and collectible view models**

To create community token permissions, users have to select
the token criteria. This commit introduces the `assetsModel` for ERC20
tokens and `collectiblesModel` for `ERC721` tokens.

The latter only supports custom minted community tokens at this point.

**This commit requires:** https://github.com/status-im/status-go/pull/3207
This commit is contained in:
Pascal Precht 2023-02-28 16:23:09 +01:00 committed by r4bbit
parent 489d5d501d
commit e4b8814bfa
31 changed files with 1364 additions and 144 deletions

View File

@ -16,7 +16,13 @@ type
NewMessage, NewMessage,
NewMessageWithPersonalMention, NewMessageWithPersonalMention,
NewMessageWithGlobalMention, NewMessageWithGlobalMention,
IdentityVerificationRequest IdentityVerificationRequest,
CommunityTokenPermissionCreated,
CommunityTokenPermissionUpdated,
CommunityTokenPermissionDeleted,
CommunityTokenPermissionCreationFailed,
CommunityTokenPermissionUpdateFailed,
CommunityTokenPermissionDeletionFailed
NotificationDetails* = object NotificationDetails* = object
notificationType*: NotificationType # the default value is `UnknownNotification` notificationType*: NotificationType # the default value is `UnknownNotification`

View File

@ -85,6 +85,12 @@ QtObject:
self, "onMeMentionedIconBadgeNotification(int)", 2) self, "onMeMentionedIconBadgeNotification(int)", 2)
signalConnect(singletonInstance.globalEvents, "showAcceptedContactRequest(QString, QString, QString)", signalConnect(singletonInstance.globalEvents, "showAcceptedContactRequest(QString, QString, QString)",
self, "onShowAcceptedContactRequest(QString, QString, QString)", 2) 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 self.notificationSetUp = true
@ -140,6 +146,30 @@ QtObject:
messageId: messageId) messageId: messageId)
self.processNotification(title, message, details) 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, proc onShowNewContactRequestNotification*(self: NotificationsManager, title: string, message: string,
sectionId: string) {.slot.} = sectionId: string) {.slot.} =
let details = NotificationDetails(notificationType: NotificationType.NewContactRequest, sectionId: sectionId) let details = NotificationDetails(notificationType: NotificationType.NewContactRequest, sectionId: sectionId)

View File

@ -15,6 +15,18 @@ QtObject:
proc showTestNotification*(self: GlobalEvents, title: string, message: string) {.signal.} 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, proc showMessageNotification*(self: GlobalEvents, title: string, message: string, sectionId: string,
isCommunitySection: bool, isSectionActive: bool, chatId: string, isChatActive: bool, messageId: string, isCommunitySection: bool, isSectionActive: bool, chatId: string, isChatActive: bool, messageId: string,
notificationType: int, isOneToOne: bool, isGroupChat: bool) {.signal.} notificationType: int, isOneToOne: bool, isGroupChat: bool) {.signal.}

View File

@ -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/message/service as message_service
import ../../../../app_service/service/gif/service as gif_service import ../../../../app_service/service/gif/service as gif_service
import ../../../../app_service/service/mailservers/service as mailservers_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 ../../../../app_service/service/visual_identity/service as procs_from_visual_identity_service
import ../../../core/signals/types import ../../../core/signals/types
@ -33,12 +36,18 @@ type
messageService: message_service.Service messageService: message_service.Service
gifService: gif_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
proc newController*(delegate: io_interface.AccessInterface, sectionId: string, isCommunity: bool, events: EventEmitter, proc newController*(delegate: io_interface.AccessInterface, sectionId: string, isCommunity: bool, events: EventEmitter,
settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service, settingsService: settings_service.Service, nodeConfigurationService: node_configuration_service.Service,
contactService: contact_service.Service, chatService: chat_service.Service, communityService: community_service.Service, contactService: contact_service.Service, chatService: chat_service.Service, communityService: community_service.Service,
messageService: message_service.Service, gifService: gif_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 = Controller()
result.delegate = delegate result.delegate = delegate
result.sectionId = sectionId result.sectionId = sectionId
@ -53,6 +62,9 @@ proc newController*(delegate: io_interface.AccessInterface, sectionId: string, i
result.messageService = messageService result.messageService = messageService
result.gifService = gifService result.gifService = gifService
result.mailserversService = mailserversService result.mailserversService = mailserversService
result.walletAccountService = walletAccountService
result.tokenService = tokenService
result.communityTokensService = communityTokensService
proc delete*(self: Controller) = proc delete*(self: Controller) =
discard discard
@ -202,6 +214,40 @@ proc init*(self: Controller) =
if (args.communityId == self.sectionId): if (args.communityId == self.sectionId):
self.delegate.onCategoryUnmuted(args.categoryId) 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): self.events.on(SIGNAL_CONTACT_NICKNAME_CHANGED) do(e: Args):
var args = ContactArgs(e) var args = ContactArgs(e)
self.delegate.onContactDetailsUpdated(args.contactId) self.delegate.onContactDetailsUpdated(args.contactId)
@ -258,8 +304,11 @@ proc getMySectionId*(self: Controller): string =
proc isCommunity*(self: Controller): bool = proc isCommunity*(self: Controller): bool =
return self.isCommunitySection return self.isCommunitySection
proc getCommunityByIdFromAllCommunities*(self: Controller, communityId: string): CommunityDto =
return self.communityService.getCommunityByIdFromAllCommunities(communityId)
proc getMyCommunity*(self: Controller): CommunityDto = proc getMyCommunity*(self: Controller): CommunityDto =
return self.communityService.getCommunityById(self.sectionId) return self.getCommunityByIdFromAllCommunities(self.sectionId)
proc getCommunityById*(self: Controller, communityId: string): CommunityDto = proc getCommunityById*(self: Controller, communityId: string): CommunityDto =
return self.communityService.getCommunityById(communityId) return self.communityService.getCommunityById(communityId)
@ -502,3 +551,25 @@ proc getColorHash*(self: Controller, pubkey: string): ColorHashDto =
proc getColorId*(self: Controller, pubkey: string): int = proc getColorId*(self: Controller, pubkey: string): int =
procs_from_visual_identity_service.colorIdOf(pubkey) 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())

View File

@ -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/message/service as message_service
import ../../../../app_service/service/gif/service as gif_service import ../../../../app_service/service/gif/service as gif_service
import ../../../../app_service/service/mailservers/service as mailservers_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 model as chats_model
import ../../../core/eventemitter import ../../../core/eventemitter
import ../../shared_models/token_list_item
type type
AccessInterface* {.pure inheritable.} = ref object of RootObj AccessInterface* {.pure inheritable.} = ref object of RootObj
@ -325,3 +328,30 @@ method switchToChannel*(self: AccessInterface, channelName: string) =
method joinSpectatedCommunity*(self: AccessInterface) = method joinSpectatedCommunity*(self: AccessInterface) =
raise newException(ValueError, "No implementation available") 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")

View File

@ -7,6 +7,11 @@ import model as chats_model
import item as chat_item import item as chat_item
import ../../shared_models/user_item as user_item import ../../shared_models/user_item as user_item
import ../../shared_models/user_model as user_model 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/module as chat_content_module
import chat_content/users/module as users_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/message/service as message_service
import ../../../../app_service/service/mailservers/service as mailservers_service import ../../../../app_service/service/mailservers/service as mailservers_service
import ../../../../app_service/service/gif/service as gif_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/visual_identity/service as visual_identity
import ../../../../app_service/service/contacts/dto/contacts as contacts_dto import ../../../../app_service/service/contacts/dto/contacts as contacts_dto
import ../../../../app_service/service/community/dto/community as community_dto
export io_interface export io_interface
@ -55,6 +64,8 @@ proc buildChatSectionUI(self: Module,
gifService: gif_service.Service, gifService: gif_service.Service,
mailserversService: mailservers_service.Service) mailserversService: mailservers_service.Service)
proc buildTokenPermissionItem*(self: Module, tokenPermission: CommunityTokenPermissionDto): TokenPermissionItem
proc newModule*( proc newModule*(
delegate: delegate_interface.AccessInterface, delegate: delegate_interface.AccessInterface,
events: EventEmitter, events: EventEmitter,
@ -68,12 +79,15 @@ proc newModule*(
communityService: community_service.Service, communityService: community_service.Service,
messageService: message_service.Service, messageService: message_service.Service,
gifService: gif_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 = ): Module =
result = Module() result = Module()
result.delegate = delegate result.delegate = delegate
result.controller = controller.newController(result, sectionId, isCommunity, events, settingsService, nodeConfigurationService, 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.view = view.newView(result)
result.viewVariant = newQVariant(result.view) result.viewVariant = newQVariant(result.view)
result.moduleLoaded = false result.moduleLoaded = false
@ -288,6 +302,67 @@ proc initContactRequestsModel(self: Module) =
self.view.contactRequestsModel().addItems(contactsWhoAddedMe) 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] = proc convertPubKeysToJson(self: Module, pubKeys: string): seq[string] =
return map(parseJson(pubKeys).getElems(), proc(x:JsonNode):string = x.getStr) return map(parseJson(pubKeys).getElems(), proc(x:JsonNode):string = x.getStr)
@ -327,6 +402,8 @@ method load*(
self.initContactRequestsModel() self.initContactRequestsModel()
else: else:
self.usersModule.load() self.usersModule.load()
self.initCommunityTokenPermissionsModel()
self.buildTokenList()
let activeChatId = self.controller.getActiveChatId() let activeChatId = self.controller.getActiveChatId()
let isCurrentSectionActive = self.controller.getIsCurrentSectionActive() let isCurrentSectionActive = self.controller.getIsCurrentSectionActive()
@ -664,6 +741,49 @@ method onChatMuted*(self: Module, chatId: string) =
method onChatUnmuted*(self: Module, chatId: string) = method onChatUnmuted*(self: Module, chatId: string) =
self.view.chatsModel().changeMutedOnItemById(chatId, muted=false) 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) = method onMarkAllMessagesRead*(self: Module, chatId: string) =
self.updateBadgeNotifications(chatId, hasUnreadMessages=false, unviewedMentionsCount=0) self.updateBadgeNotifications(chatId, hasUnreadMessages=false, unviewedMentionsCount=0)
let chatDetails = self.controller.getChatDetails(chatId) let chatDetails = self.controller.getChatDetails(chatId)
@ -995,3 +1115,65 @@ method contactsStatusUpdated*(self: Module, statusUpdates: seq[StatusUpdateDto])
method joinSpectatedCommunity*(self: Module) = method joinSpectatedCommunity*(self: Module) =
if self.usersModule != nil: if self.usersModule != nil:
self.usersModule.updateMembersList() 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

View File

@ -2,6 +2,11 @@ import NimQml, json, sequtils
import model as chats_model import model as chats_model
import item, active_item import item, active_item
import ../../shared_models/user_model as user_model 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 import io_interface
QtObject: QtObject:
@ -20,6 +25,13 @@ QtObject:
editCategoryChannelsModel: chats_model.Model editCategoryChannelsModel: chats_model.Model
editCategoryChannelsVariant: QVariant editCategoryChannelsVariant: QVariant
loadingHistoryMessagesInProgress: bool loadingHistoryMessagesInProgress: bool
tokenPermissionsModel: TokenPermissionsModel
tokenPermissionsVariant: QVariant
tokenListModel: TokenListModel
tokenListModelVariant: QVariant
collectiblesListModel: TokenListModel
collectiblesListModelVariant: QVariant
allTokenRequirementsMet: bool
proc delete*(self: View) = proc delete*(self: View) =
self.model.delete self.model.delete
@ -32,6 +44,13 @@ QtObject:
self.listOfMyContactsVariant.delete self.listOfMyContactsVariant.delete
self.editCategoryChannelsModel.delete self.editCategoryChannelsModel.delete
self.editCategoryChannelsVariant.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 self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View = proc newView*(delegate: io_interface.AccessInterface): View =
@ -49,6 +68,12 @@ QtObject:
result.listOfMyContacts = user_model.newModel() result.listOfMyContacts = user_model.newModel()
result.listOfMyContactsVariant = newQVariant(result.listOfMyContacts) result.listOfMyContactsVariant = newQVariant(result.listOfMyContacts)
result.loadingHistoryMessagesInProgress = false 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) = proc load*(self: View) =
self.delegate.viewDidLoad() self.delegate.viewDidLoad()
@ -316,3 +341,57 @@ QtObject:
proc downloadMessages*(self: View, chatId: string, filePath: string) {.slot.} = proc downloadMessages*(self: View, chatId: string, filePath: string) {.slot.} =
self.delegate.downloadMessages(chatId, filePath) 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

View File

@ -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/privacy/service as privacy_service
import ../../../app_service/service/node/service as node_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/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_models/section_item, io_interface
import ../shared_modules/keycard_popup/io_interface as keycard_shared_module import ../shared_modules/keycard_popup/io_interface as keycard_shared_module
@ -46,6 +48,8 @@ type
communityTokensService: community_tokens_service.Service communityTokensService: community_tokens_service.Service
activeSectionId: string activeSectionId: string
authenticateUserFlowRequestedBy: string authenticateUserFlowRequestedBy: string
walletAccountService: wallet_account_service.Service
tokenService: token_service.Service
# Forward declaration # Forward declaration
proc setActiveSection*(self: Controller, sectionId: string, skipSavingInSettings: bool = false) proc setActiveSection*(self: Controller, sectionId: string, skipSavingInSettings: bool = false)
@ -64,6 +68,8 @@ proc newController*(delegate: io_interface.AccessInterface,
mailserversService: mailservers_service.Service, mailserversService: mailservers_service.Service,
nodeService: node_service.Service, nodeService: node_service.Service,
communityTokensService: community_tokens_service.Service, communityTokensService: community_tokens_service.Service,
walletAccountService: wallet_account_service.Service,
tokenService: token_service.Service
): ):
Controller = Controller =
result = Controller() result = Controller()
@ -81,6 +87,8 @@ proc newController*(delegate: io_interface.AccessInterface,
result.nodeService = nodeService result.nodeService = nodeService
result.mailserversService = mailserversService result.mailserversService = mailserversService
result.communityTokensService = communityTokensService result.communityTokensService = communityTokensService
result.walletAccountService = walletAccountService
result.tokenService = tokenService
proc delete*(self: Controller) = proc delete*(self: Controller) =
discard discard
@ -103,6 +111,9 @@ proc init*(self: Controller) =
self.messageService, self.messageService,
self.gifService, self.gifService,
self.mailserversService, self.mailserversService,
self.walletAccountService,
self.tokenService,
self.communityTokensService
) )
self.events.on(SIGNAL_CHATS_LOADING_FAILED) do(e:Args): self.events.on(SIGNAL_CHATS_LOADING_FAILED) do(e:Args):
@ -135,6 +146,9 @@ proc init*(self: Controller) =
self.messageService, self.messageService,
self.gifService, self.gifService,
self.mailserversService, self.mailserversService,
self.walletAccountService,
self.tokenService,
self.communityTokensService,
setActive = args.fromUserAction setActive = args.fromUserAction
) )
@ -151,6 +165,9 @@ proc init*(self: Controller) =
self.messageService, self.messageService,
self.gifService, self.gifService,
self.mailserversService, self.mailserversService,
self.walletAccountService,
self.tokenService,
self.communityTokensService,
setActive = args.fromUserAction setActive = args.fromUserAction
) )
@ -171,6 +188,9 @@ proc init*(self: Controller) =
self.messageService, self.messageService,
self.gifService, self.gifService,
self.mailserversService, self.mailserversService,
self.walletAccountService,
self.tokenService,
self.communityTokensService,
setActive = true setActive = true
) )
@ -189,6 +209,9 @@ proc init*(self: Controller) =
self.messageService, self.messageService,
self.gifService, self.gifService,
self.mailserversService, self.mailserversService,
self.walletAccountService,
self.tokenService,
self.communityTokensService,
setActive = false setActive = false
) )

View File

@ -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/message/service as message_service
import ../../../app_service/service/gif/service as gif_service import ../../../app_service/service/gif/service as gif_service
import ../../../app_service/service/mailservers/service as mailservers_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 from ../../../app_service/common/types import StatusType
import ../../global/app_signals import ../../global/app_signals
@ -80,7 +83,10 @@ method onChatsLoaded*(
communityService: community_service.Service, communityService: community_service.Service,
messageService: message_service.Service, messageService: message_service.Service,
gifService: gif_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.} = {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -121,6 +127,9 @@ method communityJoined*(self: AccessInterface, community: CommunityDto, events:
messageService: message_service.Service, messageService: message_service.Service,
gifService: gif_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,
setActive: bool = false,) {.base.} = setActive: bool = false,) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -157,7 +157,9 @@ proc newModule*[T](
privacyService, privacyService,
mailserversService, mailserversService,
nodeService, nodeService,
communityTokensService communityTokensService,
walletAccountService,
tokenService
) )
result.moduleLoaded = false result.moduleLoaded = false
@ -512,6 +514,9 @@ method onChatsLoaded*[T](
messageService: message_service.Service, messageService: message_service.Service,
gifService: gif_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
) = ) =
var activeSection: SectionItem var activeSection: SectionItem
var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection() var activeSectionId = singletonInstance.localAccountSensitiveSettings.getActiveSection()
@ -531,7 +536,10 @@ method onChatsLoaded*[T](
communityService, communityService,
messageService, messageService,
gifService, gifService,
mailserversService mailserversService,
walletAccountService,
tokenService,
communityTokensService
) )
let channelGroupItem = self.createChannelGroupItem(channelGroup) let channelGroupItem = self.createChannelGroupItem(channelGroup)
self.view.model().addItem(channelGroupItem) self.view.model().addItem(channelGroupItem)
@ -768,6 +776,9 @@ method communityJoined*[T](
messageService: message_service.Service, messageService: message_service.Service,
gifService: gif_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,
setActive: bool = false, setActive: bool = false,
) = ) =
var firstCommunityJoined = false var firstCommunityJoined = false
@ -785,7 +796,10 @@ method communityJoined*[T](
communityService, communityService,
messageService, messageService,
gifService, gifService,
mailserversService mailserversService,
walletAccountService,
tokenService,
communityTokensService
) )
let channelGroup = community.toChannelGroupDto() let channelGroup = community.toChannelGroupDto()
self.channelGroupModules[community.id].load(channelGroup, events, settingsService, nodeConfigurationService, contactsService, 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) = method displayEphemeralNotification*[T](self: Module[T], title: string, subTitle: string, details: NotificationDetails) =
if(details.notificationType == NotificationType.NewMessage or if(details.notificationType == NotificationType.NewMessage or
details.notificationType == NotificationType.NewMessageWithPersonalMention 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): details.notificationType == NotificationType.NewMessageWithGlobalMention):
self.displayEphemeralNotification(title, subTitle, "", false, EphemeralNotificationType.Default.int, "", details) self.displayEphemeralNotification(title, subTitle, "", false, EphemeralNotificationType.Default.int, "", details)

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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())

View File

@ -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

View File

@ -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()

View File

@ -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

View File

@ -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
])

View File

@ -1,6 +1,6 @@
{.used.} {.used.}
import json, sequtils, sugar, tables import json, sequtils, sugar, tables, strutils, json_serialization
import ../../../../backend/communities import ../../../../backend/communities
include ../../../common/json_utils include ../../../common/json_utils
@ -29,6 +29,34 @@ type CommunitySettingsDto* = object
type CommunityAdminSettingsDto* = object type CommunityAdminSettingsDto* = object
pinMessageAllMembersEnabled*: bool 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 type CommunityDto* = object
id*: string id*: string
admin*: bool admin*: bool
@ -60,6 +88,7 @@ type CommunityDto* = object
declinedRequestsToJoin*: seq[CommunityMembershipRequestDto] declinedRequestsToJoin*: seq[CommunityMembershipRequestDto]
encrypted*: bool encrypted*: bool
canceledRequestsToJoin*: seq[CommunityMembershipRequestDto] canceledRequestsToJoin*: seq[CommunityMembershipRequestDto]
tokenPermissions*: Table[string, CommunityTokenPermissionDto]
type CuratedCommunity* = object type CuratedCommunity* = object
available*: bool available*: bool
@ -132,6 +161,62 @@ proc toDiscordImportTaskProgress*(jsonObj: JsonNode): DiscordImportTaskProgress
let importError = error.toDiscordImportError() let importError = error.toDiscordImportError()
result.errors.add(importError) 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 = proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
result = CommunityDto() result = CommunityDto()
discard jsonObj.getProp("id", result.id) discard jsonObj.getProp("id", result.id)
@ -165,6 +250,12 @@ proc toCommunityDto*(jsonObj: JsonNode): CommunityDto =
if(jsonObj.getProp("permissions", permissionObj)): if(jsonObj.getProp("permissions", permissionObj)):
result.permissions = toPermission(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 var adminSettingsObj: JsonNode
if(jsonObj.getProp("adminSettings", adminSettingsObj)): if(jsonObj.getProp("adminSettings", adminSettingsObj)):
result.adminSettings = toCommunityAdminSettingsDto(adminSettingsObj) result.adminSettings = toCommunityAdminSettingsDto(adminSettingsObj)

View File

@ -1,4 +1,5 @@
import NimQml, Tables, json, sequtils, std/sets, std/algorithm, strformat, strutils, chronicles, json_serialization, sugar 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 import ./dto/community as community_dto
@ -84,6 +85,15 @@ type
communityId*: string communityId*: string
categoryId*: 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 DiscordCategoriesAndChannelsArgs* = ref object of Args
categories*: seq[DiscordCategoryDto] categories*: seq[DiscordCategoryDto]
channels*: seq[DiscordChannelDto] 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_CATEGORIES_AND_CHANNELS_EXTRACTED* = "discordCategoriesAndChannelsExtracted"
const SIGNAL_DISCORD_COMMUNITY_IMPORT_FINISHED* = "discordCommunityImportFinished" const SIGNAL_DISCORD_COMMUNITY_IMPORT_FINISHED* = "discordCommunityImportFinished"
const SIGNAL_DISCORD_COMMUNITY_IMPORT_PROGRESS* = "discordCommunityImportProgress" 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_LOADING* = "curatedCommunitiesLoading"
const SIGNAL_CURATED_COMMUNITIES_LOADED* = "curatedCommunitiesLoaded" const SIGNAL_CURATED_COMMUNITIES_LOADED* = "curatedCommunitiesLoaded"
const SIGNAL_CURATED_COMMUNITIES_LOADING_FAILED* = "curatedCommunitiesLoadingFailed" const SIGNAL_CURATED_COMMUNITIES_LOADING_FAILED* = "curatedCommunitiesLoadingFailed"
const TOKEN_PERMISSIONS_ADDED = "tokenPermissionsAdded"
const TOKEN_PERMISSIONS_MODIFIED = "tokenPermissionsModified"
QtObject: QtObject:
type type
Service* = ref object of QObject Service* = ref object of QObject
@ -319,6 +338,14 @@ QtObject:
return idx return idx
return -1 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 = proc findIndexById(id: string, categories: seq[Category]): int =
var idx = -1 var idx = -1
for category in categories: for category in categories:
@ -487,6 +514,49 @@ QtObject:
self.allCommunities[community.id] = community self.allCommunities[community.id] = community
self.events.emit(SIGNAL_COMMUNITIES_UPDATE, CommunitiesArgs(communities: @[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(not self.joinedCommunities.hasKey(community.id)):
if (community.joined and community.isMember): if (community.joined and community.isMember):
self.joinedCommunities[community.id] = community self.joinedCommunities[community.id] = community
@ -568,6 +638,13 @@ QtObject:
proc getCuratedCommunities*(self: Service): seq[CuratedCommunity] = proc getCuratedCommunities*(self: Service): seq[CuratedCommunity] =
return toSeq(self.curatedCommunities.values) 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 = proc getCommunityById*(self: Service, communityId: string): CommunityDto =
if(not self.joinedCommunities.hasKey(communityId)): if(not self.joinedCommunities.hasKey(communityId)):
error "error: requested community doesn't exists", communityId error "error: requested community doesn't exists", communityId
@ -1557,3 +1634,55 @@ QtObject:
except Exception as e: except Exception as e:
error "Error extracting discord channels and categories", msg = e.msg 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

View File

@ -125,3 +125,10 @@ QtObject:
return parseCommunityTokens(response) return parseCommunityTokens(response)
except RpcException: except RpcException:
error "Error getting community tokens", message = getCurrentExceptionMsg() 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

View File

@ -47,6 +47,8 @@ QtObject:
threadpool: ThreadPool threadpool: ThreadPool
networkService: network_service.Service networkService: network_service.Service
tokens: Table[int, seq[TokenDto]] tokens: Table[int, seq[TokenDto]]
tokenList: seq[TokenDto]
tokensToAddressesMap: Table[string, Table[int, string]]
priceCache: TimedCache[float64] priceCache: TimedCache[float64]
proc updateCachedTokenPrice(self: Service, crypto: string, fiat: string, price: float64) proc updateCachedTokenPrice(self: Service, crypto: string, fiat: string, price: float64)
@ -67,6 +69,8 @@ QtObject:
result.networkService = networkService result.networkService = networkService
result.tokens = initTable[int, seq[TokenDto]]() result.tokens = initTable[int, seq[TokenDto]]()
result.priceCache = newTimedCache[float64]() result.priceCache = newTimedCache[float64]()
result.tokenList = @[]
result.tokensToAddressesMap = initTable[string, Table[int, string]]()
proc loadData*(self: Service) = proc loadData*(self: Service) =
if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()): if(not singletonInstance.localAccountSensitiveSettings.getIsWalletEnabled()):
@ -92,6 +96,32 @@ QtObject:
self.tokens[network.chainId] = default_tokens.filter( self.tokens[network.chainId] = default_tokens.filter(
proc(x: TokenDto): bool = x.chainId == network.chainId 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: except Exception as e:
error "Tokens init error", errDesription = e.msg error "Tokens init error", errDesription = e.msg
@ -99,6 +129,16 @@ QtObject:
signalConnect(singletonInstance.localAccountSensitiveSettings, "isWalletEnabledChanged()", self, "onIsWalletEnabledChanged()", 2) signalConnect(singletonInstance.localAccountSensitiveSettings, "isWalletEnabledChanged()", self, "onIsWalletEnabledChanged()", 2)
self.loadData() 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.} = proc onIsWalletEnabledChanged*(self: Service) {.slot.} =
self.loadData() self.loadData()

View File

@ -147,6 +147,13 @@ proc getAddress*(self: WalletTokenDto): string =
return "" 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] = proc getBalances*(self: WalletTokenDto, chainIds: seq[int]): seq[BalanceDto] =
for chainId in chainIds: for chainId in chainIds:
if self.balancesPerChain.hasKey(chainId): if self.balancesPerChain.hasKey(chainId):

View File

@ -829,3 +829,13 @@ QtObject:
return return
let data = KeycardActivityArgs(success: true) let data = KeycardActivityArgs(success: true)
self.events.emit(SIGNAL_KEYCARDS_SYNCHRONIZED, data) 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

View File

@ -174,6 +174,29 @@ proc requestImportDiscordCommunity*(
"encrypted": encrypted, "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].} = proc requestCancelDiscordCommunityImport*(communityId: string): RpcResponse[JsonNode] {.raises: [Exception].} =
result = callPrivateRPC("requestCancelDiscordCommunityImport".prefix, %*[communityId]) result = callPrivateRPC("requestCancelDiscordCommunityImport".prefix, %*[communityId])

View File

@ -2,7 +2,7 @@ import QtQml 2.14
QtObject { QtObject {
enum Type { enum Type {
Asset, Collectible, Ens Unknown, Asset, Collectible, Ens
} }
enum Mode { enum Mode {

View File

@ -36,16 +36,16 @@ SettingsPageLayout {
readonly property string newPermissionViewState: "NEW_PERMISSION" readonly property string newPermissionViewState: "NEW_PERMISSION"
readonly property string permissionsViewState: "PERMISSIONS" readonly property string permissionsViewState: "PERMISSIONS"
readonly property string editPermissionViewState: "EDIT_PERMISSION" readonly property string editPermissionViewState: "EDIT_PERMISSION"
readonly property bool permissionsExist: store.permissionsModel.count > 0 readonly property bool permissionsExist: permissionsModel.count > 0
signal saveChanges signal saveChanges
signal resetChanges signal resetChanges
property string permissionKeyToEdit property string permissionKeyToEdit
property ListModel holdingsToEditModel: ListModel {} property var holdingsToEditModel
property int permissionTypeToEdit: PermissionTypes.Type.None property int permissionTypeToEdit: PermissionTypes.Type.None
property ListModel channelsToEditModel: ListModel {} property var channelsToEditModel
property bool isPrivateToEditValue: false property bool isPrivateToEditValue: false
onPermissionsExistChanged: { 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 ? d.permissionsViewState : d.welcomeViewState
function initializeData() { function initializeData() {
@ -169,7 +169,7 @@ SettingsPageLayout {
channelsTracker.revision channelsTracker.revision
communityNewPermissionView.dirtyValues.permissionType communityNewPermissionView.dirtyValues.permissionType
communityNewPermissionView.dirtyValues.isPrivate communityNewPermissionView.dirtyValues.isPrivate
const model = root.store.permissionsModel const model = root.rootStore.permissionsModel
const count = model.rowCount() const count = model.rowCount()
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
@ -273,7 +273,7 @@ SettingsPageLayout {
store: root.store store: root.store
function setInitialValuesFromIndex(index) { function setInitialValuesFromIndex(index) {
const item = ModelUtils.get(root.store.permissionsModel, index) const item = ModelUtils.get(root.rootStore.permissionsModel, index)
d.holdingsToEditModel = item.holdingsListModel d.holdingsToEditModel = item.holdingsListModel
d.channelsToEditModel = item.channelsListModel d.channelsToEditModel = item.channelsListModel
@ -284,7 +284,7 @@ SettingsPageLayout {
onEditPermissionRequested: { onEditPermissionRequested: {
setInitialValuesFromIndex(index) setInitialValuesFromIndex(index)
d.permissionKeyToEdit = ModelUtils.get( d.permissionKeyToEdit = ModelUtils.get(
root.store.permissionsModel, index, "key") root.rootStore.permissionsModel, index, "key")
root.state = d.editPermissionViewState root.state = d.editPermissionViewState
} }
@ -294,7 +294,7 @@ SettingsPageLayout {
} }
onRemovePermissionRequested: { onRemovePermissionRequested: {
const key = ModelUtils.get(root.store.permissionsModel, index, "key") const key = ModelUtils.get(root.rootStore.permissionsModel, index, "key")
root.store.removePermission(key) root.store.removePermission(key)
} }
} }

View File

@ -7,11 +7,12 @@ import StatusQ.Core.Utils 0.1
QtObject { QtObject {
id: root id: root
property var mainModuleInst: mainModule
property var communitiesModuleInst: communitiesModule
readonly property bool isOwner: false readonly property bool isOwner: false
property var mintingModuleInst: mintingModule ?? null 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 var permissionConflict: QtObject { // Backend conflicts object model assignment. Now mocked data.
property bool exists: false property bool exists: false
property string holdings: qsTr("1 ETH") property string holdings: qsTr("1 ETH")
@ -20,112 +21,8 @@ QtObject {
} }
// TODO: Replace to real data, now dummy model property var assetsModel: chatCommunitySectionModule.tokenList
property var assetsModel: ListModel { property var collectiblesModel: chatCommunitySectionModule.collectiblesModel
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
}
])
}
}
// TODO: Replace to real data, now dummy model // TODO: Replace to real data, now dummy model
property var channelsModel: ListModel { property var channelsModel: ListModel {
@ -151,29 +48,20 @@ QtObject {
} }
function createPermission(holdings, permissionType, isPrivate, channels, index = null) { 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( const permissionEntry = d.createPermissionEntry(
holdings, permissionType, isPrivate, channels) holdings, permissionType, isPrivate, channels)
chatCommunitySectionModule.createOrEditCommunityTokenPermission(root.mainModuleInst.activeSection.id, "", permissionEntry.permissionType, JSON.stringify(permissionEntry.holdingsListModel), permissionEntry.isPrivate)
permissionEntry.key = "" + d.keyCounter++
root.permissionsModel.append(permissionEntry)
} }
function editPermission(key, holdings, permissionType, channels, isPrivate) { function editPermission(key, holdings, permissionType, channels, isPrivate) {
// TO BE REPLACED: Call to backend
const permissionEntry = d.createPermissionEntry( const permissionEntry = d.createPermissionEntry(
holdings, permissionType, isPrivate, channels) holdings, permissionType, isPrivate, channels)
const index = ModelUtils.indexOf(root.permissionsModel, "key", key) chatCommunitySectionModule.createOrEditCommunityTokenPermission(root.mainModuleInst.activeSection.id, key, permissionEntry.permissionType, JSON.stringify(permissionEntry.holdingsListModel), permissionEntry.isPrivate)
root.permissionsModel.set(index, permissionEntry)
} }
function removePermission(key) { function removePermission(key) {
const index = ModelUtils.indexOf(root.permissionsModel, "key", key) chatCommunitySectionModule.deleteCommunityTokenPermission(root.mainModuleInst.activeSection.id, key)
root.permissionsModel.remove(index)
} }
// Minting tokens: // Minting tokens:

View File

@ -165,6 +165,10 @@ QtObject {
stickersModule: stickersModuleInst stickersModule: stickersModuleInst
} }
property var permissionsModel: chatCommunitySectionModule.permissionsModel
property var assetsModel: chatCommunitySectionModule.tokenList
property var collectiblesModel: chatCommunitySectionModule.collectiblesModel
function sendSticker(channelId, hash, replyTo, pack, url) { function sendSticker(channelId, hash, replyTo, pack, url) {
stickersModuleInst.send(channelId, hash, replyTo, pack, url) stickersModuleInst.send(channelId, hash, replyTo, pack, url)
} }

View File

@ -53,7 +53,7 @@ StatusScrollView {
} }
Repeater { Repeater {
model: root.store.permissionsModel model: root.rootStore.permissionsModel
delegate: PermissionItem { delegate: PermissionItem {
Layout.preferredWidth: root.viewWidth Layout.preferredWidth: root.viewWidth

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit 7c7c3a1f1335ea166b7839217d9ec37b74ad0191 Subproject commit 596660c1106a5d991784a1372ba69a9f1e9b1f7e