mirror of
https://github.com/status-im/status-desktop.git
synced 2025-03-04 00:11:12 +00:00
feat(activity): Add navigation to tx details (#13634)
This commit is contained in:
parent
0d4e7bd458
commit
3126973cfc
@ -88,7 +88,7 @@ proc init*(self: Controller) =
|
|||||||
self.communityTokensModule.onOwnerTokenReceived(args.communityId, args.communityName, args.chainId, args.contractAddress)
|
self.communityTokensModule.onOwnerTokenReceived(args.communityId, args.communityName, args.chainId, args.contractAddress)
|
||||||
self.events.on(SIGNAL_COMMUNITY_TOKEN_RECEIVED) do(e: Args):
|
self.events.on(SIGNAL_COMMUNITY_TOKEN_RECEIVED) do(e: Args):
|
||||||
let args = CommunityTokenReceivedArgs(e)
|
let args = CommunityTokenReceivedArgs(e)
|
||||||
self.communityTokensModule.onCommunityTokenReceived(args.name, args.symbol, args.image, args.communityId, args.communityName, $args.amount, args.chainId, args.txHash, args.isFirst, args.tokenType, args.accountName)
|
self.communityTokensModule.onCommunityTokenReceived(args.name, args.symbol, args.image, args.communityId, args.communityName, $args.amount, args.chainId, args.txHash, args.isFirst, args.tokenType, args.accountName, args.accountAddress)
|
||||||
self.events.on(SIGNAL_SET_SIGNER_STATUS) do(e: Args):
|
self.events.on(SIGNAL_SET_SIGNER_STATUS) do(e: Args):
|
||||||
let args = SetSignerArgs(e)
|
let args = SetSignerArgs(e)
|
||||||
self.communityTokensModule.onSetSignerStateChanged(args.communityId, args.chainId, args.transactionHash, args.status)
|
self.communityTokensModule.onSetSignerStateChanged(args.communityId, args.chainId, args.transactionHash, args.status)
|
||||||
|
@ -96,7 +96,7 @@ method removeCommunityToken*(self: AccessInterface, communityId: string, chainId
|
|||||||
method onOwnerTokenReceived*(self: AccessInterface, communityId: string, communityName: string, chainId: int, contractAddress: string) {.base.} =
|
method onOwnerTokenReceived*(self: AccessInterface, communityId: string, communityName: string, chainId: int, contractAddress: string) {.base.} =
|
||||||
raise newException(ValueError, "No implementation available")
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
method onCommunityTokenReceived*(self: AccessInterface, name: string, symbol: string, image: string, communityId: string, communityName: string, balance: string, chainId: int, txHash: string, isFirst: bool, tokenType: int, accountName: string) {.base.} =
|
method onCommunityTokenReceived*(self: AccessInterface, name: string, symbol: string, image: string, communityId: string, communityName: string, balance: string, chainId: int, txHash: string, isFirst: bool, tokenType: int, accountName: string, accountAddress: string) {.base.} =
|
||||||
raise newException(ValueError, "No implementation available")
|
raise newException(ValueError, "No implementation available")
|
||||||
|
|
||||||
method onSendOwnerTokenStateChanged*(self: AccessInterface, chainId: int, transactionHash: string, tokenName: string, status: ContractTransactionStatus) {.base.} =
|
method onSendOwnerTokenStateChanged*(self: AccessInterface, chainId: int, transactionHash: string, tokenName: string, status: ContractTransactionStatus) {.base.} =
|
||||||
|
@ -334,8 +334,8 @@ method onAirdropStateChanged*(self: Module, communityId: string, tokenName: stri
|
|||||||
method onOwnerTokenReceived*(self: Module, communityId: string, communityName: string, chainId: int, contractAddress: string) =
|
method onOwnerTokenReceived*(self: Module, communityId: string, communityName: string, chainId: int, contractAddress: string) =
|
||||||
self.view.emitOwnerTokenReceived(communityId, communityName, chainId, contractAddress)
|
self.view.emitOwnerTokenReceived(communityId, communityName, chainId, contractAddress)
|
||||||
|
|
||||||
method onCommunityTokenReceived*(self: Module, name: string, symbol: string, image: string, communityId: string, communityName: string, balance: string, chainId: int, txHash: string, isFirst: bool, tokenType: int, accountName: string) =
|
method onCommunityTokenReceived*(self: Module, name: string, symbol: string, image: string, communityId: string, communityName: string, balance: string, chainId: int, txHash: string, isFirst: bool, tokenType: int, accountName: string, accountAddress: string) =
|
||||||
self.view.emitCommunityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, accountName)
|
self.view.emitCommunityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, accountName, accountAddress)
|
||||||
|
|
||||||
method onSetSignerStateChanged*(self: Module, communityId: string, chainId: int, transactionHash: string, status: ContractTransactionStatus) =
|
method onSetSignerStateChanged*(self: Module, communityId: string, chainId: int, transactionHash: string, status: ContractTransactionStatus) =
|
||||||
let communityDto = self.controller.getCommunityById(communityId)
|
let communityDto = self.controller.getCommunityById(communityId)
|
||||||
|
@ -55,7 +55,7 @@ QtObject:
|
|||||||
proc burnFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int, responseId: string) {.signal.}
|
proc burnFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int, responseId: string) {.signal.}
|
||||||
proc setSignerFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int, responseId: string) {.signal.}
|
proc setSignerFeeUpdated*(self: View, ethCurrency: QVariant, fiatCurrency: QVariant, errorCode: int, responseId: string) {.signal.}
|
||||||
proc ownerTokenReceived*(self: View, communityId: string, communityName: string, chainId: int, contractAddress: string) {.signal.}
|
proc ownerTokenReceived*(self: View, communityId: string, communityName: string, chainId: int, contractAddress: string) {.signal.}
|
||||||
proc communityTokenReceived*(self: View, name: string, symbol: string, image: string, communityId: string, communityName: string, balance: string, chainId: int, txHash: string, isFirst: bool, tokenType: int, accountName: string) {.signal.}
|
proc communityTokenReceived*(self: View, name: string, symbol: string, image: string, communityId: string, communityName: string, balance: string, chainId: int, txHash: string, isFirst: bool, tokenType: int, accountName: string, accountAddress: string) {.signal.}
|
||||||
proc setSignerStateChanged*(self: View, communityId: string, communityName: string, status: int, url: string) {.signal.}
|
proc setSignerStateChanged*(self: View, communityId: string, communityName: string, status: int, url: string) {.signal.}
|
||||||
proc ownershipNodeLost*(self: View, communityId: string, communityName: string) {.signal.}
|
proc ownershipNodeLost*(self: View, communityId: string, communityName: string) {.signal.}
|
||||||
proc sendOwnerTokenStateChanged*(self: View, tokenName: string, status: int, url: string) {.signal.}
|
proc sendOwnerTokenStateChanged*(self: View, tokenName: string, status: int, url: string) {.signal.}
|
||||||
@ -117,8 +117,8 @@ QtObject:
|
|||||||
proc emitOwnerTokenReceived*(self: View, communityId: string, communityName: string, chainId: int, contractAddress: string) =
|
proc emitOwnerTokenReceived*(self: View, communityId: string, communityName: string, chainId: int, contractAddress: string) =
|
||||||
self.ownerTokenReceived(communityId, communityName, chainId, contractAddress)
|
self.ownerTokenReceived(communityId, communityName, chainId, contractAddress)
|
||||||
|
|
||||||
proc emitCommunityTokenReceived*(self: View, name: string, symbol: string, image: string, communityId: string, communityName: string, balance: string, chainId: int, txHash: string, isFirst: bool, tokenType: int, accountName: string) =
|
proc emitCommunityTokenReceived*(self: View, name: string, symbol: string, image: string, communityId: string, communityName: string, balance: string, chainId: int, txHash: string, isFirst: bool, tokenType: int, accountName: string, accountAddress: string) =
|
||||||
self.communityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, accountName)
|
self.communityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, accountName, accountAddress)
|
||||||
|
|
||||||
proc emitSetSignerStateChanged*(self: View, communityId: string, communityName: string, status: int, url: string) =
|
proc emitSetSignerStateChanged*(self: View, communityId: string, communityName: string, status: int, url: string) =
|
||||||
self.setSignerStateChanged(communityId, communityName, status, url)
|
self.setSignerStateChanged(communityId, communityName, status, url)
|
||||||
|
@ -128,6 +128,12 @@ QtObject:
|
|||||||
read = getHasMore
|
read = getHasMore
|
||||||
notify = hasMoreChanged
|
notify = hasMoreChanged
|
||||||
|
|
||||||
|
proc getIndex*(self: Model, txHash: string): int {.slot.} =
|
||||||
|
for i, e in self.entries:
|
||||||
|
if e.getId() == txHash:
|
||||||
|
return i
|
||||||
|
return -1
|
||||||
|
|
||||||
proc refreshItemsContainingAddress*(self: Model, address: string) =
|
proc refreshItemsContainingAddress*(self: Model, address: string) =
|
||||||
for i in 0..self.entries.high:
|
for i in 0..self.entries.high:
|
||||||
if cmpIgnoreCase(self.entries[i].getSender(), address) == 0 or
|
if cmpIgnoreCase(self.entries[i].getSender(), address) == 0 or
|
||||||
|
@ -89,10 +89,15 @@ Item {
|
|||||||
if (leftPanelSelection === WalletLayout.LeftPanelSelection.SavedAddresses) {
|
if (leftPanelSelection === WalletLayout.LeftPanelSelection.SavedAddresses) {
|
||||||
d.displaySavedAddresses()
|
d.displaySavedAddresses()
|
||||||
} else {
|
} else {
|
||||||
|
let address = data.address ?? ""
|
||||||
if (leftPanelSelection === WalletLayout.LeftPanelSelection.AllAddresses) {
|
if (leftPanelSelection === WalletLayout.LeftPanelSelection.AllAddresses) {
|
||||||
d.displayAllAddresses()
|
d.displayAllAddresses()
|
||||||
} else if (leftPanelSelection === WalletLayout.LeftPanelSelection.Address) {
|
} else if (leftPanelSelection === WalletLayout.LeftPanelSelection.Address) {
|
||||||
d.displayAddress(address)
|
if (!!address) {
|
||||||
|
d.displayAddress(address)
|
||||||
|
} else {
|
||||||
|
d.displayAllAddresses()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rightPanelSelection !== WalletLayout.RightPanelSelection.Collectibles &&
|
if (rightPanelSelection !== WalletLayout.RightPanelSelection.Collectibles &&
|
||||||
@ -105,10 +110,14 @@ Item {
|
|||||||
rightPanelStackView.currentItem.resetView()
|
rightPanelStackView.currentItem.resetView()
|
||||||
rightPanelStackView.currentItem.currentTabIndex = rightPanelSelection
|
rightPanelStackView.currentItem.currentTabIndex = rightPanelSelection
|
||||||
|
|
||||||
|
let txHash = data.txHash?? ""
|
||||||
let savedAddress = data.savedAddress?? ""
|
let savedAddress = data.savedAddress?? ""
|
||||||
if (!!savedAddress) {
|
if (!!savedAddress) {
|
||||||
RootStore.currentActivityFiltersStore.resetAllFilters()
|
RootStore.currentActivityFiltersStore.resetAllFilters()
|
||||||
RootStore.currentActivityFiltersStore.toggleSavedAddress(savedAddress)
|
RootStore.currentActivityFiltersStore.toggleSavedAddress(savedAddress)
|
||||||
|
} else if (!!txHash) {
|
||||||
|
RootStore.currentActivityFiltersStore.resetAllFilters()
|
||||||
|
RootStore.currentActivityFiltersStore.displayTxDetails(txHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,8 @@ QtObject {
|
|||||||
recentsFilters.length !== 0 ||
|
recentsFilters.length !== 0 ||
|
||||||
savedAddressFilters.length !== 0
|
savedAddressFilters.length !== 0
|
||||||
|
|
||||||
|
signal displayTxDetails(string txHash)
|
||||||
|
|
||||||
readonly property QtObject _d: QtObject {
|
readonly property QtObject _d: QtObject {
|
||||||
id: d
|
id: d
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ QtObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Community token received in the user wallet:
|
// Community token received in the user wallet:
|
||||||
function onCommunityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, walletAccountName) {
|
function onCommunityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, walletAddress, walletAccountName) {
|
||||||
|
|
||||||
// Some error control:
|
// Some error control:
|
||||||
if(tokenType !== Constants.TokenType.ERC20 && tokenType !== Constants.TokenType.ERC721) {
|
if(tokenType !== Constants.TokenType.ERC20 && tokenType !== Constants.TokenType.ERC721) {
|
||||||
@ -155,7 +155,8 @@ QtObject {
|
|||||||
tokenName: name,
|
tokenName: name,
|
||||||
tokenSymbol: symbol,
|
tokenSymbol: symbol,
|
||||||
tokenImage: image,
|
tokenImage: image,
|
||||||
tokenAmount: balance
|
tokenAmount: balance,
|
||||||
|
walletAddress: walletAddress
|
||||||
}
|
}
|
||||||
|
|
||||||
if(isFirst) {
|
if(isFirst) {
|
||||||
@ -217,17 +218,18 @@ QtObject {
|
|||||||
root.sendModalPopup.open()
|
root.sendModalPopup.open()
|
||||||
return
|
return
|
||||||
case ToastsManager.ActionType.ViewTransactionDetails:
|
case ToastsManager.ActionType.ViewTransactionDetails:
|
||||||
var txHash = ""
|
|
||||||
if(actionData) {
|
if(actionData) {
|
||||||
var parsedData = JSON.parse(actionData)
|
var parsedData = JSON.parse(actionData)
|
||||||
txHash = parsedData.txHash
|
const txHash = parsedData.txHash
|
||||||
|
const walletAddress = parsedData.walletAddress
|
||||||
Global.changeAppSectionBySectionType(Constants.appSection.wallet,
|
Global.changeAppSectionBySectionType(Constants.appSection.wallet,
|
||||||
WalletLayout.LeftPanelSelection.AllAddresses,
|
WalletLayout.LeftPanelSelection.Address,
|
||||||
WalletLayout.RightPanelSelection.Activity)
|
WalletLayout.RightPanelSelection.Activity,
|
||||||
// TODO: Final navigation to the specific transaction entry --> {transaction: txHash}) --> Issue #13249
|
{address: walletAddress,
|
||||||
|
txHash: txHash})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
console.warn("Unexpected transaction hash while trying to navigate to the details page: " + txHash)
|
console.warn("Unexpected transaction hash while trying to navigate to the details page")
|
||||||
return
|
return
|
||||||
case ToastsManager.ActionType.OpenFirstCommunityTokenPopup:
|
case ToastsManager.ActionType.OpenFirstCommunityTokenPopup:
|
||||||
if(actionData) {
|
if(actionData) {
|
||||||
|
@ -46,8 +46,8 @@ ActivityNotificationBase {
|
|||||||
property int tokenType: root.tokenData.tokenType
|
property int tokenType: root.tokenData.tokenType
|
||||||
|
|
||||||
// Wallet related:
|
// Wallet related:
|
||||||
property string walletAccountName: !!root.store ? root.store.walletStore.getNameForWalletAddress(root.tokenData.walletAddress) : ""
|
|
||||||
property string txHash: root.tokenData.txHash
|
property string txHash: root.tokenData.txHash
|
||||||
|
property string walletAccountName: !!root.store && !root.isFirstTokenReceived ? root.store.walletStore.getNameForWalletAddress(root.tokenData.walletAddress) : ""
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
@ -57,8 +57,13 @@ ActivityNotificationBase {
|
|||||||
readonly property string ctaText: root.isFirstTokenReceived ? qsTr("Learn more") : qsTr("Transaction details")
|
readonly property string ctaText: root.isFirstTokenReceived ? qsTr("Learn more") : qsTr("Transaction details")
|
||||||
readonly property string title: root.isFirstTokenReceived ? (root.isAssetType ? qsTr("You received your first community asset") : qsTr("You received your first community collectible")) :
|
readonly property string title: root.isFirstTokenReceived ? (root.isAssetType ? qsTr("You received your first community asset") : qsTr("You received your first community collectible")) :
|
||||||
qsTr("Tokens received")
|
qsTr("Tokens received")
|
||||||
readonly property string info: root.isFirstTokenReceived ? qsTr("%1 %2 was airdropped to you from the %3 community").arg(root.tokenAmount).arg(d.formattedTokenName).arg(root.communityName) :
|
readonly property string info: {
|
||||||
qsTr("You were airdropped %1 %2 from %3 to %4").arg(root.tokenAmount).arg(root.tokenName).arg(root.communityName).arg(root.walletAccountName)
|
if (root.isFirstTokenReceived) {
|
||||||
|
return qsTr("%1 %2 was airdropped to you from the %3 community").arg(root.tokenAmount).arg(d.formattedTokenName).arg(root.communityName)
|
||||||
|
} else {
|
||||||
|
return qsTr("You were airdropped %1 %2 from %3 to %4").arg(root.tokenAmount).arg(root.tokenName).arg(root.communityName).arg(root.walletAccountName)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyComponent: RowLayout {
|
bodyComponent: RowLayout {
|
||||||
@ -121,9 +126,10 @@ ActivityNotificationBase {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
Global.changeAppSectionBySectionType(Constants.appSection.wallet,
|
Global.changeAppSectionBySectionType(Constants.appSection.wallet,
|
||||||
WalletLayout.LeftPanelSelection.AllAddresses,
|
WalletLayout.LeftPanelSelection.Address,
|
||||||
WalletLayout.RightPanelSelection.Activity)
|
WalletLayout.RightPanelSelection.Activity,
|
||||||
// TODO: Final navigation to the specific transaction entry --> {transaction: txHash}) --> Issue #13249
|
{address: root.tokenData.walletAddress,
|
||||||
|
txHash: root.txHash})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -38,7 +38,8 @@ QtObject {
|
|||||||
string communityId, string communityName,
|
string communityId, string communityName,
|
||||||
string balance, int chainId,
|
string balance, int chainId,
|
||||||
string txHash, bool isFirst,
|
string txHash, bool isFirst,
|
||||||
int tokenType, string walletAccountName)
|
int tokenType, string walletAccountName,
|
||||||
|
string walletAddress)
|
||||||
|
|
||||||
// Minting tokens:
|
// Minting tokens:
|
||||||
function deployCollectible(communityId, collectibleItem)
|
function deployCollectible(communityId, collectibleItem)
|
||||||
@ -136,8 +137,8 @@ QtObject {
|
|||||||
root.ownerTokenReceived(communityId, communityName)
|
root.ownerTokenReceived(communityId, communityName)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onCommunityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, walletAccountName) {
|
function onCommunityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, walletAccountName, walletAccountName, walletAddress) {
|
||||||
root.communityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, walletAccountName)
|
root.communityTokenReceived(name, symbol, image, communityId, communityName, balance, chainId, txHash, isFirst, tokenType, walletAccountName, walletAccountName, walletAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
function onSetSignerStateChanged(communityId, communityName, status, url) {
|
function onSetSignerStateChanged(communityId, communityName, status, url) {
|
||||||
|
@ -48,6 +48,10 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
d.openTxDetailsHash = ""
|
||||||
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
if (RootStore.transactionActivityStatus.isFilterDirty) {
|
if (RootStore.transactionActivityStatus.isFilterDirty) {
|
||||||
WalletStores.RootStore.currentActivityFiltersStore.applyAllFilters()
|
WalletStores.RootStore.currentActivityFiltersStore.applyAllFilters()
|
||||||
@ -69,6 +73,16 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: WalletStores.RootStore.currentActivityFiltersStore
|
||||||
|
enabled: root.visible
|
||||||
|
function onDisplayTxDetails(txHash) {
|
||||||
|
if (!d.openTxDetails(txHash)) {
|
||||||
|
d.openTxDetailsHash = txHash
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QtObject {
|
QtObject {
|
||||||
id: d
|
id: d
|
||||||
readonly property bool isInitialLoading: RootStore.loadingHistoryTransactions && transactionListRoot.count === 0
|
readonly property bool isInitialLoading: RootStore.loadingHistoryTransactions && transactionListRoot.count === 0
|
||||||
@ -84,6 +98,23 @@ ColumnLayout {
|
|||||||
d.lastRefreshTime = Date.now()
|
d.lastRefreshTime = Date.now()
|
||||||
newTransactions.visible = false
|
newTransactions.visible = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
property string openTxDetailsHash
|
||||||
|
|
||||||
|
function openTxDetails(txHash) {
|
||||||
|
// Prevent opening details when loading, that will invalidate the model data
|
||||||
|
if (RootStore.loadingHistoryTransactions) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = WalletStores.RootStore.currentActivityFiltersStore.transactionsList.getIndex(txHash)
|
||||||
|
if (index < 0)
|
||||||
|
return false
|
||||||
|
const entry = transactionListRoot.itemAtIndex(index)
|
||||||
|
root.selectedTransaction = Qt.binding(() => entry.modelData)
|
||||||
|
root.launchTransactionDetail(index)
|
||||||
|
return true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
@ -142,6 +173,16 @@ ColumnLayout {
|
|||||||
objectName: "walletAccountTransactionList"
|
objectName: "walletAccountTransactionList"
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
onCountChanged: {
|
||||||
|
if (!!d.openTxDetailsHash && root.visible) {
|
||||||
|
if (d.openTxDetails(d.openTxDetailsHash)) {
|
||||||
|
d.openTxDetailsHash = ""
|
||||||
|
} else {
|
||||||
|
RootStore.fetchMoreTransactions()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
model: SortFilterProxyModel {
|
model: SortFilterProxyModel {
|
||||||
id: txModel
|
id: txModel
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user