feat(@desktop/wallet): Multichain QR Code

Implements the new Recieve Modal with multichain functionality

closes #5735, #6057
This commit is contained in:
Khushboo Mehta 2022-05-18 23:58:07 +02:00 committed by Khushboo-dev-cpp
parent 56f7c52e81
commit 8d3fad8342
29 changed files with 373 additions and 109 deletions

View File

@ -12,7 +12,10 @@ type
nativeCurrencySymbol: string
isTest: bool
isEnabled: bool
iconUrl: string
chainColor: string
shortName: string
proc initItem*(
chainId: int,
nativeCurrencyDecimals: int,
@ -24,6 +27,9 @@ proc initItem*(
nativeCurrencySymbol: string,
isTest: bool,
isEnabled: bool,
iconUrl: string,
chainColor: string,
shortName: string,
): Item =
result.chainId = chainId
result.nativeCurrencyDecimals = nativeCurrencyDecimals
@ -35,6 +41,9 @@ proc initItem*(
result.nativeCurrencySymbol = nativeCurrencySymbol
result.isTest = isTest
result.isEnabled = isEnabled
result.iconUrl = iconUrl
result.chainColor = chainColor
result.shortName = shortName
proc `$`*(self: Item): string =
result = fmt"""NetworkItem(
@ -46,8 +55,11 @@ proc `$`*(self: Item): string =
blockExplorerURL:{self.blockExplorerURL},
nativeCurrencyName:{self.nativeCurrencyName},
nativeCurrencySymbol:{self.nativeCurrencySymbol},
isTest:{self.isTest}
isEnabled:{self.isEnabled}
isTest:{self.isTest},
isEnabled:{self.isEnabled},
iconUrl:{self.iconUrl},
shortName: {self.shortName},
chainColor: {self.chainColor},
]"""
proc getChainId*(self: Item): int =
@ -78,4 +90,13 @@ proc getIsTest*(self: Item): bool =
return self.isTest
proc getIsEnabled*(self: Item): bool =
return self.isEnabled
return self.isEnabled
proc getIconURL*(self: Item): string =
return self.iconUrl
proc getShortName*(self: Item): string =
return self.shortName
proc getChainColor*(self: Item): string =
return self.chainColor

View File

@ -14,6 +14,9 @@ type
NativeCurrencySymbol
IsTest
IsEnabled
IconUrl
ChainColor
ShortName
QtObject:
type
@ -59,6 +62,9 @@ QtObject:
ModelRole.NativeCurrencySymbol.int:"nativeCurrencySymbol",
ModelRole.IsTest.int:"isTest",
ModelRole.IsEnabled.int:"isEnabled",
ModelRole.IconUrl.int:"iconUrl",
ModelRole.ShortName.int: "shortName",
ModelRole.ChainColor.int: "chainColor",
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
@ -92,6 +98,12 @@ QtObject:
result = newQVariant(item.getIsTest())
of ModelRole.IsEnabled:
result = newQVariant(item.getIsEnabled())
of ModelRole.IconUrl:
result = newQVariant(item.getIconURL())
of ModelRole.ShortName:
result = newQVariant(item.getShortName())
of ModelRole.ChainColor:
result = newQVariant(item.getChainColor())
proc setItems*(self: Model, items: seq[Item]) =
self.beginResetModel()

View File

@ -80,6 +80,9 @@ QtObject:
n.nativeCurrencySymbol,
n.isTest,
n.enabled,
n.iconUrl,
n.chainColor,
n.shortName,
))
self.layer1.setItems(items.filter(i => i.getLayer() == 1))
self.layer2.setItems(items.filter(i => i.getLayer() == 2))
@ -97,4 +100,4 @@ QtObject:
proc toggleTestNetworksEnabled*(self: View) {.slot.} =
self.delegate.toggleTestNetworksEnabled()
self.areTestNetworksEnabled = not self.areTestNetworksEnabled
self.areTestNetworksEnabledChanged()
self.areTestNetworksEnabledChanged()

View File

