feat(@desktop/wallet): Add Token Detail View

fixes #6491
This commit is contained in:
Khushboo Mehta 2022-08-08 23:12:12 +02:00 committed by Khushboo-dev-cpp
parent 41694a6e09
commit 0515152bd7
24 changed files with 805 additions and 83 deletions

View File

@ -50,6 +50,16 @@ proc setAssets(self: Module, tokens: seq[WalletTokenDto]) =
t.enabledNetworkBalance.currencybalance,
t.visible,
toSeq(t.balancesPerChain.values),
t.description,
t.assetWebsiteUrl,
t.builtOn,
t.smartContractAddress,
t.marketCap,
t.highDay,
t.lowDay,
t.changePctHour,
t.changePctDay,
t.changePct24hour,
)
items.add(item)
@ -91,4 +101,4 @@ proc onTokensRebuilt(self: Module, accountsTokens: OrderedTable[string, seq[Wall
let walletAccount = self.controller.getWalletAccount(self.currentAccountIndex)
if not accountsTokens.contains(walletAccount.address):
return
self.setAssets(accountsTokens[walletAccount.address])
self.setAssets(accountsTokens[walletAccount.address])

View File

@ -114,3 +114,27 @@ QtObject:
self.items = items
self.endResetModel()
self.countChanged()
proc getChainColor*(self: Model, chainId: int): string {.slot.} =
for item in self.items:
if(item.getChainId() == chainId):
return item.getChainColor()
return ""
proc getIconUrl*(self: Model, chainId: int): string {.slot.} =
for item in self.items:
if(item.getChainId() == chainId):
return item.getIconURL()
return ""
proc getNetworkIconUrl*(self: Model, shortName: string): string {.slot.} =
for item in self.items:
if(item.getShortName() == toLowerAscii(shortName)):
return item.getIconURL()
return ""
proc getNetworkName*(self: Model, shortName: string): string {.slot.} =
for item in self.items:
if(item.getShortName() == toLowerAscii(shortName)):
return item.getChainName()
return ""

View File

@ -54,6 +54,16 @@ method refreshWalletAccounts*(self: Module) =
t.enabledNetworkBalance.currencyBalance,
t.visible,
toSeq(t.balancesPerChain.values),
t.description,
t.assetWebsiteUrl,
t.builtOn,
t.smartContractAddress,
t.marketCap,
t.highDay,
t.lowDay,
t.changePctHour,
t.changePctDay,
t.changePct24hour,
))
)

View File

@ -81,6 +81,16 @@ proc setAssetsAndBalance(self: Module, tokens: seq[WalletTokenDto]) =
t.enabledNetworkBalance.currencybalance,
t.visible,
toSeq(t.balancesPerChain.values),
t.description,
t.assetWebsiteUrl,
t.builtOn,
t.smartContractAddress,
t.marketCap,
t.highDay,
t.lowDay,
t.changePctHour,
t.changePctDay,
t.changePct24hour,
)
items.add(item)
totalCurrencyBalanceForAllAssets += t.enabledNetworkBalance.currencybalance

View File

