feat(CommunityTokens): Display community member contact data for token holders list (#11787)

* feat(CommunityTokens): proposal of CommunityCollectibleOwner

Close #11143

* feat(Communities): implement viewProfile from token holders list

* fix: re-request community token owners after a contact get removed from a community
This commit is contained in:
Mikhail Rogachev 2023-08-17 11:24:14 +04:00 committed by GitHub
parent b38faeae64
commit f4b028bd71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 102 additions and 35 deletions

View File

@ -1,5 +1,6 @@
import strformat, sequtils, stint
import ../../../../../../app_service/service/community_tokens/dto/community_token
import ../../../../../../app_service/service/community_tokens/community_collectible_owner
import ../../../../../../app_service/service/network/dto
import ../../../../../../app_service/common/types
from backend/collectibles_types import CollectibleOwner
@ -24,7 +25,7 @@ type
proc initTokenItem*(
tokenDto: CommunityTokenDto,
network: NetworkDto,
tokenOwners: seq[CollectibleOwner],
tokenOwners: seq[CommunityCollectibleOwner],
accountName: string,
burnState: ContractTransactionStatus,
remoteDestructedAddresses: seq[string],
@ -41,9 +42,8 @@ proc initTokenItem*(
result.burnState = burnState
result.remoteDestructedAddresses = remoteDestructedAddresses
result.tokenOwnersModel = newTokenOwnersModel()
result.tokenOwnersModel.setItems(tokenOwners.map(proc(owner: CollectibleOwner): TokenOwnersItem =
# TODO find member with the address - later when airdrop to member will be added
result = initTokenOwnersItem("", "", owner, remoteDestructedAddresses)
result.tokenOwnersModel.setItems(tokenOwners.map(proc(owner: CommunityCollectibleOwner): TokenOwnersItem =
result = initTokenOwnersItem(owner.contactId, owner.name, owner.imageSource, owner.collectibleOwner, remoteDestructedAddresses)
))
proc `$`*(self: TokenItem): string =

View File

@ -3,9 +3,9 @@ import token_item
import token_owners_item
import token_owners_model
import ../../../../../../app_service/service/community_tokens/dto/community_token
import ../../../../../../app_service/service/community_tokens/community_collectible_owner
import ../../../../../../app_service/common/utils
import ../../../../../../app_service/common/types
from backend/collectibles_types import CollectibleOwner
type
ModelRole {.pure.} = enum
@ -107,12 +107,11 @@ QtObject:
self.dataChanged(index, index, @[ModelRole.RemainingSupply.int])
return
proc setCommunityTokenOwners*(self: TokenModel, chainId: int, contractAddress: string, owners: seq[CollectibleOwner]) =
proc setCommunityTokenOwners*(self: TokenModel, chainId: int, contractAddress: string, owners: seq[CommunityCollectibleOwner]) =
for i in 0 ..< self.items.len:
if((self.items[i].tokenDto.address == contractAddress) and (self.items[i].tokenDto.chainId == chainId)):
self.items[i].tokenOwnersModel.setItems(owners.map(proc(owner: CollectibleOwner): TokenOwnersItem =
# TODO find member with the address - later when airdrop to member will be added
result = initTokenOwnersItem("", "", owner, self.items[i].remoteDestructedAddresses)
self.items[i].tokenOwnersModel.setItems(owners.map(proc(owner: CommunityCollectibleOwner): TokenOwnersItem =
result = initTokenOwnersItem(owner.contactId, owner.name, owner.imageSource, owner.collectibleOwner, self.items[i].remoteDestructedAddresses)
))
let index = self.createIndex(i, 0, nil)
defer: index.delete

View File

@ -4,6 +4,7 @@ import ../../../../../../app_service/common/types
type
TokenOwnersItem* = object
contactId*: string
name*: string
imageSource*: string
ownerDetails*: CollectibleOwner
@ -16,11 +17,13 @@ proc remoteDestructTransactionStatus*(remoteDestructedAddresses: seq[string], ad
return ContractTransactionStatus.Completed
proc initTokenOwnersItem*(
contactId: string,
name: string,
imageSource: string,
ownerDetails: CollectibleOwner,
remoteDestructedAddresses: seq[string]
): TokenOwnersItem =
result.contactId = contactId
result.name = name
result.imageSource = imageSource
result.ownerDetails = ownerDetails
@ -30,6 +33,7 @@ proc initTokenOwnersItem*(
proc `$`*(self: TokenOwnersItem): string =
result = fmt"""TokenOwnersItem(
contactId: {self.contactId},
name: {self.name},
amount: {self.amount},
ownerDetails: {self.ownerDetails}

View File

@ -4,6 +4,7 @@ import token_owners_item
type
ModelRole {.pure.} = enum
Name = UserRole + 1
ContactId,
ImageSource
WalletAddress
Amount
@ -45,6 +46,7 @@ QtObject:
method roleNames(self: TokenOwnersModel): Table[int, string] =
{
ModelRole.Name.int:"name",
ModelRole.ContactId.int:"contactId",
ModelRole.ImageSource.int:"imageSource",
ModelRole.WalletAddress.int:"walletAddress",
ModelRole.Amount.int:"amount",
@ -70,6 +72,8 @@ QtObject:
case enumRole:
of ModelRole.Name:
result = newQVariant(item.name)
of ModelRole.ContactId:
result = newQVariant(item.contactId)
of ModelRole.ImageSource:
result = newQVariant(item.imageSource)
of ModelRole.WalletAddress:

View File

@ -23,7 +23,7 @@ import ../../../app_service/service/token/service as token_service
import ../../../app_service/service/network/service as networks_service
import ../../../app_service/service/visual_identity/service as procs_from_visual_identity_service
from backend/collectibles_types import CollectibleOwner
import ../../../app_service/service/community_tokens/community_collectible_owner
import io_interface
import ../shared_modules/keycard_popup/io_interface as keycard_shared_module
@ -404,6 +404,10 @@ proc init*(self: Controller) =
var args = CommunityMemberArgs(e)
self.delegate.onAcceptRequestToJoinSuccess(args.communityId, args.pubKey, args.requestId)
self.events.on(SIGNAL_COMMUNITY_MEMBERS_CHANGED) do(e: Args):
let args = CommunityMembersArgs(e)
self.communityTokensService.fetchCommunityTokenOwners(args.communityId)
self.events.on(SIGNAL_SHARED_KEYCARD_MODULE_FLOW_TERMINATED) do(e: Args):
let args = SharedKeycarModuleFlowTerminatedArgs(e)
if args.uniqueIdentifier == UNIQUE_MAIN_MODULE_KEYCARD_SYNC_IDENTIFIER:
@ -504,7 +508,7 @@ proc getVerificationRequestFrom*(self: Controller, publicKey: string): Verificat
proc getCommunityTokens*(self: Controller, communityId: string): seq[CommunityTokenDto] =
self.communityTokensService.getCommunityTokens(communityId)
proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner] =
proc getCommunityTokenOwners*(self: Controller, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner] =
return self.communityTokensService.getCommunityTokenOwners(communityId, chainId, contractAddress)
proc getCommunityTokenOwnerName*(self: Controller, contractOwnerAddress: string): string =

View File

@ -12,8 +12,8 @@ import ../../../app_service/service/community_tokens/service as community_token_
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/community_tokens/community_collectible_owner
from ../../../app_service/common/types import StatusType, ContractTransactionStatus
from backend/collectibles_types import CollectibleOwner
import ../../global/app_signals
import ../../core/eventemitter
@ -294,7 +294,7 @@ method onCommunityTokenDeploymentStarted*(self: AccessInterface, communityToken:
method onOwnerTokensDeploymentStarted*(self: AccessInterface, ownerToken: CommunityTokenDto, masterToken: CommunityTokenDto) {.base.} =
raise newException(ValueError, "No implementation available")
method onCommunityTokenOwnersFetched*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, owners: seq[CollectibleOwner]) {.base.} =
method onCommunityTokenOwnersFetched*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, owners: seq[CommunityCollectibleOwner]) {.base.} =
raise newException(ValueError, "No implementation available")
method onCommunityTokenDeployStateChanged*(self: AccessInterface, communityId: string, chainId: int, contractAddress: string, deployState: DeployState) {.base.} =

View File

@ -28,7 +28,7 @@ import network_connection/module as network_connection_module
import shared_urls/module as shared_urls_module
import ../../../app_service/service/contacts/dto/contacts
from backend/collectibles_types import CollectibleOwner
import ../../../app_service/service/community_tokens/community_collectible_owner
import ../../../app_service/service/keychain/service as keychain_service
import ../../../app_service/service/chat/service as chat_service
@ -1060,7 +1060,7 @@ method onCommunityTokenRemoved*[T](self: Module[T], communityId: string, chainId
if item.id != "":
item.removeCommunityToken(chainId, address)
method onCommunityTokenOwnersFetched*[T](self: Module[T], communityId: string, chainId: int, contractAddress: string, owners: seq[CollectibleOwner]) =
method onCommunityTokenOwnersFetched*[T](self: Module[T], communityId: string, chainId: int, contractAddress: string, owners: seq[CommunityCollectibleOwner]) =
let item = self.view.model().getItemById(communityId)
if item.id != "":
item.setCommunityTokenOwners(chainId, contractAddress, owners)

View File

@ -3,11 +3,11 @@ import ./member_model, ./member_item
import ../main/communities/models/[pending_request_item, pending_request_model]
import ../main/communities/tokens/models/token_model as community_tokens_model
import ../main/communities/tokens/models/token_item
from backend/collectibles_types import CollectibleOwner
import ../../global/global_singleton
import ../../../app_service/common/types
import ../../../app_service/service/community_tokens/community_collectible_owner
type
SectionType* {.pure.} = enum
@ -353,7 +353,7 @@ proc updateBurnState*(self: SectionItem, chainId: int, contractAddress: string,
proc updateRemoteDestructedAddresses*(self: SectionItem, chainId: int, contractAddress: string, addresess: seq[string]) {.inline.} =
self.communityTokensModel.updateRemoteDestructedAddresses(chainId, contractAddress, addresess)
proc setCommunityTokenOwners*(self: SectionItem, chainId: int, contractAddress: string, owners: seq[CollectibleOwner]) {.inline.} =
proc setCommunityTokenOwners*(self: SectionItem, chainId: int, contractAddress: string, owners: seq[CommunityCollectibleOwner]) {.inline.} =
self.communityTokensModel.setCommunityTokenOwners(chainId, contractAddress, owners)
proc communityTokens*(self: SectionItem): community_tokens_model.TokenModel {.inline.} =

View File

@ -0,0 +1,9 @@
import json
from backend/collectibles_types import CollectibleOwner
type
CommunityCollectibleOwner* = object
contactId*: string
name*: string
imageSource*: string
collectibleOwner*: CollectibleOwner

View File

@ -4,6 +4,7 @@ import ../../../app/core/eventemitter
import ../../../app/core/tasks/[qt, threadpool]
import ../../../app/modules/shared_models/currency_amount
import ../../../backend/communities as communities_backend
import ../../../backend/community_tokens as tokens_backend
import ../transaction/service as transaction_service
import ../token/service as token_service
@ -19,7 +20,9 @@ import ../../common/conversion
import ../../common/account_constants
import ../../common/utils as common_utils
import ../community/dto/community
import ../contacts/dto/contacts
import ./community_collectible_owner
import ./dto/deployment_parameters
import ./dto/community_token
import ./dto/community_token_owner
@ -197,7 +200,7 @@ type
communityId*: string
contractAddress*: string
chainId*: int
owners*: seq[CollectibleOwner]
owners*: seq[CommunityCollectibleOwner]
# Signals which may be emitted by this service:
const SIGNAL_COMMUNITY_TOKEN_DEPLOY_STATUS* = "communityTokenDeployStatus"
@ -231,7 +234,7 @@ QtObject:
tokenOwnersTimer: QTimer
tokenOwners1SecTimer: QTimer # used to update 1 sec after changes in owners
tempTokenOwnersToFetch: CommunityTokenDto # used by 1sec timer
tokenOwnersCache: Table[ContractTuple, seq[CollectibleOwner]]
tokenOwnersCache: Table[ContractTuple, seq[CommunityCollectibleOwner]]
tempFeeTable: Table[int, SuggestedFeesDto] # fees per chain, filled during gas computation, used during operation (deployment, mint, burn)
tempGasTable: Table[ContractTuple, int] # gas per contract, filled during gas computation, used during operation (deployment, mint, burn)
@ -245,7 +248,7 @@ QtObject:
# Forward declaration
proc fetchAllTokenOwners*(self: Service)
proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner]
proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner]
proc getCommunityToken*(self: Service, chainId: int, address: string): CommunityTokenDto
proc delete*(self: Service) =
@ -699,12 +702,12 @@ QtObject:
if common_utils.contractUniqueKey(token.chainId, token.address) == contractUniqueKey:
return token
proc getOwnerBalances(self: Service, contractOwners: seq[CollectibleOwner], ownerAddress: string): seq[CollectibleBalance] =
proc getOwnerBalances(self: Service, contractOwners: seq[CommunityCollectibleOwner], ownerAddress: string): seq[CollectibleBalance] =
for owner in contractOwners:
if owner.address == ownerAddress:
return owner.balances
if owner.collectibleOwner.address == ownerAddress:
return owner.collectibleOwner.balances
proc collectTokensToBurn(self: Service, walletAndAmountList: seq[WalletAndAmount], contractOwners: seq[CollectibleOwner]): seq[UInt256] =
proc collectTokensToBurn(self: Service, walletAndAmountList: seq[WalletAndAmount], contractOwners: seq[CommunityCollectibleOwner]): seq[UInt256] =
if len(walletAndAmountList) == 0 or len(contractOwners) == 0:
return
for walletAndAmount in walletAndAmountList:
@ -1009,7 +1012,7 @@ QtObject:
self.threadpool.start(arg)
# get owners from cache
proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CollectibleOwner] =
proc getCommunityTokenOwners*(self: Service, communityId: string, chainId: int, contractAddress: string): seq[CommunityCollectibleOwner] =
return self.tokenOwnersCache.getOrDefault((chainId: chainId, address: contractAddress))
proc onCommunityTokenOwnersFetched*(self:Service, response: string) {.slot.} =
@ -1024,8 +1027,29 @@ QtObject:
let resultJson = responseJson["result"]
var owners = fromJson(resultJson, CollectibleContractOwnership).owners
owners = owners.filter(x => x.address != ZERO_ADDRESS)
self.tokenOwnersCache[(chainId, contractAddress)] = owners
let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: owners)
let response = communities_backend.getCommunityMembersForWalletAddresses(communityId, chainId)
if response.error != nil:
let errorMessage = responseJson["error"].getStr
error "Can't get community members with addresses", errorMsg=errorMessage
return
let communityOwners = owners.map(proc(owner: CollectibleOwner): CommunityCollectibleOwner =
let ownerAddressUp = owner.address.toUpper()
for responseAddress in response.result.keys():
let responseAddressUp = responseAddress.toUpper()
if ownerAddressUp == responseAddressUp:
let member = response.result[responseAddress].toContactsDto()
return CommunityCollectibleOwner(
contactId: member.id,
name: member.displayName,
imageSource: member.image.thumbnail,
collectibleOwner: owner
)
return CommunityCollectibleOwner(collectibleOwner: owner)
)
self.tokenOwnersCache[(chainId, contractAddress)] = communityOwners
let data = CommunityTokenOwnersArgs(chainId: chainId, contractAddress: contractAddress, communityId: communityId, owners: communityOwners)
self.events.emit(SIGNAL_COMMUNITY_TOKEN_OWNERS_FETCHED, data)
proc onRefreshTransferableTokenOwners*(self:Service) {.slot.} =
@ -1042,6 +1066,11 @@ QtObject:
for token in allTokens:
self.fetchCommunityOwners(token)
proc fetchCommunityTokenOwners*(self: Service, communityId: string) =
let tokens = self.getCommunityTokens(communityId)
for token in tokens:
self.fetchCommunityOwners(token)
proc getOwnerToken*(self: Service, communityId: string): CommunityTokenDto =
let communityTokens = self.getCommunityTokens(communityId)
for token in communityTokens:

View File

@ -457,3 +457,6 @@ proc getCheckChannelPermissionResponses*(communityId: string,): RpcResponse[Json
proc getCommunityPublicKeyFromPrivateKey*(communityPrivateKey: string,): RpcResponse[JsonNode] {.raises: [Exception].} =
return callPrivateRPC("getCommunityPublicKeyFromPrivateKey".prefix, %*[communityPrivateKey])
proc getCommunityMembersForWalletAddresses*(communityId: string, chainId: int): RpcResponse[JsonNode] {.raises: [Exception].} =
return callPrivateRPC("getCommunityMembersForWalletAddresses".prefix, %* [communityId, chainId])

View File

@ -46,9 +46,9 @@ SplitView {
isAirdropEnabled: airdropCheckBox.checked
onViewProfileRequested:
logs.logEvent("onViewProfileRequested: " + address)
logs.logEvent("onViewProfileRequested: " + contactId)
onViewMessagesRequested:
logs.logEvent("onViewMessagesRequested: " + address)
logs.logEvent("onViewMessagesRequested: " + contactId)
onAirdropRequested:
logs.logEvent("onAirdropRequested: " + address)
onRemoteDestructRequested:

View File

@ -537,6 +537,14 @@ StackView {
}
}
onViewProfileRequested: {
Global.openProfilePopup(contactId)
}
onViewMessagesRequested: {
// TODO: https://github.com/status-im/status-desktop/issues/11860
console.warn("View Messages is not implemented yet")
}
onBanRequested: {
tokenMasterActionPopup.openPopup(
TokenMasterActionPopup.ActionType.Ban, name)

View File

@ -27,8 +27,8 @@ Control {
readonly property bool empty: countCheckHelper.count === 0
signal viewProfileRequested(string address)
signal viewMessagesRequested(string address)
signal viewProfileRequested(string contactId)
signal viewMessagesRequested(string contactId)
signal airdropRequested(string address)
signal remoteDestructRequested(string name, string address)
signal kickRequested(string name, string address)
@ -134,6 +134,7 @@ Control {
const entry = ModelUtils.get(proxyModel, index)
menu.contactId = entry.contactId
menu.name = entry.name
menu.currentAddress = entry.walletAddress
menu.popup(parent, mouse.x, mouse.y)
@ -146,6 +147,7 @@ Control {
StatusMenu {
id: menu
property string contactId
property string name
property string currentAddress
readonly property bool rawAddress: name === ""
@ -157,7 +159,7 @@ Control {
icon.name: "profile"
enabled: !menu.rawAddress
onTriggered: root.viewProfileRequested(menu.currentAddress)
onTriggered: root.viewProfileRequested(menu.contactId)
}
StatusAction {
@ -165,7 +167,7 @@ Control {
icon.name: "chat"
enabled: !menu.rawAddress
onTriggered: root.viewMessagesRequested(menu.currentAddress)
onTriggered: root.viewMessagesRequested(menu.contactId)
}
StatusAction {

View File

@ -67,6 +67,9 @@ StatusScrollView {
signal airdropRequested(string address)
signal generalAirdropRequested
signal viewProfileRequested(string contactId)
signal viewMessagesRequested(string contactId)
signal remoteDestructRequested(string name, string address)
signal kickRequested(string name, string address)
signal banRequested(string name, string address)
@ -208,6 +211,8 @@ StatusScrollView {
Layout.topMargin: Style.current.padding
Layout.fillWidth: true
onViewProfileRequested: root.viewProfileRequested(contactId)
onViewMessagesRequested: root.viewMessagesRequested(contactId)
onAirdropRequested: root.airdropRequested(address)
onGeneralAirdropRequested: root.generalAirdropRequested()
onRemoteDestructRequested: root.remoteDestructRequested(name, address)

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit c4cd5775db42219444ea6b57c472958c907c636f
Subproject commit 6d3e6d1b5da9d7ee1883065fb731e122721e0065