@ -115,7 +115,9 @@ let NETWORKS* = %* [
"chainName": "Mainnet",
"rpcUrl": "https://mainnet.infura.io/v3/" & INFURA_TOKEN_RESOLVED,
"blockExplorerUrl": "https://etherscan.io/",
"iconUrl": "",
"iconUrl": "network/Network=Ethereum",
"chainColor": "#627EEA",
"shortName": "eth",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -128,7 +130,9 @@ let NETWORKS* = %* [
"chainName": "Ropsten",
"rpcUrl": "https://ropsten.infura.io/v3/" & INFURA_TOKEN_RESOLVED,
"blockExplorerUrl": "https://ropsten.etherscan.io/",
"iconUrl": "",
"iconUrl": "network/Network=Tetnet",
"chainColor": "#939BA1",
"shortName": "ropEth",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -141,7 +145,9 @@ let NETWORKS* = %* [
"chainName": "Rinkeby",
"rpcUrl": "https://rinkeby.infura.io/v3/" & INFURA_TOKEN_RESOLVED,
"blockExplorerUrl": "https://rinkeby.etherscan.io/",
"iconUrl": "",
"iconUrl": "network/Network=Tetnet",
"chainColor": "#939BA1",
"shortName": "rinEth",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -154,7 +160,9 @@ let NETWORKS* = %* [
"chainName": "Goerli",
"rpcUrl": "http://goerli.blockscout.com/",
"blockExplorerUrl": "https://goerli.etherscan.io/",
"iconUrl": "",
"iconUrl": "network/Network=Tetnet",
"chainColor": "#939BA1",
"shortName": "goeEth",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -167,7 +175,9 @@ let NETWORKS* = %* [
"chainName": "Optimism",
"rpcUrl": "https://optimism-mainnet.infura.io/v3/" & INFURA_TOKEN_RESOLVED,
"blockExplorerUrl": "https://optimistic.etherscan.io",
"iconUrl": "",
"iconUrl": "network/Network=Optimism",
"chainColor": "#E90101",
"shortName": "opt",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -180,7 +190,9 @@ let NETWORKS* = %* [
"chainName": "Optimism Kovan",
"rpcUrl": "https://optimism-kovan.infura.io/v3/" & INFURA_TOKEN_RESOLVED,
"blockExplorerUrl": "https://kovan-optimistic.etherscan.io",
"iconUrl": "",
"iconUrl": "network/Network=Tetnet",
"chainColor": "#939BA1",
"shortName": "kovOpt",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -193,7 +205,9 @@ let NETWORKS* = %* [
"chainName": "Arbitrum",
"rpcUrl": "https://arbitrum-mainnet.infura.io/v3/" & INFURA_TOKEN_RESOLVED,
"blockExplorerUrl": "https://arbiscan.io/",
"iconUrl": "",
"iconUrl": "network/Network=Arbitrum",
"chainColor": "#51D0F0",
"shortName": "arb",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,
@ -206,7 +220,9 @@ let NETWORKS* = %* [
"chainName": "Arbitrum Rinkeby",
"rpcUrl": "https://arbitrum-rinkeby.infura.io/v3/" & INFURA_TOKEN_RESOLVED,
"blockExplorerUrl": " https://testnet.arbiscan.io",
"iconUrl": "",
"iconUrl": "network/Network=Tetnet",
"chainColor": "#939BA1",
"shortName": "rinArb",
"nativeCurrencyName": "Ether",
"nativeCurrencySymbol": "ETH",
"nativeCurrencyDecimals": 18,

View File

@ -15,9 +15,25 @@ type NetworkDto* = ref object
nativeCurrencySymbol* {.serializedFieldName("nativeCurrencySymbol").}: string
isTest* {.serializedFieldName("isTest").}: bool
enabled* {.serializedFieldName("enabled").}: bool
chainColor* {.serializedFieldName("chainColor").}: string
shortName* {.serializedFieldName("shortName").}: string
proc `$`*(self: NetworkDto): string =
return fmt"Network(chainId:{self.chainId}, name:{self.chainName}, rpcURL:{self.rpcURL}, isTest:{self.isTest}, enabled:{self.enabled})"
return fmt"""Network(
chainId:{self.chainId},
nativeCurrencyDecimals:{self.nativeCurrencyDecimals},
layer:{self.layer},
chainName:{self.chainName},
name:{self.chainName},
rpcURL:{self.rpcURL},
blockExplorerURL:{self.blockExplorerURL},
iconURL:{self.iconURL},
nativeCurrencyName:{self.nativeCurrencyName},
nativeCurrencySymbol:{self.nativeCurrencySymbol},
isTest:{self.isTest}, enabled:{self.enabled},
chainColor:{self.chainColor},
shortName:{self.shortName}
)"""
proc hash*(self: NetworkDto): Hash =
return self.chainId.hash

View File

@ -69,6 +69,8 @@ proc upsertNetwork*(self: Service, network: NetworkDto) =
nativeCurrencySymbol: network.nativeCurrencySymbol,
isTest: network.isTest,
enabled: network.enabled,
chainColor: network.chainColor,
shortName: network.shortName,
))
self.dirty.store(true)
@ -122,4 +124,4 @@ proc getNetworkForCollectibles*(self: Service): NetworkDto =
if self.settingsService.areTestNetworksEnabled():
return self.getNetwork(Rinkeby)
return self.getNetwork(Mainnet)
return self.getNetwork(Mainnet)

View File

@ -38,7 +38,8 @@ type
nativeCurrencySymbol* {.serializedFieldName("nativeCurrencySymbol").}: string
isTest* {.serializedFieldName("isTest").}: bool
enabled* {.serializedFieldName("enabled").}: bool
chainColor* {.serializedFieldName("chainColor").}: string
shortName* {.serializedFieldName("shortName").}: string
rpc(clientVersion, "web3"):
discard

View File

@ -7,7 +7,7 @@ import StatusQ.Core.Theme 0.1
StatusListItem {
property var network
title: network.chainName
image.source: Style.png("networks/" + network.chainName.toLowerCase())
image.source: Style.png(network.iconUrl)
width: parent.width
leftPadding: Style.current.padding
rightPadding: Style.current.padding

View File

@ -59,6 +59,13 @@ Popup {
delegate: chainItem
}
StatusBaseText {
font.pixelSize: Style.current.primaryTextFontSize
color: Theme.palette.baseColor1
text: qsTr("Layer 2")
visible: chainRepeater2.count > 0
}
Repeater {
id: chainRepeater2
model: popup.layer2Networks
@ -77,29 +84,24 @@ Popup {
Component {
id: chainItem
Item {
width: content.width
height: 40
StatusBaseText {
anchors.left: parent.left
anchors.leftMargin: Style.current.bigPadding
anchors.verticalCenter: parent.verticalCenter
font.pixelSize: Style.current.primaryTextFontSize
text: model.chainName
color: Theme.palette.directColor1
StatusListItem {
implicitWidth: scrollView.width
title: model.chainName
image.source: Style.png(model.iconUrl)
onClicked: {
checkBox.checked = !checkBox.checked
}
StatusCheckBox {
anchors.right: parent.right
anchors.rightMargin: Style.current.bigPadding
anchors.verticalCenter: parent.verticalCenter
checked: model.isEnabled
onCheckedChanged: {
if (model.isEnabled !== checked) {
popup.toggleNetwork(model.chainId)
components: [
StatusCheckBox {
id: checkBox
checked: model.isEnabled
onCheckedChanged: {
if (model.isEnabled !== checked) {
popup.toggleNetwork(model.chainId)
}
}
}
}
]
}
}
}

View File

@ -1,12 +1,15 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtGraphicalEffects 1.13
import QtQuick.Layouts 1.13
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1
import utils 1.0
@ -17,94 +20,199 @@ import "../stores"
StatusModal {
id: popup
property alias selectedAccount: accountSelector.selectedAccount
property var selectedAccount
property string networkPrefix: ""
property string completeAddressWithNetworkPrefix
onSelectedAccountChanged: {
if (selectedAccount.address) {
txtWalletAddress.text = selectedAccount.address
}
}
onCompleteAddressWithNetworkPrefixChanged: {
qrCodeImage.source = RootStore.getQrCode(completeAddressWithNetworkPrefix)
}
//% "Receive"
header.title: qsTrId("receive")
contentHeight: layout.implicitHeight
width: 556
showHeader: false
showAdvancedHeader: true
hasFloatingButtons: true
advancedHeaderComponent: StatusFloatingButtonsSelector {
id: floatingHeader
model: RootStore.accounts
delegate: Rectangle {
width: button.width
height: button.height
radius: 8
visible: floatingHeader.visibleIndices.includes(index)
StatusButton {
id: button
topPadding: 8
bottomPadding: 0
implicitHeight: 32
defaultLeftPadding: 4
text: name
icon.emoji: !!emoji ? emoji: ""
icon.emojiSize: Emoji.size.middle
icon.name: !emoji ? "filled-account": ""
normalColor: "transparent"
highlighted: index === floatingHeader.currentIndex
onClicked: {
popup.selectedAccount = model
floatingHeader.currentIndex = index
}
Component.onCompleted: {
// On startup make the preseected wallet in the floating menu
if(name === popup.selectedAccount.name)
floatingHeader.currentIndex = index
}
}
}
popupMenuDelegate: StatusListItem {
implicitWidth: 272
title: name
subTitle: currencyBalance
icon.emoji: !!emoji ? emoji: ""
icon.color: model.color
icon.name: !emoji ? "filled-account": ""
icon.letterSize: 14
icon.isLetterIdenticon: !!model.emoji
icon.background.color: Theme.palette.indirectColor1
onClicked: {
popup.selectedAccount = model
floatingHeader.itemSelected(index)
}
visible: !floatingHeader.visibleIndices.includes(index)
}
}
contentItem: Column {
id: layout
width: popup.width
topPadding: Style.current.smallPadding
topPadding: Style.current.xlPadding
spacing: Style.current.bigPadding
Rectangle {
id: qrCodeBox
StatusSwitchTabBar {
id: tabBar
anchors.horizontalCenter: parent.horizontalCenter
height: 339
width: 339
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: qrCodeBox.width
height: qrCodeBox.height
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
width: qrCodeBox.width
height: qrCodeBox.height
radius: Style.current.bigPadding
border.width: 1
border.color: Style.current.border
}
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
width: Style.current.bigPadding
height: Style.current.bigPadding
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
width: Style.current.bigPadding
height: Style.current.bigPadding
}
}
StatusSwitchTabButton {
text: qsTr("Legacy")
}
Image {
id: qrCodeImage
anchors.centerIn: parent
height: parent.height
width: parent.width
asynchronous: true
fillMode: Image.PreserveAspectFit
mipmap: true
smooth: false
}
Rectangle {
anchors.centerIn: qrCodeImage
width: 78
height: 78
color: "white"
StatusIcon {
anchors.centerIn: parent
anchors.margins: 2
width: 78
height: 78
source: Style.svg("status-logo-icon")
}
StatusSwitchTabButton {
text: qsTr("Multichain")
enabled: localAccountSensitiveSettings.isMultiNetworkEnabled
}
}
StatusAccountSelector {
id: accountSelector
anchors.horizontalCenter: parent.horizontalCenter
width: 240
label: ""
showAccountDetails: false
accounts: RootStore.accounts
currency: RootStore.currentCurrency
dropdownWidth: parent.width - (Style.current.padding * 2)
dropdownAlignment: StatusSelect.MenuAlignment.Center
onSelectedAccountChanged: {
if (selectedAccount.address) {
qrCodeImage.source = RootStore.getQrCode(selectedAccount.address)
txtWalletAddress.text = selectedAccount.address
Item {
width: parent.width
height: qrCodeBox.height
id: centralLayout
Grid {
id: multiChainList
property bool need2Columns: RootStore.enabledNetworks.count >= 9
anchors.centerIn: parent
anchors.horizontalCenterOffset: need2Columns ? 0 : qrCodeBox.width/2 + Style.current.xlPadding + Style.current.halfPadding
height: qrCodeBox.height
columnSpacing: need2Columns ? qrCodeBox.width + Style.current.bigPadding : 0
flow: Grid.TopToBottom
columns: need2Columns ? 2 : 1
spacing: 5
Repeater {
model: RootStore.enabledNetworks
delegate: StatusListItemTag {
image.source: Style.png(iconUrl)
title: shortName
titleText.color: chainColor
closeButtonVisible: false
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: selectPopup.open()
}
}
}
StatusListItemTag {
closeButtonVisible: false
icon.name: "edit_pencil"
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
selectPopup.open()
}
}
}
}
Rectangle {
id: qrCodeBox
height: 339
width: 339
anchors.centerIn: parent
anchors.horizontalCenter: parent.horizontalCenter
layer.enabled: true
layer.effect: OpacityMask {
maskSource: Item {
width: qrCodeBox.width
height: qrCodeBox.height
Rectangle {
anchors.top: parent.top
anchors.left: parent.left
width: qrCodeBox.width
height: qrCodeBox.height
radius: Style.current.bigPadding
border.width: 1
border.color: Style.current.border
}
Rectangle {
anchors.top: parent.top
anchors.right: parent.right
width: Style.current.bigPadding
height: Style.current.bigPadding
}
Rectangle {
anchors.bottom: parent.bottom
anchors.left: parent.left
width: Style.current.bigPadding
height: Style.current.bigPadding
}
}
}
Image {
id: qrCodeImage
anchors.centerIn: parent
height: parent.height
width: parent.width
asynchronous: true
fillMode: Image.PreserveAspectFit
mipmap: true
smooth: false
}
Rectangle {
anchors.centerIn: qrCodeImage
width: 78
height: 78
color: "white"
StatusIcon {
anchors.centerIn: parent
anchors.margins: 2
width: 78
height: 78
source: Style.svg("status-logo-icon")
}
}
}
}
@ -124,6 +232,23 @@ StatusModal {
color: Theme.palette.baseColor1
text: qsTr("Your Address")
}
RowLayout {
id: networksLabel
spacing: -1
Repeater {
model: RootStore.enabledNetworks
delegate: StatusBaseText {
font.pixelSize: 15
color: chainColor
text: shortName + ":"
Component.onCompleted: {
if(index === 0)
popup.networkPrefix = ""
popup.networkPrefix +=text
}
}
}
}
StatusAddress {
id: txtWalletAddress
color: Theme.palette.directColor1
@ -137,6 +262,7 @@ StatusModal {
anchors.rightMargin: Style.current.bigPadding
spacing: 5
CopyToClipBoardButton {
id: copyToClipBoard
store: RootStore
textToCopy: txtWalletAddress.text
}
@ -148,6 +274,71 @@ StatusModal {
}
}
}
NetworkSelectPopup {
id: selectPopup
x: multiChainList.x + Style.current.xlPadding + Style.current.halfPadding
y: centralLayout.y
layer1Networks: RootStore.layer1Networks
layer2Networks: RootStore.layer2Networks
testNetworks: RootStore.testNetworks
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
onToggleNetwork: {
RootStore.toggleNetwork(chainId)
}
}
states: [
State {
name: "legacy"
when: tabBar.currentIndex === 0
PropertyChanges {
target: multiChainList
visible: false
}
PropertyChanges {
target: contactsLabel
visible: true
}
PropertyChanges {
target: networksLabel
visible: false
}
PropertyChanges {
target: copyToClipBoard
textToCopy: txtWalletAddress.text
}
PropertyChanges {
target: popup
completeAddressWithNetworkPrefix: popup.selectedAccount.address
}
},
State {
name: "multichain"
when: tabBar.currentIndex === 1
PropertyChanges {
target: multiChainList
visible: true
}
PropertyChanges {
target: contactsLabel
visible: false
}
PropertyChanges {
target: networksLabel
visible: true
}
PropertyChanges {
target: copyToClipBoard
textToCopy: popup.networkPrefix + txtWalletAddress.text
}
PropertyChanges {
target: popup
completeAddressWithNetworkPrefix: popup.networkPrefix + popup.selectedAccount.address
}
}
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

2
vendor/status-go vendored

@ -1 +1 @@
Subproject commit efa14805bd3a87f7e87f2655dd651dc4d9c18e6d
Subproject commit fea37ff5b46ff84153591d327f4672b7d3c45915