@ -13,6 +13,16 @@ type
enabledNetworkBalance: float
networkVisible: bool
balances: balance_model.BalanceModel
description: string
assetWebsiteUrl: string
builtOn: string
smartContractAddress: string
marketCap: string
highDay: string
lowDay: string
changePctHour: string
changePctDay: string
changePct24hour: string
proc initItem*(
name, symbol: string,
@ -21,7 +31,17 @@ proc initItem*(
enabledNetworkBalance: float,
enabledNetworkCurrencyBalance: float,
networkVisible: bool,
balances: seq[BalanceDto]
balances: seq[BalanceDto],
description: string,
assetWebsiteUrl: string,
builtOn: string,
smartContractAddress: string,
marketCap: string,
highDay: string,
lowDay: string,
changePctHour: string,
changePctDay: string,
changePct24hour: string,
): Item =
result.name = name
result.symbol = symbol
@ -32,6 +52,16 @@ proc initItem*(
result.networkVisible = networkVisible
result.balances = balance_model.newModel()
result.balances.setItems(balances)
result.description = description
result.assetWebsiteUrl = assetWebsiteUrl
result.builtOn = builtOn
result.smartContractAddress = smartContractAddress
result.marketCap = marketCap
result.highDay = highDay
result.lowDay = lowDay
result.changePctHour = changePctHour
result.changePctDay = changePctDay
result.changePct24hour = changePct24hour
proc `$`*(self: Item): string =
result = fmt"""AllTokensItem(
@ -42,6 +72,16 @@ proc `$`*(self: Item): string =
enabledNetworkBalance: {self.enabledNetworkBalance},
enabledNetworkCurrencyBalance: {self.enabledNetworkCurrencyBalance},
networkVisible: {self.networkVisible},
description: {self.description},
assetWebsiteUrl: {self.assetWebsiteUrl}
builtOn: {self.builtOn}
smartContractAddress: {self.smartContractAddress}
marketCap: {self.marketCap},
highDay: {self.highDay},
lowDay: {self.lowDay},
changePctHour: {self.changePctHour},
changePctDay: {self.changePctDay},
changePct24hour: {self.changePct24hour},
]"""
proc getName*(self: Item): string =
@ -66,4 +106,34 @@ proc getNetworkVisible*(self: Item): bool =
return self.networkVisible
proc getBalances*(self: Item): balance_model.BalanceModel =
return self.balances
return self.balances
proc getDescription*(self: Item): string =
return self.description
proc getAssetWebsiteUrl*(self: Item): string =
return self.assetWebsiteUrl
proc getBuiltOn*(self: Item): string =
return self.builtOn
proc getSmartContractAddress*(self: Item): string =
return self.smartContractAddress
proc getMarketCap*(self: Item): string =
return self.marketCap
proc getHighDay*(self: Item): string =
return self.highDay
proc getLowDay*(self: Item): string =
return self.lowDay
proc getChangePctHour*(self: Item): string =
return self.changePctHour
proc getChangePctDay*(self: Item): string =
return self.changePctDay
proc getChangePct24hour*(self: Item): string =
return self.changePct24hour

View File

@ -12,6 +12,16 @@ type
EnabledNetworkBalance
NetworkVisible
Balances
Description
AssetWebsiteUrl
BuiltOn
SmartContractAddress
MarketCap
HighDay
LowDay
ChangePctHour
ChangePctDay
ChangePct24hour
QtObject:
type
@ -55,6 +65,16 @@ QtObject:
ModelRole.EnabledNetworkBalance.int:"enabledNetworkBalance",
ModelRole.NetworkVisible.int:"networkVisible",
ModelRole.Balances.int:"balances",
ModelRole.Description.int:"description",
ModelRole.AssetWebsiteUrl.int:"assetWebsiteUrl",
ModelRole.BuiltOn.int:"builtOn",
ModelRole.SmartContractAddress.int:"smartContractAddress",
ModelRole.MarketCap.int:"marketCap",
ModelRole.HighDay.int:"highDay",
ModelRole.LowDay.int:"lowDay",
ModelRole.ChangePctHour.int:"changePctHour",
ModelRole.ChangePctDay.int:"changePctDay",
ModelRole.ChangePct24hour.int:"changePct24hour",
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -84,6 +104,27 @@ QtObject:
result = newQVariant(item.getNetworkVisible())
of ModelRole.Balances:
result = newQVariant(item.getBalances())
of ModelRole.Description:
result = newQVariant(item.getDescription())
of ModelRole.AssetWebsiteUrl:
result = newQVariant(item.getAssetWebsiteUrl())
of ModelRole.BuiltOn:
result = newQVariant(item.getBuiltOn())
of ModelRole.SmartContractAddress:
result = newQVariant(item.getSmartContractAddress())
of ModelRole.MarketCap:
result = newQVariant(item.getMarketCap())
of ModelRole.HighDay:
result = newQVariant(item.getHighDay())
of ModelRole.LowDay:
result = newQVariant(item.getLowDay())
of ModelRole.ChangePctHour:
result = newQVariant(item.getChangePctHour())
of ModelRole.ChangePctDay:
result = newQVariant(item.getChangePctDay())
of ModelRole.ChangePct24hour:
result = newQVariant(item.getChangePct24hour())
proc rowData(self: Model, index: int, column: string): string {.slot.} =
if (index >= self.items.len):
@ -97,6 +138,17 @@ QtObject:
of "enabledNetworkCurrencyBalance": result = $item.getEnabledNetworkCurrencyBalance()
of "enabledNetworkBalance": result = $item.getEnabledNetworkBalance()
of "networkVisible": result = $item.getNetworkVisible()
of "description": result = $item.getDescription()
of "assetWebsiteUrl": result = $item.getAssetWebsiteUrl()
of "builtOn": result = $item.getBuiltOn()
of "smartContractAddress": result = $item.getSmartContractAddress()
of "marketCap": result = $item.getMarketCap()
of "highDay": result = $item.getHighDay()
of "lowDay": result = $item.getLowDay()
of "changePctHour": result = $item.getChangePctHour()
of "changePctDay": result = $item.getChangePctDay()
of "changePct24hour": result = $item.getChangePct24hour()
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()

View File

@ -30,7 +30,7 @@ let DEFAULT_TORRENT_CONFIG_TORRENTDIR* = joinPath(main_constants.defaultDataDir(
let NETWORKS* = %* [
{
"chainId": 1,
"chainName": "Mainnet",
"chainName": "Ethereum Mainnet",
"rpcUrl": "https://mainnet.infura.io/v3/" & INFURA_TOKEN_RESOLVED,
"blockExplorerUrl": "https://etherscan.io/",
"iconUrl": "network/Network=Ethereum",

View File

@ -17,12 +17,54 @@ type
color*: string
isCustom* {.dontSerialize.}: bool
isVisible* {.dontSerialize.}: bool
description* :string
assetWebsiteUrl*: string
builtOn*: string
smartContractAddress*: string
marketCap*: string
highDay*: string
lowDay*: string
changePctHour*: string
changePctDay*: string
changePct24hour*: string
proc newTokenDto*(
name: string, chainId: int, address: Address, symbol: string, decimals: int, hasIcon: bool, isCustom: bool = false
name: string,
chainId: int,
address: Address,
symbol: string,
decimals: int,
hasIcon: bool,
isCustom: bool = false,
description: string = "",
assetWebsiteUrl: string = "",
builtOn: string = "",
smartContractAddress: string = "",
marketCap: string = "",
highDay: string = "",
lowDay: string = "",
changePctHour: string = "",
changePctDay: string = "",
changePct24hour: string = "",
): TokenDto =
return TokenDto(
name: name, chainId: chainId, address: address, symbol: symbol, decimals: decimals, hasIcon: hasIcon, isCustom: isCustom
name: name,
chainId: chainId,
address: address,
symbol: symbol,
decimals: decimals,
hasIcon: hasIcon,
isCustom: isCustom,
description: description,
assetWebsiteUrl: assetWebsiteUrl,
builtOn: builtOn,
smartContractAddress: smartContractAddress,
marketCap: marketCap,
highDay: highDay,
lowDay: lowDay,
changePctHour: changePctHour,
changePctDay: changePctDay,
changePct24hour: changePct24hour,
)
proc toTokenDto*(jsonObj: JsonNode, isVisible: bool, hasIcon: bool = false, isCustom: bool = true): TokenDto =
@ -36,8 +78,18 @@ proc toTokenDto*(jsonObj: JsonNode, isVisible: bool, hasIcon: bool = false, isCu
discard jsonObj.getProp("symbol", result.symbol)
discard jsonObj.getProp("decimals", result.decimals)
discard jsonObj.getProp("color", result.color)
discard jsonObj.getProp("description", result.description)
discard jsonObj.getProp("assetWebsiteUrl", result.assetWebsiteUrl)
discard jsonObj.getProp("builtOn", result.builtOn)
discard jsonObj.getProp("smartContractAddress", result.smartContractAddress)
discard jsonObj.getProp("marketCap", result.marketCap)
discard jsonObj.getProp("highDay", result.highDay)
discard jsonObj.getProp("lowDay", result.lowDay)
discard jsonObj.getProp("changePctHour", result.changePctHour)
discard jsonObj.getProp("changePctDay", result.changePctDay)
discard jsonObj.getProp("changePct24hour", result.changePct24hour)
result.isVisible = isVisible
proc addressAsString*(self: TokenDto): string =
return $self.address
return $self.address

View File

@ -169,6 +169,38 @@ proc fetchPrices(networkSymbols: seq[string], allTokens: seq[TokenDto], currency
except Exception as e:
error "error fetching prices: ", message = e.msg
proc getMarketValues(networkSymbols: seq[string], allTokens: seq[TokenDto], currency: string): Table[string, Table[string, string]] =
let allSymbols = prepareSymbols(networkSymbols, allTokens)
for symbols in allSymbols:
if symbols.len == 0:
continue
try:
let response = backend.fetchMarketValues(symbols, currency)
for (symbol, marketValue) in response.result.pairs:
var marketValues: Table[string, string] = initTable[string, string]()
for (key, value) in marketValue.pairs:
marketValues[key] = value.getStr()
result[symbol] = marketValues
except Exception as e:
error "error fetching markey values: ", message = e.msg
proc getTokenDetails(networkSymbols: seq[string], allTokens: seq[TokenDto]): Table[string, Table[string, string]] =
let allSymbols = prepareSymbols(networkSymbols, allTokens)
for symbols in allSymbols:
if symbols.len == 0:
continue
try:
let response = backend.fetchTokenDetails(symbols)
for (symbol, tokenDetail) in response.result.pairs:
var tokenDetails: Table[string, string] = initTable[string, string]()
for (key, value) in tokenDetail.pairs:
tokenDetails[key] = value.getStr()
result[symbol] = tokenDetails
except Exception as e:
error "error fetching markey values: ", message = e.msg
proc getTokensBalances(walletAddresses: seq[string], allTokens: seq[TokenDto]): JsonNode =
try:
result = newJObject()
@ -218,6 +250,8 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
allTokens = deduplicate(allTokens)
var prices = fetchPrices(networkSymbols, allTokens, arg.currency)
var marketValues = getMarketValues(networkSymbols, allTokens, arg.currency)
var tokenDetails = getTokenDetails(networkSymbols, allTokens)
let tokenBalances = getTokensBalances(arg.walletAddresses, allTokens)
var builtTokensPerAccount = %*{ }
@ -252,6 +286,21 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
var totalTokenBalance: BalanceDto
totalTokenBalance.balance = toSeq(balancesPerChain.values).map(x => x.balance).foldl(a + b)
totalTokenBalance.currencyBalance = totalTokenBalance.balance * prices[networkDto.nativeCurrencySymbol]
var marketCap: string = ""
var highDay: string = ""
var lowDay: string = ""
var changePctHour: string = ""
var changePctDay: string = ""
var changePct24hour: string = ""
if(marketValues.hasKey(networkDto.nativeCurrencySymbol)):
marketCap = marketValues[networkDto.nativeCurrencySymbol]["MKTCAP"]
highDay = marketValues[networkDto.nativeCurrencySymbol]["HIGHDAY"]
lowDay = marketValues[networkDto.nativeCurrencySymbol]["LOWDAY"]
changePctHour = marketValues[networkDto.nativeCurrencySymbol]["CHANGEPCTHOUR"]
changePctDay = marketValues[networkDto.nativeCurrencySymbol]["CHANGEPCTDAY"]
changePct24hour = marketValues[networkDto.nativeCurrencySymbol]["CHANGEPCT24HOUR"]
builtTokens.add(WalletTokenDto(
name: networkDto.nativeCurrencyName,
symbol: networkDto.nativeCurrencySymbol,
@ -263,6 +312,16 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
enabledNetworkBalance: enabledNetworkBalance,
balancesPerChain: balancesPerChain,
visible: networkDto.enabled,
description: "Ethereum is a decentralized platform that runs smart contracts (applications that run exactly as programmed without any possibility of downtime, censorship, fraud or third party interference). In the Ethereum protocol and blockchain, there is a price for each operation. In order to have anything transferred or executed by the network, you have to consume or burn Gas. Ethereums native cryptocurrency is Ether (ETH) and it is used to pay for computation time and transaction fees.The introductory whitepaper was originally published in 2013 by Vitalik Buterin, the founder of Ethereum, the project was crowdfunded during August 2014 by fans all around the world and launched in 2015. Ethereum is developed and maintained by ETHDEV with contributions from minds across the globe. There is an Ecosystem Support Program which is a branch of the Ethereum Foundation focused on supporting projects and entities within the greater Ethereum community to promote the success and growth of the ecosystem. Multiple startups work with the Ethereum blockchain covering areas in: DeFi, NFTs, Ethereum Name Service, Wallets, Scaling, etc.The launch of Ethereum is a process divided into 4 main phases: Frontier, Homestead, Metropolis and Serenity.Ethereum 2.0, also known as Serenity, is the final phase of Ethereum, it aims to solve the decentralized scaling challenge. A naive way to solve Ethereum's problems would be to make it more centralized. But decentralization is too important, as it gives Ethereum censorship resistance, openness, data privacy and near-unbreakable security.The Eth2 upgrades will make Ethereum scalable, secure, and decentralized. Sharding will make Ethereum more scalable by increasing transactions per second while decreasing the power needed to run a node and validate the chain. The beacon chain will make Ethereum secure by coordinating validators across shards. And staking will lower the barrier to participation, creating a larger more decentralized network.The beacon chain will also introduce proof-of-stake to Ethereum. Ethereum is moving to the proof-of-stake (PoS) consensus mechanism from proof-of-work (PoW). This was always the plan as it's a key part of the community's strategy to scale Ethereum via the Eth2 upgrades. However, getting PoS right is a big technical challenge and not as straightforward as using PoW to reach consensus across the networkKeep up with Ethereum upgradesFor ETH holders and Dapp users, this has no impact whatsoever, however, for users wishing to get involved, there are ways to participate in Ethereum and future Eth2-related efforts. Get involved in Eth 2.0Blockchain data provided by: Etherchain (Main Source), Blockchair (Backup), and Etherscan (Total Supply only).",
assetWebsiteUrl: "https://www.ethereum.org/",
builtOn: "",
smartContractAddress: "",
marketCap: marketCap,
highDay: highDay,
lowDay: lowDay,
changePctHour: changePctHour,
changePctDay: changePctDay,
changePct24hour: changePct24hour,
)
)
@ -294,6 +353,32 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
var totalTokenBalance: BalanceDto
totalTokenBalance.balance = toSeq(balancesPerChain.values).map(x => x.balance).foldl(a + b)
totalTokenBalance.currencyBalance = totalTokenBalance.balance * prices[tokenDto.symbol]
var marketCap: string = ""
var highDay: string = ""
var lowDay: string = ""
var changePctHour: string = ""
var changePctDay: string = ""
var changePct24hour: string = ""
var description: string = ""
var assetWebsiteUrl: string = ""
var builtOn: string = ""
var smartContractAddress: string = ""
if(tokenDetails.hasKey(tokenDto.symbol)):
description = tokenDetails[tokenDto.symbol]["Description"]
assetWebsiteUrl = tokenDetails[tokenDto.symbol]["AssetWebsiteUrl"]
builtOn = tokenDetails[tokenDto.symbol]["BuiltOn"]
smartContractAddress = tokenDetails[tokenDto.symbol]["SmartContractAddress"]
if(marketValues.hasKey(tokenDto.symbol)):
marketCap = marketValues[tokenDto.symbol]["MKTCAP"]
highDay = marketValues[tokenDto.symbol]["HIGHDAY"]
lowDay = marketValues[tokenDto.symbol]["LOWDAY"]
changePctHour = marketValues[tokenDto.symbol]["CHANGEPCTHOUR"]
changePctDay = marketValues[tokenDto.symbol]["CHANGEPCTDAY"]
changePct24hour = marketValues[tokenDto.symbol]["CHANGEPCT24HOUR"]
let tokenDescription = description.multiReplace([("|", ""),("Facebook",""),("Telegram",""),("Discord",""),("Youtube",""),("YouTube",""),("Instagram",""),("Reddit",""),("Github",""),("GitHub",""),("Whitepaper",""),("Medium",""),("Weibo",""),("LinkedIn",""),("Litepaper",""),("KakaoTalk",""),("BitcoinTalk",""),("Slack",""),("Docs",""),("Kakao",""),("Gitter","")])
builtTokens.add(WalletTokenDto(
name: tokenDto.name,
symbol: tokenDto.symbol,
@ -304,7 +389,17 @@ const prepareTokensTask: Task = proc(argEncoded: string) {.gcsafe, nimcall.} =
totalBalance: totalTokenBalance,
balancesPerChain: balancesPerChain,
enabledNetworkBalance: enabledNetworkBalance,
visible: visible
visible: visible,
description: tokenDescription,
assetWebsiteUrl: assetWebsiteUrl,
builtOn: builtOn,
smartContractAddress: smartContractAddress,
marketCap: marketCap,
highDay: highDay,
lowDay: lowDay,
changePctHour: changePctHour,
changePctDay: changePctDay,
changePct24hour: changePct24hour
)
)

View File

@ -20,6 +20,16 @@ type
enabledNetworkBalance*: BalanceDto
balancesPerChain*: Table[int, BalanceDto]
visible*: bool
description*: string
assetWebsiteUrl*: string
builtOn*: string
smartContractAddress*: string
marketCap*: string
highDay*: string
lowDay*: string
changePctHour*: string
changePctDay*: string
changePct24hour*: string
type
WalletAccountDto* = ref object of RootObj
@ -97,6 +107,16 @@ proc toWalletTokenDto*(jsonObj: JsonNode): WalletTokenDto =
discard jsonObj.getProp("color", result.color)
discard jsonObj.getProp("isCustom", result.isCustom)
discard jsonObj.getProp("visible", result.visible)
discard jsonObj.getProp("description", result.description)
discard jsonObj.getProp("assetWebsiteUrl", result.assetWebsiteUrl)
discard jsonObj.getProp("builtOn", result.builtOn)
discard jsonObj.getProp("smartContractAddress", result.smartContractAddress)
discard jsonObj.getProp("marketCap", result.marketCap)
discard jsonObj.getProp("highDay", result.highDay)
discard jsonObj.getProp("lowDay", result.lowDay)
discard jsonObj.getProp("changePctHour", result.changePctHour)
discard jsonObj.getProp("changePctDay", result.changePctDay)
discard jsonObj.getProp("changePct24hour", result.changePct24hour)
var totalBalanceObj: JsonNode
if(jsonObj.getProp("totalBalance", totalBalanceObj)):
@ -126,5 +146,15 @@ proc walletTokenDtoToJson*(dto: WalletTokenDto): JsonNode =
"totalBalance": %* dto.totalBalance,
"enabledNetworkBalance": %* dto.enabledNetworkBalance,
"balancesPerChain": balancesPerChainJsonObj,
"visible": dto.visible
"visible": dto.visible,
"description": dto.description,
"assetWebsiteUrl": dto.assetWebsiteUrl,
"builtOn": dto.builtOn,
"smartContractAddress": dto.smartContractAddress,
"marketCap": dto.marketCap,
"highDay": dto.highDay,
"lowDay": dto.lowDay,
"changePctHour": dto.changePctHour,
"changePctDay": dto.changePctDay,
"changePct24hour": dto.changePct24hour
}

View File

@ -203,3 +203,10 @@ rpc(addDappPermissions, "permissions"):
rpc(deleteDappPermissionsByNameAndAddress, "permissions"):
dapp: string
address: string
rpc(fetchMarketValues, "wallet"):
symbols: seq[string]
currency: string
rpc(fetchTokenDetails, "wallet"):
symbols: seq[string]

View File

@ -1,5 +1,4 @@
import QtQuick 2.13
import QtQuick.Controls 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
@ -7,6 +6,7 @@ import StatusQ.Components 0.1
import shared 1.0
import shared.panels 1.0
import shared.controls 1.0
import utils 1.0
import "../popups"
@ -60,35 +60,10 @@ Item {
Repeater {
id: chainRepeater
model: store.enabledNetworks
delegate: Control {
horizontalPadding: Style.current.halfPadding
verticalPadding: 5
background: Rectangle {
implicitWidth: 66
implicitHeight: 32
color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
radius: 36
}
contentItem: Row {
spacing: 4
// FIXME this could be StatusIcon but it can't load images from an arbitrary URL
Image {
anchors.verticalCenter: parent.verticalCenter
width: 22
height: 22
source: Style.svg("tiny/" + model.iconUrl)
}
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
text: model.shortName
color: model.chainColor
font.pixelSize: Style.current.primaryTextFontSize
font.weight: Font.Medium
}
}
delegate: InformationTag {
tagPrimaryLabel.text: model.shortName
tagPrimaryLabel.color: model.chainColor
image.source: Style.svg("tiny/" + model.iconUrl)
}
}
}

View File

@ -1,5 +1,4 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtGraphicalEffects 1.13
import QtQuick.Layouts 1.13
@ -138,36 +137,10 @@ StatusModal {
spacing: 5
Repeater {
model: RootStore.enabledNetworks
delegate: Control {
horizontalPadding: Style.current.halfPadding
verticalPadding: 5
background: Rectangle {
implicitWidth: 66
implicitHeight: 32
color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
radius: 36
}
contentItem: Row {
spacing: 4
// FIXME this could be StatusIcon but it can't load images from an arbitrary URL
Image {
anchors.verticalCenter: parent.verticalCenter
width: 24
height: 24
source: Style.svg("tiny/" + model.iconUrl)
}
StatusBaseText {
anchors.verticalCenter: parent.verticalCenter
text: model.shortName
color: model.chainColor
font.pixelSize: Style.current.primaryTextFontSize
font.weight: Font.Medium
}
}
delegate: InformationTag {
tagPrimaryLabel.text: model.shortName
tagPrimaryLabel.color: model.chainColor
image.source: Style.svg("tiny/" + model.iconUrl)
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor

View File

@ -67,6 +67,11 @@ Item {
AssetsView {
account: RootStore.currentAccount
assetDetailsLaunched: stack.currentIndex === 2
onAssetClicked: {
assetDetailView.token = token
stack.currentIndex = 2
}
}
CollectiblesView {
onCollectibleClicked: {
@ -79,7 +84,14 @@ Item {
}
}
CollectibleDetailView {
anchors.fill: parent
Layout.fillWidth: true
Layout.fillHeight: true
onGoBack: stack.currentIndex = 0
}
AssetsDetailView {
id: assetDetailView
Layout.fillWidth: true
Layout.fillHeight: true
onGoBack: stack.currentIndex = 0
}
}

View File

@ -7,6 +7,7 @@ import StatusQ.Core.Theme 0.1
import shared.panels 1.0
import "../../stores"
import utils 1.0
Item {
id: root

View File

@ -0,0 +1,87 @@
import QtQuick 2.13
import QtQuick.Controls 2.14
import utils 1.0
import shared.controls 1.0
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
Control {
id: root
property alias primaryText: tokenName.text
property alias secondaryText: cryptoBalance.text
property alias tertiaryText: fiatBalance.text
property var balances
property StatusImageSettings image: StatusImageSettings {
width: 40
height: 40
}
property var getNetworkColor: function(chainId){}
property var getNetworkIcon: function(chainId){}
topPadding: Style.current.padding
contentItem: Column {
spacing: 4
Row {
spacing: 8
StatusSmartIdenticon {
id: identiconLoader
anchors.verticalCenter: parent.verticalCenter
image: root.image
}
StatusBaseText {
id: tokenName
width: Math.min(root.width - identiconLoader.width - cryptoBalance.width - fiatBalance.width - 24, implicitWidth)
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
lineHeight: 30
lineHeightMode: Text.FixedHeight
elide: Text.ElideRight
color: Theme.palette.directColor1
}
StatusBaseText {
id: cryptoBalance
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
lineHeight: 30
lineHeightMode: Text.FixedHeight
color: Theme.palette.baseColor1
}
StatusBaseText {
id: dotSeparator
anchors.verticalCenter: parent.verticalCenter
anchors.verticalCenterOffset: -15
font.pixelSize: 50
color: Theme.palette.baseColor1
text: "."
}
StatusBaseText {
id: fiatBalance
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: 22
lineHeight: 30
lineHeightMode: Text.FixedHeight
color: Theme.palette.baseColor1
}
}
Row {
spacing: Style.current.smallPadding
anchors.left: parent.left
anchors.leftMargin: identiconLoader.width
Repeater {
id: chainRepeater
model: balances ? balances : null
delegate: InformationTag {
tagPrimaryLabel.text: model.balance
tagPrimaryLabel.color: root.getNetworkColor(model.chainId)
image.source: Style.svg("tiny/%1".arg(root.getNetworkIcon(model.chainId)))
}
}
}
}
}

View File

@ -0,0 +1,66 @@
import QtQuick 2.13
import QtQuick.Layouts 1.13
import QtQuick.Controls 2.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
Control {
property alias image : image
property alias iconAsset : iconAsset
property alias tagPrimaryLabel: tagPrimaryLabel
property alias tagSecondaryLabel: tagSecondaryLabel
property alias controlBackground: controlBackground
horizontalPadding: Style.current.halfPadding
verticalPadding: 5
background: Rectangle {
id: controlBackground
implicitWidth: 66
implicitHeight: 32
color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
radius: 36
}
contentItem: RowLayout {
spacing: 4
// FIXME this could be StatusIcon but it can't load images from an arbitrary URL
Image {
id: image
Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: visible ? 22 : 0
Layout.maximumHeight: visible ? 22 : 0
visible: image.source !== ""
}
StatusIcon {
id: iconAsset
Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: visible ? 22 : 0
Layout.maximumHeight: visible ? 22 : 0
visible: iconAsset.icon !== ""
}
StatusBaseText {
id: tagPrimaryLabel
Layout.alignment: Qt.AlignVCenter
font.pixelSize: Style.current.primaryTextFontSize
font.weight: Font.Medium
color: Theme.palette.directColor1
visible: text !== ""
}
StatusBaseText {
id: tagSecondaryLabel
Layout.alignment: Qt.AlignVCenter
Layout.maximumWidth: 100
font.pixelSize: Style.current.primaryTextFontSize
font.weight: Font.Medium
color: Theme.palette.baseColor1
visible: text !== ""
elide: Text.ElideMiddle
}
}
}

View File

@ -12,6 +12,8 @@ Rectangle {
property alias primaryText: primaryText.text
property alias secondaryText: secondaryText.text
property alias primaryLabel: primaryText
property alias secondaryLabel: secondaryText
property alias tagsModel: tags.model
property alias tagsDelegate: tags.delegate
property int maxWidth: 0

View File

@ -26,3 +26,5 @@ TransactionFormGroup 1.0 TransactionFormGroup.qml
EmojiHash 1.0 EmojiHash.qml
InformationTile 1.0 InformationTile.qml
SocialLinkPreview 1.0 SocialLinkPreview.qml
AssetsDetailsHeader 1.0 AssetsDetailsHeader.qml
InformationTag 1.0 InformationTag.qml

View File

@ -40,6 +40,23 @@ QtObject {
property var walletTokensModule: walletSectionAllTokens
property var tokens: walletSectionAllTokens.all
property var accounts: walletSectionAccounts.model
function getNetworkColor(chainId) {
return networksModule.all.getChainColor(chainId)
}
function getNetworkIcon(chainId) {
return networksModule.all.getIconUrl(chainId)
}
function getNetworkIconUrl(symbol) {
return networksModule.all.getNetworkIconUrl(symbol)
}
function getNetworkName(symbol) {
return networksModule.all.getNetworkName(symbol)
}
readonly property var formationChars: (["*", "`", "~"])
function getSelectedTextWithFormationChars(messageInputField) {

View File

@ -0,0 +1,192 @@
import QtQuick 2.13
import QtQuick.Layouts 1.13
import QtQuick.Controls 2.14
import QtQuick.Window 2.12
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import utils 1.0
import shared.controls 1.0
import "../stores"
Item {
id: root
property var token
signal goBack()
StatusFlatButton {
id: backButton
anchors.top: parent.top
anchors.left: parent.left
anchors.topMargin: -Style.current.xlPadding
anchors.leftMargin: -Style.current.xlPadding
icon.name: "arrow-left"
icon.width: 20
icon.height: 20
text: qsTr("Assets")
size: StatusBaseButton.Size.Large
onClicked: root.goBack()
}
AssetsDetailsHeader {
id: tokenDetailsHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
width: parent.width
image.source: token && token.symbol ? Style.png("tokens/%1".arg(token.symbol)) : ""
primaryText: token ? token.name : ""
secondaryText: token ? qsTr("%1 %2").arg(Utils.toLocaleString(token.totalBalance, RootStore.locale, {"currency": true})).arg(token.symbol) : ""
tertiaryText: token ? "%1 %2".arg(Utils.toLocaleString(token.totalCurrencyBalance.toFixed(2), RootStore.locale, {"currency": true})).arg(RootStore.currencyStore.currentCurrency.toUpperCase()) : ""
balances: token && token.balances ? token.balances : null
getNetworkColor: function(chainId){
return RootStore.getNetworkColor(chainId)
}
getNetworkIcon: function(chainId){
return RootStore.getNetworkIcon(chainId)
}
}
ColumnLayout {
anchors.top: tokenDetailsHeader.bottom
anchors.topMargin: 24
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
width: parent.width
spacing: Style.current.padding
RowLayout {
Layout.fillWidth: true
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Market Cap")
secondaryText: token && token.marketCap !== "" ? token.marketCap : "---"
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Day Low")
secondaryText: token && token.lowDay !== "" ? token.lowDay : "---"
}
InformationTile {
maxWidth: parent.width
primaryText: qsTr("Day High")
secondaryText: token && token.highDay ? token.highDay : "---"
}
Item {
Layout.fillWidth: true
}
InformationTile {
readonly property string changePctHour: token ? token.changePctHour : ""
maxWidth: parent.width
primaryText: qsTr("Hour")
secondaryText: changePctHour ? "%1%".arg(changePctHour) : "---"
secondaryLabel.color: Math.sign(Number(changePctHour)) === 0 ? Theme.palette.directColor1 :
Math.sign(Number(changePctHour)) === -1 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
}
InformationTile {
readonly property string changePctDay: token ? token.changePctDay : ""
maxWidth: parent.width
primaryText: qsTr("Day")
secondaryText: changePctDay ? "%1%".arg(changePctDay) : "---"
secondaryLabel.color: Math.sign(Number(changePctDay)) === 0 ? Theme.palette.directColor1 :
Math.sign(Number(changePctDay)) === -1 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
}
InformationTile {
readonly property string changePct24hour: token ? token.changePct24hour : ""
maxWidth: parent.width
primaryText: qsTr("24 Hours")
secondaryText: changePct24hour ? "%1%".arg(changePct24hour) : "---"
secondaryLabel.color: Math.sign(Number(changePct24hour)) === 0 ? Theme.palette.directColor1 :
Math.sign(Number(changePct24hour)) === -1 ? Theme.palette.dangerColor1 :
Theme.palette.successColor1
}
}
StatusTabBar {
Layout.fillWidth: true
Layout.topMargin: Style.current.xlPadding
StatusTabButton {
leftPadding: 0
width: implicitWidth
text: qsTr("Overview")
}
}
StackLayout {
id: stack
Layout.fillWidth: true
Layout.fillHeight: true
StatusScrollView {
id: scrollView
Layout.preferredWidth: parent.width
Layout.preferredHeight: parent.height
ScrollBar.horizontal.policy: ScrollBar.AsNeeded
topPadding: 8
bottomPadding: 8
Flow {
id: detailsFlow
readonly property bool isOverflowing: detailsFlow.width - tagsLayout.width - tokenDescriptionText.width < 24
spacing: 24
width: scrollView.availableWidth
StatusBaseText {
id: tokenDescriptionText
width: Math.max(536 , scrollView.availableWidth - tagsLayout.width - 24)
font.pixelSize: 15
lineHeight: 22
lineHeightMode: Text.FixedHeight
text: token ? token.description : ""
color: Theme.palette.directColor1
elide: Text.ElideRight
wrapMode: Text.Wrap
textFormat: Qt.RichText
}
ColumnLayout {
id: tagsLayout
spacing: 10
InformationTag {
id: website
Layout.alignment: detailsFlow.isOverflowing ? Qt.AlignLeft : Qt.AlignRight
iconAsset.icon: "browser"
tagPrimaryLabel.text: qsTr("Website")
controlBackground.color: Theme.palette.baseColor2
controlBackground.border.color: "transparent"
visible: token && token.assetWebsiteUrl !== ""
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Global.openLink(token.assetWebsiteUrl)
}
}
InformationTag {
id: smartContractAddress
Layout.alignment: detailsFlow.isOverflowing ? Qt.AlignLeft : Qt.AlignRight
image.source: token && token.builtOn !== "" ? Style.svg("tiny/" + RootStore.getNetworkIconUrl(token.builtOn)) : ""
tagPrimaryLabel.text: token && token.builtOn !== "" ? RootStore.getNetworkName(token.builtOn) : "---"
tagSecondaryLabel.text: token && token.smartContractAddress !== "" ? token.smartContractAddress : "---"
controlBackground.color: Theme.palette.baseColor2
controlBackground.border.color: "transparent"
visible: token && token.builtOn !== "" && token.smartContractAddress !== ""
}
}
}
}
}
}
}

View File

@ -1,32 +1,66 @@
import QtQuick 2.13
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.14
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import SortFilterProxyModel 0.2
import utils 1.0
import shared 1.0
import "../stores"
import "../controls"
Item {
id: root
property var account
property bool assetDetailsLaunched: false
signal assetClicked(var token)
QtObject {
id: d
readonly property var alwaysVisible : ["ETH", "SNT", "DAI", "STT"]
property int selectedAssetIndex: -1
}
height: assetListView.height
StatusListView {
id: assetListView
anchors.fill: parent
model: account.assets
objectName: "assetViewStatusListView"
delegate: AssetDelegate {
locale: RootStore.locale
currency: RootStore.currentCurrency
currencySymbol: RootStore.currencyStore.currentCurrencySymbol
anchors.fill: parent
model: SortFilterProxyModel {
sourceModel: account.assets
filters: [
ExpressionFilter {
expression: d.alwaysVisible.includes(symbol) || (networkVisible && enabledNetworkBalance > 0)
}
]
}
delegate: StatusListItem {
width: parent.width
title: name
subTitle: qsTr("%1 %2").arg(Utils.toLocaleString(enabledNetworkBalance, RootStore.locale, {"currency": true})).arg(symbol)
image.source: symbol ? Style.png("tokens/" + symbol) : ""
components: [
StatusBaseText {
font.pixelSize: 15
font.strikeout: false
text: enabledNetworkCurrencyBalance.toLocaleCurrencyString(Qt.locale(), RootStore.currencyStore.currentCurrencySymbol)
}
]
onClicked: {
d.selectedAssetIndex = index
assetClicked(model)
}
Component.onCompleted: {
// on Model reset if the detail view is shown, update the data in background.
if(root.assetDetailsLaunched && index === d.selectedAssetIndex)
assetClicked(model)
}
}
Layout.fillWidth: true
Layout.fillHeight: true
ScrollBar.vertical: ScrollBar { policy: ScrollBar.AsNeeded }
}

View File

@ -10,3 +10,4 @@ PasswordView 1.0 PasswordView.qml
ProfileView 1.0 ProfileView.qml
AssetsView 1.0 AssetsView.qml
HistoryView 1.0 HistoryView.qml
AssetsDetailView 1.0 AssetsDetailView.qml

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit e6a3f63ec7f2fa691878ed35f921413dc8acfc66
Subproject commit 25a80cf20b2fd6153eccdb3cd154126181b86561