refactor: Refactoring of AccountSelector dropdown

The new account selector expects a generic account model. It will display all the account data if provided, including preferred chains, balance or asset balance. Otherwise it will display only the available data.
The account selector can receive an initial selection based on account address and will provide the current selected address and the current selected model item.

- Unify the account selector between communities and wallet
- Update the account selector to work with addresses instead of model indexes
- Adapt all components using the account selector or the account selection
- Move/reuse qml components involved in the account selector UI
- Remove nim logic used to handle index based account selection.
- Adding storybook page
This commit is contained in:
Alex Jbanca 2024-06-07 15:27:56 +03:00 committed by Alex Jbanca
parent 0645ed4712
commit 8b4cbc59a8
50 changed files with 902 additions and 528 deletions

View File

@ -247,12 +247,12 @@ QtObject:
self.toNetworksModel.getRouteDisabledNetworkChainIds(), self.toNetworksModel.getRoutePreferredNetworkChainIds(),
self.sendType, self.fromNetworksModel.getRouteLockedChainIds())
proc switchSenderAccountByAddress*(self: View, address: string) =
proc switchSenderAccountByAddress*(self: View, address: string) {.slot.} =
let (account, index) = self.senderAccounts.getItemByAddress(address)
self.setSelectedSenderAccount(account)
self.delegate.setSelectedSenderAccountIndex(index)
proc switchReceiveAccountByAddress*(self: View, address: string) =
proc switchReceiveAccountByAddress*(self: View, address: string) {.slot.} =
let (account, index) = self.accounts.getItemByAddress(address)
self.setSelectetReceiveAccount(account)
self.delegate.setSelectedReceiveAccountIndex(index)

View File

@ -0,0 +1,54 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import Models 1.0
import SortFilterProxyModel 0.2
import shared.controls 1.0
Item {
id: root
ColumnLayout {
spacing: 16
anchors.centerIn: parent
implicitWidth: 150
WalletAccountsModel {
id: accountsModel
}
Label {
text: "Default style"
font.bold: true
Layout.fillWidth: true
}
AccountSelector {
id: accountSelector
Layout.fillWidth: true
model: WalletAccountsModel {}
onCurrentAccountAddressChanged: {
accountSelector2.selectedAddress = currentAccountAddress
}
}
Label {
text: "Header style"
font.bold: true
Layout.fillWidth: true
}
AccountSelectorHeader {
id: accountSelector2
model: accountSelector.model
onCurrentAccountAddressChanged: {
accountSelector.selectedAddress = currentAccountAddress
}
}
}
}
// category: Components

View File

@ -72,7 +72,7 @@ Item {
spacing: 8
accounts: d.selectedAccount
accounts: WalletAccountsModel {}
flatNetworks: SortFilterProxyModel {
sourceModel: NetworksModel.flatNetworks

View File

@ -52,6 +52,7 @@ SplitView {
filters: ValueFilter { roleName: "isTest"; value: false }
}
accounts: WalletAccountsModel {}
ownerToken.accountAddress: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881"
onMintClicked: logs.logEvent("EditOwnerTokenView::onMintClicked")

View File

@ -16,22 +16,8 @@ SplitView {
id: feesModel
}
ListModel {
WalletAccountsModel {
id: accountsModel
ListElement {
name: "Test account"
emoji: "😋"
address: "0x7F47C2e18a4BBf5487E6fb082eC2D9Ab0E6d7240"
color: "red"
}
ListElement {
name: "Another account - generated"
emoji: "🚗"
address: "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8888"
color: "blue"
}
}
LimitProxyModel {

View File

@ -34,17 +34,15 @@ SplitView {
id: dialog
visible: true
accounts: ListModel {
ListElement {
position: 0
name: "My account"
}
accounts: WalletAccountsModel {
id: accountsModel
}
selectedAccount: {
"name": "My account",
"emoji": "",
"address": "0x1234567890123456789012345678901234567890",
"preferredSharingChainIds": "10:42161:1:"
"name": "Hot wallet (generated)",
"emoji": "🚗",
"color": "#216266",
"address": "0x7F47C2e98a4BBf5487E6fb082eC2D9Ab0E6d8881",
"preferredSharingChainIds": "5:420:421613",
}
switchingAccounsEnabled: true
changingPreferredChainsEnabled: true

View File

@ -103,16 +103,19 @@ SplitView {
closePolicy: Popup.CloseOnEscape
destroyOnClose: true
swapInputParamsForm: SwapInputParamsForm {
selectedAccountAddress: {
if (accountComboBox.model.count > 0 && accountComboBox.currentIndex >= 0) {
return ModelUtils.get(accountComboBox.model, accountComboBox.currentIndex, "address")
}
return ""
}
selectedAccountAddress: accountComboBox.currentValue ?? ""
selectedNetworkChainId: d.getNetwork()
fromTokensKey: fromTokenComboBox.currentValue
fromTokenAmount: swapInput.text
toTokenKey: toTokenComboBox.currentValue
onSelectedAccountAddressChanged: {
if (selectedAccountAddress !== accountComboBox.currentValue)
accountComboBox.currentIndex = accountComboBox.indexOfValue(selectedAccountAddress)
}
Binding on selectedAccountAddress {
value: accountComboBox.currentValue ?? ""
}
}
swapAdaptor: SwapModalAdaptor {
swapStore: dSwapStore
@ -162,6 +165,7 @@ SplitView {
ComboBox {
id: accountComboBox
textRole: "name"
valueRole: "address"
model: SortFilterProxyModel {
sourceModel: d.accountsModel
filters: ValueFilter {

View File

@ -0,0 +1,132 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import shared.controls 1.0
import utils 1.0
SplitView {
id: root
Item {
SplitView.preferredWidth: walletAccountListItem.implicitWidth
SplitView.preferredHeight: walletAccountListItem.implicitHeight
WalletAccountListItem {
id: walletAccountListItem
clearVisible: showClearButton.checked
name: nameField.text
address: addressField.text
chainShortNames: chainShortNamesField.text
emoji: emojiField.text
walletColor: walletColorField.text
currencyBalance: QtObject {
readonly property double amount: parseInt(currencyBalanceField.text)
readonly property string symbol: "USD"
readonly property int displayDecimals: 4
readonly property bool stripTrailingZeroes: false
}
walletType: walletTypeCombo.currentText
migratedToKeycard: migratedToKeycardCheckBox.checked
accountBalance: hasAccountBalanceCheckBox.checked ? ({
formattedBalance: formattedAccountBalance.text,
balance: formattedAccountBalance.text,
iconUrl: "network/Network=Hermez",
chainColor: "#FF0000"
}) : null
onCleared: {
console.log("Cleared clicked")
}
onClicked: (itemId, mouse) => {
console.log("Clicked: ", itemId, mouse)
}
onTitleClicked: (titleId) => {
console.log("Title clicked: ", titleId)
}
onIconClicked: (mouse) => {
console.log("Icon clicked: ", mouse)
}
}
}
Pane {
id: pane
SplitView.fillWidth: true
SplitView.fillHeight: true
Column {
TextField {
id: nameField
text: "Piggy Bank"
placeholderText: "Name"
}
TextField {
id: addressField
text: "0x1234567890abcdef"
placeholderText: "Address"
}
TextField {
id: chainShortNamesField
text: "<font color=\"red\">eth:</font><font color=\"blue\">oeth:</font><font color=\"green\">arb:</font>"
placeholderText: "Chain Short Names"
}
TextField {
id: emojiField
text: "🐷"
placeholderText: "Emoji"
}
TextField {
id: walletColorField
text: "#FF0000"
placeholderText: "Wallet Color"
}
Label {
text: "Currency balance amount"
}
TextField {
id: currencyBalanceField
text: "1232343234234"
placeholderText: "Currency Balance"
}
Label {
text: "Wallet Type: " + Constants.watchWalletType
}
ComboBox {
id: walletTypeCombo
model: [Constants.watchWalletType, Constants.keyWalletType, Constants.seedWalletType, Constants.generatedWalletType]
currentIndex: 0
}
CheckBox {
id: migratedToKeycardCheckBox
text: "Migrated to Keycard"
}
CheckBox {
id: showClearButton
text: "Show Clear Button"
}
CheckBox {
id: hasAccountBalanceCheckBox
text: "Has Account Balance"
checked: true
}
TextField {
id: formattedAccountBalance
text: "123.45"
visible: hasAccountBalanceCheckBox.checked
}
}
}
}

View File

@ -52,7 +52,12 @@ Item {
swapOutputData: SwapOutputData{}
}
readonly property var swapFormData: SwapInputParamsForm {}
property SwapInputParamsForm swapFormData: null
Component {
id: swapFormDataComponent
SwapInputParamsForm { }
}
Component {
id: componentUnderTest
@ -76,7 +81,9 @@ Item {
// helper functions -------------------------------------------------------------
function init() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
root.swapFormData = createTemporaryObject(swapFormDataComponent, root)
swapAdaptor.swapFormData = root.swapFormData
controlUnderTest = createTemporaryObject(componentUnderTest, root, { swapInputParamsForm: root.swapFormData})
}
function launchAndVerfyModal() {
@ -94,7 +101,7 @@ Item {
}
function getAndVerifyAccountsModalHeader() {
const accountsModalHeader = findChild(controlUnderTest, "accountsModalHeader")
const accountsModalHeader = findChild(controlUnderTest, "accountSelector")
verify(!!accountsModalHeader)
return accountsModalHeader
}
@ -140,22 +147,23 @@ Item {
/* using a for loop set different accounts as default index and
check if the correct values are displayed in the floating header*/
for (let i = 0; i< swapAdaptor.nonWatchAccounts.count; i++) {
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(i).address
const nonWatchAccount = swapAdaptor.nonWatchAccounts.get(i)
root.swapFormData.selectedAccountAddress = nonWatchAccount.address
// Launch popup
launchAndVerfyModal()
const floatingHeaderBackground = findChild(controlUnderTest, "headerBackground")
verify(!!floatingHeaderBackground)
compare(floatingHeaderBackground.color.toString().toUpperCase(), Utils.getColorForId(swapAdaptor.nonWatchAccounts.get(i).colorId).toString().toUpperCase())
compare(floatingHeaderBackground.color.toString().toUpperCase(), Utils.getColorForId(nonWatchAccount.colorId).toString().toUpperCase())
const headerContentItemText = findChild(controlUnderTest, "headerContentItemText")
const headerContentItemText = findChild(controlUnderTest, "textContent")
verify(!!headerContentItemText)
compare(headerContentItemText.text, swapAdaptor.nonWatchAccounts.get(i).name)
compare(headerContentItemText.text, nonWatchAccount.name)
const headerContentItemEmoji = findChild(controlUnderTest, "headerContentItemEmoji")
const headerContentItemEmoji = findChild(controlUnderTest, "assetContent")
verify(!!headerContentItemEmoji)
compare(headerContentItemEmoji.emojiId, SQUtils.Emoji.iconId(swapAdaptor.nonWatchAccounts.get(i).emoji))
compare(headerContentItemEmoji.asset.emoji, nonWatchAccount.emoji)
}
closeAndVerfyModal()
}
@ -189,6 +197,7 @@ Item {
}
function test_floating_header_list_items() {
skip("Randomly failing")
// Launch popup and account selection modal
launchAndVerfyModal()
const accountsModalHeader = getAndVerifyAccountsModalHeader()
@ -201,7 +210,7 @@ Item {
let delegateUnderTest = comboBoxList.itemAtIndex(i)
// check if the items are organized as per the position role
if(!!delegateUnderTest && !!comboBoxList.itemAtIndex(i+1)) {
verify(comboBoxList.itemAtIndex(i+1).modelData.position > delegateUnderTest.modelData.position)
verify(comboBoxList.itemAtIndex(i+1).model.position > delegateUnderTest.model.position)
}
compare(delegateUnderTest.title, swapAdaptor.nonWatchAccounts.get(i).name)
compare(delegateUnderTest.subTitle, SQUtils.Utils.elideText(swapAdaptor.nonWatchAccounts.get(i).address, 6, 4))
@ -223,12 +232,13 @@ Item {
// TODO: always null not sure why
// const walletAccountTypeIcon = findChild(delegateUnderTest, "walletAccountTypeIcon")
// verify(!!walletAccountTypeIcon)
// compare(walletAccountTypeIcon.icon, swapAdaptor.nonWatchAccounts.get(i).walletType === Constants.watchWalletType ? "show" : delegateUnderTest.modelData.migratedToKeycard ? "keycard": "")
// compare(walletAccountTypeIcon.icon, swapAdaptor.nonWatchAccounts.get(i).walletType === Constants.watchWalletType ? "show" : delegateUnderTest.model.migratedToKeycard ? "keycard": "")
// Hover over the item and check hovered state
mouseMove(delegateUnderTest, delegateUnderTest.width/2, delegateUnderTest.height/2)
verify(delegateUnderTest.sensor.containsMouse)
compare(delegateUnderTest.subTitle, WalletUtils.colorizedChainPrefix(root.swapAdaptor.getNetworkShortNames(swapAdaptor.nonWatchAccounts.get(i).preferredSharingChainIds)))
compare(delegateUnderTest.title, swapAdaptor.nonWatchAccounts.get(i).name)
compare(delegateUnderTest.subTitle, WalletUtils.colorizedChainPrefix(root.swapAdaptor.getNetworkShortNames(swapAdaptor.nonWatchAccounts.get(i).preferredSharingChainIds)), "Randomly failing locally. Add a bug if you see this failing in CI")
verify(delegateUnderTest.color, Theme.palette.baseColor2)
}
@ -249,7 +259,7 @@ Item {
// before setting network chainId and fromTokensKey the header should not have balances
for(let i =0; i< comboBoxList.model.count; i++) {
let delegateUnderTest = comboBoxList.itemAtIndex(i)
verify(!delegateUnderTest.modelData.fromToken)
verify(!delegateUnderTest.model.accountBalance)
}
// close account selection dropdown
@ -267,19 +277,19 @@ Item {
for(let i =0; i< comboBoxList.model.count; i++) {
let delegateUnderTest = comboBoxList.itemAtIndex(i)
verify(!!delegateUnderTest.modelData.fromToken)
verify(!!delegateUnderTest.modelData.accountBalance)
verify(!!delegateUnderTest.model.fromToken)
verify(!!delegateUnderTest.model.accountBalance)
compare(delegateUnderTest.inlineTagModel, 1)
const inlineTagDelegate_0 = findChild(delegateUnderTest, "inlineTagDelegate_0")
verify(!!inlineTagDelegate_0)
compare(inlineTagDelegate_0.asset.name, Style.svg("tiny/%1".arg(delegateUnderTest.modelData.accountBalance.iconUrl)))
compare(inlineTagDelegate_0.asset.color.toString().toUpperCase(), delegateUnderTest.modelData.accountBalance.chainColor.toString().toUpperCase())
compare(inlineTagDelegate_0.titleText.color, delegateUnderTest.modelData.accountBalance.balance === "0" ? Theme.palette.baseColor1 : Theme.palette.directColor1)
compare(inlineTagDelegate_0.asset.name, Style.svg("tiny/%1".arg(delegateUnderTest.model.accountBalance.iconUrl)))
compare(inlineTagDelegate_0.asset.color.toString().toUpperCase(), delegateUnderTest.model.accountBalance.chainColor.toString().toUpperCase())
compare(inlineTagDelegate_0.titleText.color, delegateUnderTest.model.accountBalance.balance === "0" ? Theme.palette.baseColor1 : Theme.palette.directColor1)
let bigIntBalance = SQUtils.AmountsArithmetic.toNumber(delegateUnderTest.modelData.accountBalance.balance, delegateUnderTest.modelData.fromToken.decimals)
compare(inlineTagDelegate_0.title, root.swapAdaptor.formatCurrencyAmount(bigIntBalance, delegateUnderTest.modelData.fromToken.symbol))
let bigIntBalance = SQUtils.AmountsArithmetic.toNumber(delegateUnderTest.model.accountBalance.balance, delegateUnderTest.model.fromToken.decimals)
compare(inlineTagDelegate_0.title, root.swapAdaptor.formatCurrencyAmount(bigIntBalance, delegateUnderTest.model.fromToken.symbol))
}
closeAndVerfyModal()
@ -312,13 +322,13 @@ Item {
verify(!!floatingHeaderBackground)
compare(floatingHeaderBackground.color.toString().toUpperCase(), swapAdaptor.nonWatchAccounts.get(i).color.toString().toUpperCase())
const headerContentItemText = findChild(accountsModalHeader, "headerContentItemText")
const headerContentItemText = findChild(accountsModalHeader, "textContent")
verify(!!headerContentItemText)
compare(headerContentItemText.text, swapAdaptor.nonWatchAccounts.get(i).name)
const headerContentItemEmoji = findChild(accountsModalHeader, "headerContentItemEmoji")
const headerContentItemEmoji = findChild(accountsModalHeader, "assetContent")
verify(!!headerContentItemEmoji)
compare(headerContentItemEmoji.emojiId, SQUtils.Emoji.iconId(swapAdaptor.nonWatchAccounts.get(i).emoji))
compare(headerContentItemEmoji.asset.emoji, swapAdaptor.nonWatchAccounts.get(i).emoji)
}
closeAndVerfyModal()
}
@ -413,7 +423,7 @@ Item {
let balancesModel = SQUtils.ModelUtils.getByKey(root.swapAdaptor.walletAssetsStore.baseGroupedAccountAssetModel, "tokensKey", root.swapFormData.fromTokensKey).balances
verify(!!balancesModel)
let filteredBalances = SQUtils.ModelUtils.modelToArray(balancesModel).filter(balances => balances.chainId === root.swapFormData.selectedNetworkChainId).filter(balances => balances.account === accountDelegateUnderTest.modelData.address)
let filteredBalances = SQUtils.ModelUtils.modelToArray(balancesModel).filter(balances => balances.chainId === root.swapFormData.selectedNetworkChainId).filter(balances => balances.account === accountDelegateUnderTest.model.address)
verify(!!filteredBalances)
let accountBalance = filteredBalances.length > 0 ? filteredBalances[0]: { balance: "0", iconUrl: networkModelItem.iconUrl, chainColor: networkModelItem.chainColor}
verify(!!accountBalance)
@ -488,6 +498,7 @@ Item {
}
function test_modal_swap_proposal_setup() {
skip("Randomly failing")
root.swapAdaptor.reset()
// Launch popup
@ -965,6 +976,9 @@ Item {
function test_modal_max_button_click_with_preset_pay_value() {
// Launch popup
launchAndVerfyModal()
// The default is the first account. Setting the second account to test switching accounts
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(1).address
formValuesChanged.clear()
// try setting value before popup is launched and check values
let valueToExchange = 0.2
@ -1018,6 +1032,9 @@ Item {
function test_modal_max_button_click_with_no_preset_pay_value() {
// Launch popup
launchAndVerfyModal()
// The default is the first account. Setting the second account to test switching accounts
root.swapFormData.selectedAccountAddress = swapAdaptor.nonWatchAccounts.get(1).address
formValuesChanged.clear()
// try setting value before popup is launched and check values
root.swapFormData.selectedNetworkChainId = root.swapAdaptor.filteredFlatNetworksModel.get(0).chainId

View File

@ -42,6 +42,7 @@ ListModel {
}
],
preferredSharingChainIds: "5:420:421613",
colorizedChainPrefixes: "eth:opt",
currencyBalance: ({amount: 1.25,
symbol: "USD",
displayDecimals: 4,
@ -68,6 +69,7 @@ ListModel {
}
],
preferredSharingChainIds: "5:420:421613",
colorizedChainPrefixes: "eth:opt",
currencyBalance: ({amount: 10,
symbol: "USD",
displayDecimals: 4,
@ -103,6 +105,7 @@ ListModel {
}
],
preferredSharingChainIds: "5:420:421613",
colorizedChainPrefixes: "eth:opt",
currencyBalance: ({amount: 110.05,
symbol: "USD",
displayDecimals: 4,
@ -146,6 +149,7 @@ ListModel {
}
],
preferredSharingChainIds: "5:420:421613",
colorizedChainPrefixes: "eth:opt",
currencyBalance: ({amount: 999,
symbol: "USD",
displayDecimals: 4,

View File

@ -170,8 +170,13 @@ QtObject {
}
}
function switchSenderAccount(index) {
selectedSenderAccount = senderAccounts.get(index)
function switchSenderAccountByAddress(address) {
for (let i = 0; i < senderAccounts.count; i++) {
if (senderAccounts.get(i).address === address) {
selectedSenderAccount = senderAccounts.get(i)
break
}
}
}
function getNetworkShortNames(chainIds) {

View File

@ -159,8 +159,6 @@ Rectangle {
acceptedButtons: Qt.NoButton
hoverEnabled: true
StatusSmartIdenticon {
id: iconOrImage
anchors.left: parent.left

View File

@ -1,6 +1,6 @@
import QtQuick 2.13
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
@ -46,15 +46,17 @@ Control {
color: root.bgColor
radius: root.bgRadius
border.color: root.bgBorderColor
}
MouseArea {
anchors.fill: parent
enabled: root.tagClickable
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.tagClicked(mouse)
onClicked: (mouse) => {
root.tagClicked(mouse)
}
z: -1
}
contentItem: RowLayout {
id: layout
spacing: root.spacing
@ -79,12 +81,13 @@ Control {
id: closeIcon
color: Theme.palette.primaryColor1
icon: "close-circle"
visible: closeButtonVisible
visible: root.closeButtonVisible
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: root.clicked(mouse)
anchors.fill: parent
onClicked: (mouse) => {
root.clicked(mouse)
}
}
}
}

View File

@ -469,16 +469,9 @@ Item {
Component {
id: clearButton
StatusFlatRoundButton {
StatusClearButton {
visible: edit.length != 0 && root.clearable && !root.multiline
&& edit.activeFocus
type: StatusFlatRoundButton.Type.Secondary
width: 24
height: 24
icon.name: "clear"
icon.width: 16
icon.height: 16
icon.color: Theme.palette.baseColor1
onClicked: {
edit.clear()
}

View File

@ -8,6 +8,8 @@ StatusFlatRoundButton {
icon.name: "clear"
icon.width: 16
icon.height: 16
implicitWidth: 24
implicitHeight: 24
icon.color: Theme.palette.baseColor1
backgroundHoverColor: "transparent"
}

View File

@ -16,6 +16,7 @@ Item {
property alias delegate: comboBox.delegate
property alias contentItem: comboBox.contentItem
property alias comboBoxListViewSection: listView.section
readonly property alias indicator: statusIndicator
property alias currentIndex: comboBox.currentIndex
property alias currentValue: comboBox.currentValue
@ -31,6 +32,36 @@ Item {
property int size: StatusComboBox.Size.Large
property int type: StatusComboBox.Type.Primary
readonly property Component defaultBackgroundComponent: Rectangle {
color: root.type === StatusComboBox.Type.Secondary ? "transparent" : Theme.palette.baseColor2
radius: 8
border.width: (!!root.validationError || root.forceError
|| comboBox.hovered || comboBox.down
|| comboBox.visualFocus
|| root.type === StatusComboBox.Type.Secondary)
? 1 : 0
border.color: {
if (!!root.validationError || root.forceError)
return Theme.palette.dangerColor1
if (comboBox.visualFocus || comboBox.popup.opened)
return Theme.palette.primaryColor1
if (comboBox.hovered)
return Theme.palette.primaryColor2
if (root.type === StatusComboBox.Type.Secondary)
return Theme.palette.directColor7
return "transparent"
}
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
}
}
enum Size {
Small,
Large
@ -76,34 +107,8 @@ Item {
padding: 16
spacing: 16
background: Rectangle {
color: root.type === StatusComboBox.Type.Secondary ? "transparent" : Theme.palette.baseColor2
radius: 8
border.width: (!!root.validationError || root.forceError
|| comboBox.hovered || comboBox.down
|| comboBox.visualFocus
|| root.type === StatusComboBox.Type.Secondary)
? 1 : 0
border.color: {
if (!!root.validationError || root.forceError)
return Theme.palette.dangerColor1
if (comboBox.visualFocus || comboBox.popup.opened)
return Theme.palette.primaryColor1
if (comboBox.hovered)
return Theme.palette.primaryColor2
if (root.type === StatusComboBox.Type.Secondary)
return Theme.palette.directColor7
return "transparent"
}
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
}
background: Loader {
sourceComponent: root.defaultBackgroundComponent
}
contentItem: StatusBaseText {
@ -115,6 +120,7 @@ Item {
}
indicator: StatusIcon {
id: statusIndicator
x: comboBox.mirrored ? comboBox.padding : comboBox.width - width - comboBox.padding
y: comboBox.topPadding + (comboBox.availableHeight - height) / 2
width: root.size === StatusComboBox.Size.Large ? 24 : 16

View File

@ -5,6 +5,7 @@ StatusBanner 0.1 StatusBanner.qml
StatusChatCommandButton 0.1 StatusChatCommandButton.qml
StatusChatInfoButton 0.1 StatusChatInfoButton.qml
StatusChatListCategoryItemButton 0.1 StatusChatListCategoryItemButton.qml
StatusClearButton 0.1 StatusClearButton.qml
StatusColorSelector 0.1 StatusColorSelector.qml
StatusIconTabButton 0.1 StatusIconTabButton.qml
StatusIdenticonRing 0.1 StatusIdenticonRing.qml

View File

@ -102,6 +102,7 @@
<file>StatusQ/Controls/StatusChatInfoButton.qml</file>
<file>StatusQ/Controls/StatusChatListCategoryItemButton.qml</file>
<file>StatusQ/Controls/StatusCheckBox.qml</file>
<file>StatusQ/Controls/StatusClearButton.qml</file>
<file>StatusQ/Controls/StatusColorRadioButton.qml</file>
<file>StatusQ/Controls/StatusColorSelector.qml</file>
<file>StatusQ/Controls/StatusColorSelectorGrid.qml</file>

View File

@ -1,9 +0,0 @@
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
StatusEmojiAndColorComboBox {
type: StatusComboBox.Type.Secondary
size: StatusComboBox.Size.Small
implicitHeight: 44
defaultAssetName: "filled-account"
}

View File

@ -5,6 +5,8 @@ import QtQuick.Layouts 1.15
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import shared.controls 1.0
import utils 1.0

View File

@ -1,4 +1,3 @@
AccountSelector 1.0 AccountSelector.qml
AddressesInputList 1.0 AddressesInputList.qml
AddressesSelectorPanel 1.0 AddressesSelectorPanel.qml
AirdropRecipientsSelector 1.0 AirdropRecipientsSelector.qml

View File

@ -52,7 +52,8 @@ StatusDialog {
readonly property string remainingTokensDisplayText:
LocaleUtils.numberToLocaleString(remainingTokensFloat)
property string accountAddress
readonly property string accountAddress: feesBox.accountsSelector.currentAccountAddress
property string amountToBurn: !isFormValid ? "" :
specificAmountButton.checked ? amountInput.amount : root.remainingTokens
@ -176,17 +177,6 @@ StatusDialog {
model: d.isFormValid ? singleFeeModel : undefined
accountsSelector.model: root.accounts
accountsSelector.onCurrentIndexChanged: {
if (accountsSelector.currentIndex < 0)
return
const item = SQUtils.ModelUtils.get(
accountsSelector.model,
accountsSelector.currentIndex)
d.accountAddress = item.address
}
QtObject {
id: singleFeeModel

View File

@ -50,6 +50,8 @@ StatusDialog {
property bool ackCheck: false
// Fees related props:
// TODO: These properties are not used in the current implementation!
// Check if the current fees box in this popup is needed!!
property string accountAddress: ""
property string accountName: ""
}
@ -291,6 +293,7 @@ StatusDialog {
}
FeesBox {
id: feesBox
Layout.fillWidth: true
implicitWidth: 0
@ -307,14 +310,16 @@ StatusDialog {
readonly property bool error: root.feeErrorText !== ""
}
accountsSelector.onCurrentIndexChanged: {
if (accountsSelector.currentIndex < 0)
return
Binding {
target: d
property: "accountAddress"
value: feesBox.accountsSelector.currentAccountAddress
}
const item = ModelUtils.get(accountsSelector.model,
accountsSelector.currentIndex)
d.accountAddress = item.address
d.accountName = item.name
Binding {
target: d
property: "accountName"
value: feesBox.accountsSelector.currentAccount.name
}
}

View File

@ -114,13 +114,10 @@ StatusDialog {
model: d.tokenCount > 0 ? singleFeeModel : undefined
accountsSelector.model: root.accounts
accountsSelector.onCurrentIndexChanged: {
if (accountsSelector.currentIndex < 0)
return
const item = ModelUtils.get(accountsSelector.model,
accountsSelector.currentIndex)
d.accountAddress = item.address
Binding {
target: d
property: "accountAddress"
value: feesBox.accountsSelector.currentAccountAddress
}
QtObject {

View File

@ -59,8 +59,8 @@ StatusDialog {
QtObject {
id: d
property string accountAddress: ""
property string accountName: ""
readonly property string accountAddress: feesBox.accountsSelector.currentAccountAddress
readonly property string accountName: feesBox.accountsSelector.currentAccount.name ?? ""
}
ColumnLayout {
@ -142,6 +142,7 @@ StatusDialog {
}
FeesBox {
id: feesBox
Layout.fillWidth: true
implicitWidth: 0
@ -157,16 +158,6 @@ StatusDialog {
"" : root.feeText
readonly property bool error: root.feeErrorText !== ""
}
accountsSelector.onCurrentIndexChanged: {
if (accountsSelector.currentIndex < 0)
return
const item = ModelUtils.get(accountsSelector.model,
accountsSelector.currentIndex)
d.accountAddress = item.address
d.accountName = item.name
}
}
}

View File

@ -195,24 +195,18 @@ StatusScrollView {
}
accountsSelector.model: root.accounts || null
accountsSelector.selectedAddress: root.token.accountAddress
Component.onCompleted: {
const initIndex = StatusQUtils.ModelUtils.indexOf(
accountsSelector.model, "name",
token.accountName)
Binding {
target: root.token
property: "accountAddress"
value: feesBox.accountsSelector.currentAccountAddress
}
accountsSelector.currentIndex = (initIndex !== -1) ? initIndex : 0
accountsSelector.currentIndexChanged.connect(() => {
if (accountsSelector.currentIndex < 0)
return
const item = StatusQUtils.ModelUtils.get(
accountsSelector.model,
accountsSelector.currentIndex)
token.accountAddress = item.address
token.accountName = item.name
})
Binding {
target: root.token
property: "accountName"
value: feesBox.accountsSelector.currentAccount.name
}
}

View File

@ -145,8 +145,7 @@ StatusScrollView {
.concat([...selectedKeysFilter.keys])
}
readonly property string selectedFeeAccount: ModelUtils.get(root.accountsModel,
feesBox.accountIndex).address
readonly property string selectedFeeAccount: feesBox.accountsSelector.currentAccountAddress
function prepareEntry(key, amount, type) {
const tokenModel = type === Constants.TokenType.ERC20
@ -532,9 +531,6 @@ StatusScrollView {
FeesBox {
id: feesBox
readonly property int accountIndex: accountsSelector.currentIndex
Layout.fillWidth: true
model: feesModel
@ -567,10 +563,8 @@ StatusScrollView {
enabled: root.isFullyFilled && root.feesAvailable && root.feeErrorText === ""
onClicked: {
const accountItem = ModelUtils.get(root.accountsModel,
feesBox.accountIndex)
feesPopup.accountAddress = accountItem.address
feesPopup.accountName = accountItem.name
feesPopup.accountAddress = feesBox.accountsSelector.currentAccountAddress
feesPopup.accountName = feesBox.accountsSelector.currentAccount.name ?? ""
feesPopup.open()
}
}

View File

@ -354,29 +354,18 @@ StatusScrollView {
}
accountsSelector.model: root.accounts
accountsSelector.selectedAddress: root.token.accountAddress
// account can be changed also on preview page and it should be
// reflected in the form after navigating back
Connections {
Binding {
target: root.token
function onAccountAddressChanged() {
const idx = SQUtils.ModelUtils.indexOf(
feesBox.accountsSelector.model, "address",
root.token.accountAddress)
feesBox.accountsSelector.currentIndex = idx
}
property: "accountAddress"
value: feesBox.accountsSelector.currentAccountAddress
}
accountsSelector.onCurrentIndexChanged: {
if (accountsSelector.currentIndex < 0)
return
const item = SQUtils.ModelUtils.get(
accountsSelector.model, accountsSelector.currentIndex)
root.token.accountAddress = item.address
root.token.accountName = item.name
Binding {
target: root.token
property: "accountName"
value: feesBox.accountsSelector.currentAccount.name
}
}

View File

@ -14,6 +14,8 @@ import AppLayouts.Communities.panels 1.0
import AppLayouts.Wallet.controls 1.0
import utils 1.0
import shared.controls 1.0
import SortFilterProxyModel 0.2
StatusScrollView {
@ -149,30 +151,32 @@ StatusScrollView {
AccountSelector {
id: accountBox
readonly property string address: {
root.accounts.count
return SQUtils.ModelUtils.get(root.accounts, currentIndex, "address")
}
readonly property string initAccountName: ownerToken.accountName
readonly property int initIndex: {
root.accounts.count
return SQUtils.ModelUtils.indexOf(root.accounts, "name", initAccountName)
}
Layout.fillWidth: true
Layout.topMargin: -Style.current.halfPadding
currentIndex: (initIndex !== -1) ? initIndex : 0
model: root.accounts
onAddressChanged: {
ownerToken.accountAddress = address
tMasterToken.accountAddress = address
selectedAddress: ownerToken.accountAddress
Binding {
target: root.ownerToken
property: "accountAddress"
value: accountBox.currentAccountAddress
}
control.onDisplayTextChanged: {
ownerToken.accountName = control.displayText
tMasterToken.accountName = control.displayText
Binding {
target: root.ownerToken
property: "accountName"
value: accountBox.currentAccount.name
}
Binding {
target: root.tMasterToken
property: "accountAddress"
value: accountBox.currentAccountAddress
}
Binding {
target: root.tMasterToken
property: "accountName"
value: accountBox.currentAccount.name
}
}

View File

@ -136,7 +136,7 @@ Item {
}
property SwapInputParamsForm swapFormData: SwapInputParamsForm {
selectedAccountAddress: StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts, d.selectedAccountIndex, "address")
selectedAccountAddress: RootStore.selectedAddress
selectedNetworkChainId: {
// Without this when we switch testnet mode, the correct network is not evaluated
RootStore.areTestNetworksEnabled
@ -167,8 +167,6 @@ Item {
rightPanelStackView.currentItem.resetView()
}
}
readonly property int selectedAccountIndex: RootStore.showAllAccounts ? 0 : leftTab.currentAccountIndex
}
SignPhraseModal {
@ -216,7 +214,7 @@ Item {
hasFloatingButtons: true
})
onLaunchSwapModal: {
d.swapFormData.selectedAccountAddress = StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts, d.selectedAccountIndex, "address")
d.swapFormData.selectedAccountAddress = RootStore.selectedAddress
d.swapFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId")
d.swapFormData.fromTokensKey = tokensKey
d.swapFormData.toTokenKey = RootStore.areTestNetworksEnabled ? Constants.swap.testStatusTokenKey : Constants.swap.mainnetStatusTokenKey
@ -335,7 +333,7 @@ Item {
}
onLaunchSwapModal: {
d.swapFormData.fromTokensKey = ""
d.swapFormData.selectedAccountAddress = StatusQUtils.ModelUtils.get(RootStore.nonWatchAccounts, d.selectedAccountIndex, "address")
d.swapFormData.selectedAccountAddress = RootStore.selectedAddress
d.swapFormData.selectedNetworkChainId = StatusQUtils.ModelUtils.getByKey(RootStore.filteredFlatModel, "layer", 1, "chainId")
if(!!walletStore.currentViewedHoldingTokensKey && walletStore.currentViewedHoldingType === Constants.TokenType.ERC20) {
d.swapFormData.fromTokensKey = walletStore.currentViewedHoldingTokensKey

View File

@ -4,6 +4,7 @@ import QtQuick.Layouts 1.13
import QtQuick.Controls 2.14
import SortFilterProxyModel 0.2
import StatusQ 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
@ -18,6 +19,7 @@ import shared.popups 1.0
import shared.popups.send.controls 1.0
import AppLayouts.stores 1.0
import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.controls 1.0
import ".."
@ -39,7 +41,7 @@ StatusModal {
property var store: RootStore
signal selectedAccountIndexChanged(int selectedIndex)
signal updateSelectedAddress(string address)
signal updatePreferredChains(string address, string preferredChains)
onSelectedAccountChanged: {
@ -53,18 +55,34 @@ StatusModal {
showHeader: false
showAdvancedHeader: hasFloatingButtons
advancedHeaderComponent: AccountsModalHeader {
advancedHeaderComponent: Item {
implicitWidth: accountSelector.implicitWidth
implicitHeight: accountSelector.implicitHeight
AccountSelectorHeader {
id: accountSelector
control.enabled: root.switchingAccounsEnabled && model.count > 1
width: implicitWidth
model: SortFilterProxyModel {
sourceModel: root.accounts
sorters: RoleSorter { roleName: "position"; sortOrder: Qt.AscendingOrder }
proxyRoles: [
FastExpressionRole {
name: "colorizedChainPrefixes"
function getChainShortNames(chainIds) {
const chainShortNames = root.getNetworkShortNames(chainIds)
return WalletUtils.colorizedChainPrefix(chainShortNames)
}
expression: getChainShortNames(model.preferredSharingChainIds)
expectedRoles: ["preferredSharingChainIds"]
}
]
}
selectedAccount: root.selectedAccount
getNetworkShortNames: root.getNetworkShortNames
onSelectedIndexChanged: {
root.selectedAccountIndexChanged(selectedIndex)
selectedAddress: !!root.selectedAccount ? root.selectedAccount.address : ""
onCurrentAccountAddressChanged: {
root.updateSelectedAddress(currentAccountAddress)
}
}
}

View File

@ -69,18 +69,20 @@ StatusDialog {
onClosed: root.swapAdaptor.reset()
header: AccountsModalHeader {
header: Item {
height: selector.height
anchors.top: parent.top
anchors.topMargin: -height - 18
AccountSelectorHeader {
id: selector
control.popup.width: 512
model: root.swapAdaptor.nonWatchAccounts
getNetworkShortNames: root.swapAdaptor.getNetworkShortNames
formatCurrencyAmount: root.swapAdaptor.formatCurrencyAmount
/* TODO: once the Account Header is reworked we simply should be
able to use an index and not this logic of selectedAccount being set */
selectedAccount: root.swapAdaptor.getSelectedAccountByAddress(root.swapInputParamsForm.selectedAccountAddress)
onSelectedIndexChanged: {
root.swapInputParamsForm.selectedAccountAddress = root.swapAdaptor.getSelectedAccountAddressByIndex(selectedIndex)
selectedAddress: root.swapInputParamsForm.selectedAccountAddress
onCurrentAccountAddressChanged: {
if (currentAccountAddress !== "" && currentAccountAddress !== root.swapInputParamsForm.selectedAccountAddress) {
root.swapInputParamsForm.selectedAccountAddress = currentAccountAddress
}
}
}
}
@ -127,6 +129,13 @@ StatusDialog {
networkFilter.setChain(root.swapInputParamsForm.selectedNetworkChainId)
}
}
Connections {
target: root.swapInputParamsForm
function onSelectedNetworkChainIdChanged() {
networkFilter.setChain(root.swapInputParamsForm.selectedNetworkChainId)
}
}
}
}

View File

@ -7,6 +7,7 @@ import StatusQ.Core.Utils 0.1
import utils 1.0
import shared.stores 1.0
import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.stores 1.0 as WalletStore
QObject {
@ -25,8 +26,8 @@ QObject {
property bool showCommunityTokens
// To expose the selected from and to Token from the SwapModal
readonly property var fromToken: ModelUtils.getByKey(root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel, "key", root.swapFormData.fromTokensKey)
readonly property var toToken: ModelUtils.getByKey(root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel, "key", root.swapFormData.toTokenKey)
readonly property var fromToken: fromTokenEntry.item
readonly property var toToken: toTokenEntry.item
readonly property var nonWatchAccounts: SortFilterProxyModel {
sourceModel: root.swapStore.accounts
@ -45,6 +46,15 @@ QObject {
FastExpressionRole {
name: "fromToken"
expression: root.fromToken
},
FastExpressionRole {
name: "colorizedChainPrefixes"
function getChainShortNames(chainIds) {
const chainShortNames = root.getNetworkShortNames(chainIds)
return WalletUtils.colorizedChainPrefix(chainShortNames)
}
expression: getChainShortNames(model.preferredSharingChainIds)
expectedRoles: ["preferredSharingChainIds"]
}
]
}
@ -98,6 +108,27 @@ QObject {
// FIXME sort by assetsController instead, to have the sorting/order as in the main wallet view
}
ModelEntry {
id: fromTokenEntry
sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel
key: "key"
value: root.swapFormData.fromTokensKey
}
ModelEntry {
id: toTokenEntry
sourceModel: root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel
key: "key"
value: root.swapFormData.toTokenKey
}
ModelEntry {
id: selectedAccountEntry
sourceModel: root.nonWatchAccounts
key: "address"
value: root.swapFormData.selectedAccountAddress
}
QtObject {
id: d
@ -143,19 +174,31 @@ QObject {
}
function processAccountBalance(address) {
if (!root.swapFormData.fromTokensKey) {
return null
}
let network = ModelUtils.getByKey(root.filteredFlatNetworksModel, "chainId", root.swapFormData.selectedNetworkChainId)
if(!!network) {
if (!network) {
return null
}
let balancesModel = ModelUtils.getByKey(filteredBalancesModel, "tokensKey", root.swapFormData.fromTokensKey, "balances")
let accountBalance = ModelUtils.getByKey(balancesModel, "account", address)
if(!accountBalance) {
if(accountBalance) {
let balance = AmountsArithmetic.toNumber(accountBalance.balance, root.fromToken.decimals)
let formattedBalance = root.formatCurrencyAmount(balance, root.fromToken.symbol)
accountBalance.formattedBalance = formattedBalance
return accountBalance
}
return {
balance: "0",
iconUrl: network.iconUrl,
chainColor: network.chainColor}
chainColor: network.chainColor,
formattedBalance: root.formatCurrencyAmount(.0 , root.fromToken.symbol)
}
return accountBalance
}
return null
}
/* Internal function to calculate total balance */
@ -244,21 +287,6 @@ QObject {
return disabledChainIds.join(":")
}
// TODO: remove once the AccountsModalHeader is reworked!!
function getSelectedAccountAddressByIndex(index) {
if (root.nonWatchAccounts.count > 0 && index >= 0) {
return ModelUtils.get(nonWatchAccounts, index, "address")
}
return ""
}
function getSelectedAccountByAddress(address) {
if (root.nonWatchAccounts.count > 0 && !!address) {
return ModelUtils.getByKey(root.nonWatchAccounts, "address", address)
}
return null
}
function fetchSuggestedRoutes(cryptoValueRaw) {
if (root.swapFormData.isFormFilledCorrectly() && !!cryptoValueRaw) {
root.swapOutputData.reset()
@ -267,7 +295,7 @@ QObject {
// Identify new swap with a different uuid
d.uuid = Utils.uuid()
let account = getSelectedAccountByAddress(root.swapFormData.selectedAccountAddress)
let account = selectedAccountEntry.item
let accountAddress = account.address
let disabledChainIds = getDisabledChainIds(root.swapFormData.selectedNetworkChainId)
let preferedChainIds = getAllChainIds()
@ -283,7 +311,7 @@ QObject {
}
function sendApproveTx() {
let account = getSelectedAccountByAddress(root.swapFormData.selectedAccountAddress)
let account = selectedAccountEntry.item
let accountAddress = account.address
root.swapStore.authenticateAndTransfer(d.uuid, accountAddress, accountAddress,
@ -292,7 +320,7 @@ QObject {
}
function sendSwapTx() {
let account = getSelectedAccountByAddress(root.swapFormData.selectedAccountAddress)
let account = selectedAccountEntry.item
let accountAddress = account.address
root.swapStore.authenticateAndTransfer(d.uuid, accountAddress, accountAddress,

View File

@ -1,8 +1,10 @@
import QtQuick 2.15
import StatusQ 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import AppLayouts.Wallet 1.0
import AppLayouts.Wallet.services.dapps 1.0
import AppLayouts.Profile.stores 1.0
import shared.stores 1.0
@ -30,6 +32,17 @@ QObject {
value: Constants.watchWalletType
inverted: true
}
proxyRoles: [
FastExpressionRole {
name: "colorizedChainPrefixes"
function getChainShortNames(chainIds) {
const chainShortNames = root.walletStore.getNetworkShortNames(chainIds)
return WalletUtils.colorizedChainPrefix(chainShortNames)
}
expression: getChainShortNames(model.preferredSharingChainIds)
expectedRoles: ["preferredSharingChainIds"]
}
]
}
readonly property var flatNetworks: root.walletStore ? root.walletStore.flatNetworks : null

View File

@ -440,8 +440,8 @@ QtObject {
walletSection.runEditAccountPopup(address)
}
function switchReceiveAccount(index) {
walletSectionSend.switchReceiveAccount(index)
function switchReceiveAccountByAddress(address) {
walletSectionSend.switchReceiveAccountByAddress(address)
}
function toggleWatchOnlyAccounts() {

View File

@ -27,7 +27,6 @@ Rectangle {
id: root
objectName: "walletLeftTab"
property alias currentAccountIndex: walletAccountsListView.currentIndex
property var networkConnectionStore
property var selectAllAccounts: function(){}
property var changeSelectedAccount: function(){}

View File

@ -1960,11 +1960,11 @@ Item {
return WalletStore.RootStore.selectedReceiveAccount
}
onSelectedAccountIndexChanged: {
onUpdateSelectedAddress: (address) => {
if (showQR.showSingleAccount || showQR.showForSavedAddress) {
return
}
WalletStore.RootStore.switchReceiveAccount(selectedIndex)
WalletStore.RootStore.switchReceiveAccountByAddress(address)
}
onUpdatePreferredChains: {

View File

@ -0,0 +1,137 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQml 2.15
import StatusQ 0.1
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import utils 1.0
import shared.controls 1.0
/**
Expected model structure:
name [string] - account name e.g. "Piggy Bank"
address [string] - wallet account address e.g. "0x1234567890"
colorizedChainPrefixes [string] - chain prefixes with rich text colors e.g. "<font color=\"red\">eth:</font><font color=\"blue\">oeth:</font><font color=\"green\">arb:</font>"
emoji [string] - emoji for account e.g. "🐷"
colorId [string] - color id for account e.g. "1"
currencyBalance [var] - fiat currency balance
amount [number] - amount of currency e.g. 1234
symbol [string] - currency symbol e.g. "USD"
optDisplayDecimals [number] - optional number of decimals to display
stripTrailingZeroes [bool] - strip trailing zeroes
walletType [string] - wallet type e.g. Constants.watchWalletType. See `Constants` for possible values
migratedToKeycard [bool] - whether account is migrated to keycard
accountBalance [var] - account balance for a specific network
formattedBalance [string] - formatted balance e.g. "1234.56B"
balance [string] - balance e.g. "123456000000"
iconUrl [string] - icon url e.g. "network/Network=Hermez"
chainColor [string] - chain color e.g. "#FF0000"
**/
StatusComboBox {
id: root
// input property for programatic selection
property string selectedAddress: ""
// output property for selected account
readonly property alias currentAccount: selectedEntry.item
readonly property string currentAccountAddress: root.control.currentValue ?? ""
// styling options
type: StatusComboBox.Type.Secondary
size: StatusComboBox.Size.Small
currentIndex: {
if (count === 0) return
return Math.max(control.indexOfValue(d.currentAccountSelection), 0)
}
objectName: "accountSelector"
popupContentItemObjectName: "accountSelectorList"
control.popup.width: 430
control.valueRole: "address"
control.textRole: "name"
implicitHeight: control.implicitHeight
implicitWidth: control.implicitWidth
contentItem: RowLayout {
id: contentItemRow
spacing: 4
StatusSmartIdenticon {
id: assetContent
objectName: "assetContent"
asset.emoji: currentAccount.emoji ?? ""
asset.color: currentAccount.color ?? Theme.palette.baseColor1
asset.width: 24
asset.height: asset.width
asset.isLetterIdenticon: !!currentAccount.emoji
asset.bgColor: Theme.palette.primaryColor3
visible: !!currentAccount.emoji
}
StatusBaseText {
id: textContent
objectName: "textContent"
Layout.fillWidth: true
Layout.fillHeight: true
text: currentAccount.name ?? ""
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
font.pixelSize: 13
color: Theme.palette.directColor1
}
}
delegate: WalletAccountListItem {
id: delegateItem
required property var model
width: ListView.view.width
name: model.name
address: model.address
chainShortNames: model.colorizedChainPrefixes ?? ""
emoji: model.emoji
walletColor: Utils.getColorForId(model.colorId)
currencyBalance: model.currencyBalance
walletType: model.walletType
migratedToKeycard: model.migratedToKeycard ?? false
accountBalance: model.accountBalance ?? null
color: sensor.containsMouse || highlighted ?
Theme.palette.baseColor2 :
!!currentAccount && currentAccount.name === model.name ? Theme.palette.statusListItem.highlightColor : "transparent"
onClicked: {
d.currentAccountSelection = model.address
control.popup.close()
}
}
ModelEntry {
id: selectedEntry
sourceModel: root.model ?? null
key: "address"
value: control.currentValue
}
QtObject {
id: d
property string currentAccountSelection: root.selectedAddress
Binding on currentAccountSelection {
value: root.selectedAddress
}
}
}

View File

@ -0,0 +1,62 @@
import QtQuick 2.15
import QtQuick.Layouts 1.15
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
AccountSelector {
id: root
control.padding: 0
control.rightInset: -6 //broken indicator positioning
control.spacing: 4
indicator.color: Theme.palette.indirectColor1
control.background: Rectangle {
objectName: "headerBackground"
radius: 8
color: d.headerStyleBackgroundColor
}
contentItem: RowLayout {
id: contentItemRow
spacing: 0
StatusSmartIdenticon {
id: assetContent
objectName: "assetContent"
asset.emoji: currentAccount.emoji ?? ""
asset.color: d.headerStyleBackgroundColor
asset.width: 32
asset.height: asset.width
asset.isLetterIdenticon: !!currentAccount.emoji
asset.bgColor: Theme.palette.primaryColor3
visible: !!currentAccount.emoji
}
StatusBaseText {
id: textContent
objectName: "textContent"
Layout.fillWidth: true
Layout.fillHeight: true
text: currentAccount.name ?? ""
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
elide: Text.ElideRight
font.pixelSize: 15
color: Theme.palette.indirectColor1
}
}
QtObject {
id: d
readonly property color headerStyleBackgroundColor: !!currentAccount ? root.control.hovered ?
Utils.getHoveredColor(currentAccount.colorId) :
Utils.getColorForId(currentAccount.colorId) : "transparent"
}
}

View File

@ -0,0 +1,104 @@
import QtQuick 2.15
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import AppLayouts.Wallet 1.0
import utils 1.0
StatusListItem {
id: root
property bool clearVisible: false
required property string name
required property string address
required property string chainShortNames
required property string emoji
required property string walletColor
required property var currencyBalance
required property string walletType
required property bool migratedToKeycard
/*
formattedBalance [string] - formatted balance e.g. "1234.56B"
balance [string] - balance e.g. "123456000000"
iconUrl [string] - icon url e.g. "network/Network=Hermez"
chainColor [string] - chain color e.g. "#FF0000"
*/
property var accountBalance: null
signal cleared()
objectName: root.name
height: visible ? 64 : 0
title: root.name
subTitle:{
if(!!root.address) {
let elidedAddress = StatusQUtils.Utils.elideText(root.address,6,4)
return sensor.containsMouse ? root.chainShortNames || Utils.richColorText(elidedAddress, Theme.palette.directColor1) : elidedAddress
}
return ""
}
statusListItemSubTitle.wrapMode: Text.NoWrap
asset.emoji: root.emoji
asset.color: root.walletColor
asset.name: root.emoji ? "filled-account": ""
asset.letterSize: 14
asset.isLetterIdenticon: !!root.emoji
asset.bgColor: Theme.palette.indirectColor1
asset.width: 40
asset.height: 40
radius: 0
color: sensor.containsMouse || highlighted ? Theme.palette.baseColor2 : "transparent"
components: [
Column {
anchors.verticalCenter: parent.verticalCenter
StatusTextWithLoadingState {
objectName: "walletAccountCurrencyBalance"
anchors.right: parent.right
font.pixelSize: 15
text: !!root.currencyBalance ? LocaleUtils.currencyAmountToLocaleString(root.currencyBalance) : ""
}
StatusIcon {
objectName: "walletAccountTypeIcon"
anchors.right: parent.right
width: !!icon ? 15: 0
height: !!icon ? 15 : 0
color: Theme.palette.directColor1
icon: root.walletType === Constants.watchWalletType ? "show" :
root.migratedToKeycard ? "keycard" : ""
}
},
StatusClearButton {
anchors.verticalCenter: parent.verticalCenter
visible: root.clearVisible
onClicked: root.cleared()
}
]
inlineTagModel: !!root.accountBalance && !!root.accountBalance.formattedBalance ? 1 : 0
inlineTagDelegate: StatusListItemTag {
objectName: "inlineTagDelegate_" + index
background: null
height: 16
asset.height: 16
asset.width: 16
title: root.accountBalance.formattedBalance
titleText.font.pixelSize: 12
titleText.color: root.accountBalance.balance === "0" ? Theme.palette.baseColor1 : Theme.palette.directColor1
asset.isImage: true
asset.name: Style.svg("tiny/%1".arg(root.accountBalance.iconUrl))
asset.color: root.accountBalance.chainColor
closeButtonVisible: false
hoverEnabled: true
tagClickable: true
onTagClicked: root.clicked(root.itemId, mouse)
onClicked: root.clicked(root.itemId, mouse)
}
}

View File

@ -1,3 +1,5 @@
AccountSelector 1.0 AccountSelector.qml
AccountSelectorHeader 1.0 AccountSelectorHeader.qml
AddressInput 1.0 AddressInput.qml
AmountInput 1.0 AmountInput.qml
AssetAndAmountInput 1.0 AssetAndAmountInput.qml
@ -48,6 +50,7 @@ TransactionAddressTile 1.0 TransactionAddressTile.qml
TransactionDataTile 1.0 TransactionDataTile.qml
TransactionDelegate 1.0 TransactionDelegate.qml
TransactionDetailsHeader.qml 1.0 TransactionDetailsHeader.qml
WalletAccountListItem 1.0 WalletAccountListItem.qml
MockedKeycardReaderStateSelector 1.0 MockedKeycardReaderStateSelector.qml
MockedKeycardStateSelector 1.0 MockedKeycardStateSelector.qml
AssetsSectionDelegate 1.0 AssetsSectionDelegate.qml

View File

@ -5,8 +5,11 @@ import QtQuick.Dialogs 1.3
import QtGraphicalEffects 1.0
import SortFilterProxyModel 0.2
import AppLayouts.Wallet 1.0
import utils 1.0
import shared.stores.send 1.0
import shared.controls 1.0
import StatusQ 0.1
import StatusQ.Components 0.1
@ -188,24 +191,40 @@ StatusDialog {
onClosed: popup.store.resetStoredProperties()
header: AccountsModalHeader {
header: Item {
implicitHeight: accountSelector.implicitHeight
implicitWidth: accountSelector.implicitWidth
anchors.top: parent.top
anchors.topMargin: -height - 18
AccountSelectorHeader {
id: accountSelector
model: SortFilterProxyModel {
sourceModel: popup.store.senderAccounts
sorters: RoleSorter { roleName: "position"; sortOrder: Qt.AscendingOrder }
proxyRoles: [
FastExpressionRole {
name: "colorizedChainPrefixes"
function getChainShortNames(chainIds) {
const chainShortNames = popup.store.getNetworkShortNames(chainIds)
return WalletUtils.colorizedChainPrefix(chainShortNames)
}
selectedAccount: !!popup.preSelectedAccount ? popup.preSelectedAccount: {}
getNetworkShortNames: function(chainIds) {return store.getNetworkShortNames(chainIds)}
onSelectedIndexChanged: {
store.switchSenderAccount(selectedIndex)
expression: getChainShortNames(model.preferredSharingChainIds)
expectedRoles: ["preferredSharingChainIds"]
}
]
}
selectedAddress: !!popup.preSelectedAccount && !!popup.preSelectedAccount.address ? popup.preSelectedAccount.address : ""
onCurrentAccountAddressChanged: {
store.switchSenderAccountByAddress(currentAccountAddress)
if (d.isSelectedHoldingValidAsset) {
d.setSelectedHoldingId(d.selectedHolding.symbol, d.selectedHoldingType)
}
popup.recalculateRoutesAndFees()
}
}
}
ColumnLayout {
id: group1

View File

@ -1,97 +0,0 @@
import QtQuick 2.15
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import utils 1.0
import shared.controls 1.0
StatusComboBox {
id: root
property var selectedAccount
property var getNetworkShortNames: function(chainIds){}
property var formatCurrencyAmount: function(balance, symbol){}
property int selectedIndex: -1
objectName: "accountsModalHeader"
popupContentItemObjectName: "accountSelectorList"
control.padding: 0
control.spacing: 0
control.leftPadding: 8
control.rightPadding: 8
control.topPadding: 10
control.popup.width: 430
control.indicator: null
control.background: Rectangle {
objectName: "headerBackground"
width: contentItem.childrenRect.width + control.leftPadding + control.rightPadding
height: 32
radius: 8
color: !!selectedAccount ? hoverHandler.hovered ?
Utils.getHoveredColor(selectedAccount.colorId) :
Utils.getColorForId(selectedAccount.colorId) : "transparent"
HoverHandler {
id: hoverHandler
cursorShape: Qt.PointingHandCursor
}
}
contentItem: Row {
anchors.verticalCenter: parent.verticalCenter
width: childrenRect.width
spacing: 8
Padding {}
StatusEmoji {
objectName: "headerContentItemEmoji"
anchors.verticalCenter: parent.verticalCenter
width: 16
height: 16
emojiId: StatusQUtils.Emoji.iconId(!!selectedAccount && !!selectedAccount.emoji ? selectedAccount.emoji : "", StatusQUtils.Emoji.size.verySmall) || ""
visible: !!emojiId
}
StatusBaseText {
objectName: "headerContentItemText"
anchors.verticalCenter: parent.verticalCenter
text: !!selectedAccount && !!selectedAccount.name ? selectedAccount.name : ""
font.pixelSize: 15
color: Theme.palette.indirectColor1
}
StatusIcon {
anchors.verticalCenter: parent.verticalCenter
width: 16
height: width
visible: !!root.model && root.model.count > 1
icon: "chevron-down"
color: Theme.palette.indirectColor1
}
Padding {}
}
delegate: WalletAccountListItem {
width: ListView.view.width
modelData: model
getNetworkShortNames: root.getNetworkShortNames
formatCurrencyAmount: root.formatCurrencyAmount
color: sensor.containsMouse || highlighted ?
Theme.palette.baseColor2 :
!!selectedAccount && selectedAccount.name === model.name ? Theme.palette.statusListItem.highlightColor : "transparent"
onClicked: {
selectedIndex = index
control.popup.close()
}
Component.onCompleted:{
if(!!selectedAccount && selectedAccount.address === model.address)
selectedIndex = index
}
}
}

View File

@ -34,9 +34,7 @@ StatusListItem {
radius: 0
color: sensor.containsMouse || highlighted ? Theme.palette.baseColor2 : "transparent"
components: [
ClearButton {
width: 24
height: 24
StatusClearButton {
visible: root.clearVisible
onClicked: root.cleared()
}

View File

@ -1,90 +0,0 @@
import QtQuick 2.15
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import AppLayouts.Wallet 1.0
import utils 1.0
StatusListItem {
id: root
property var modelData
property var getNetworkShortNames: function(chainIds){}
property bool clearVisible: false
property var formatCurrencyAmount: function(balances, symbols){}
signal cleared()
objectName: !!modelData ? modelData.name: ""
height: visible ? 64 : 0
title: !!modelData && !!modelData.name ? modelData.name : ""
subTitle:{
if(!!modelData) {
let elidedAddress = StatusQUtils.Utils.elideText(modelData.address,6,4)
let chainShortNames = root.getNetworkShortNames(modelData.preferredSharingChainIds)
return sensor.containsMouse ? WalletUtils.colorizedChainPrefix(chainShortNames) || Utils.richColorText(elidedAddress, Theme.palette.directColor1) : elidedAddress
}
return ""
}
statusListItemSubTitle.wrapMode: Text.NoWrap
asset.emoji: !!modelData && !!modelData.emoji ? modelData.emoji: ""
asset.color: !!modelData ? Utils.getColorForId(modelData.colorId): ""
asset.name: !!modelData && !modelData.emoji ? "filled-account": ""
asset.letterSize: 14
asset.isLetterIdenticon: !!modelData && !!modelData.emoji ? true : false
asset.bgColor: Theme.palette.indirectColor1
asset.width: 40
asset.height: 40
radius: 0
color: sensor.containsMouse || highlighted ? Theme.palette.baseColor2 : "transparent"
components: [
Column {
anchors.verticalCenter: parent.verticalCenter
StatusTextWithLoadingState {
objectName: "walletAccountCurrencyBalance"
anchors.right: parent.right
font.pixelSize: 15
text: LocaleUtils.currencyAmountToLocaleString(!!modelData ? modelData.currencyBalance: "")
}
StatusIcon {
objectName: "walletAccountTypeIcon"
anchors.right: parent.right
width: !!icon ? 15: 0
height: !!icon ? 15 : 0
color: Theme.palette.directColor1
icon: !!modelData ? modelData.walletType === Constants.watchWalletType ? "show" :
modelData.migratedToKeycard ? "keycard" : "" : ""
}
},
ClearButton {
anchors.verticalCenter: parent.verticalCenter
width: 24
height: 24
visible: root.clearVisible
onClicked: root.cleared()
}
]
inlineTagModel: !!root.modelData.fromToken && !!root.modelData.accountBalance ? 1 : 0
inlineTagDelegate: StatusListItemTag {
objectName: "inlineTagDelegate_" + index
readonly property double balance: StatusQUtils.AmountsArithmetic.toNumber(root.modelData.accountBalance.balance, root.modelData.fromToken.decimals)
background: null
height: 16
asset.height: 16
asset.width: 16
title: root.formatCurrencyAmount(balance, root.modelData.fromToken.symbol)
titleText.font.pixelSize: 12
titleText.color: balance === 0 ? Theme.palette.baseColor1 : Theme.palette.directColor1
asset.isImage: true
asset.name: Style.svg("tiny/%1".arg(root.modelData.accountBalance.iconUrl))
asset.color: root.modelData.accountBalance.chainColor
closeButtonVisible: false
}
}

View File

@ -1,4 +1,3 @@
AccountsModalHeader 1.0 AccountsModalHeader.qml
WalletAccountListItem 1.0 WalletAccountListItem.qml
GasSelector 1.0 GasSelector.qml
GasValidator 1.0 GasValidator.qml

View File

@ -8,6 +8,9 @@ import StatusQ.Core.Utils 0.1 as StatusQUtils
import AppLayouts.Wallet 1.0
import shared.controls 1.0 as SharedControls
import shared.stores.send 1.0
import utils 1.0
import "../controls"
@ -15,7 +18,7 @@ import "../controls"
Loader {
id: root
property var store
property TransactionStore store
property bool isCollectiblesTransfer
property bool isBridgeTx: false
property bool interactive: true
@ -138,19 +141,29 @@ Loader {
Component {
id: myAccountRecipient
WalletAccountListItem {
property string chainShortNames: !!modelData ? store.getNetworkShortNames(modelData.preferredSharingChainIds): ""
SharedControls.WalletAccountListItem {
id: accountItem
readonly property var modelData: root.selectedRecipient
name: !!modelData ? modelData.name : ""
address: !!modelData ? modelData.address : ""
chainShortNames: !!modelData ? store.getNetworkShortNames(modelData.preferredSharingChainIds) : ""
emoji: !!modelData ? modelData.emoji : ""
walletColor: !!modelData ? Utils.getColorForId(modelData.colorId): ""
currencyBalance: !!modelData ? modelData.currencyBalance : ""
walletType: !!modelData ? modelData.walletType : ""
migratedToKeycard: !!modelData ? modelData.migratedToKeycard ?? false : false
accountBalance: !!modelData ? modelData.accountBalance : null
implicitWidth: parent.width
modelData: root.selectedRecipient
radius: 8
clearVisible: true
color: Theme.palette.indirectColor1
sensor.enabled: false
subTitle: {
if(!!modelData) {
let elidedAddress = StatusQUtils.Utils.elideText(modelData.address,6,4)
let chainShortNames = store.getNetworkShortNames(modelData.preferredSharingChainIds)
return WalletUtils.colorizedChainPrefix(chainShortNames) + StatusQUtils.Utils.elideText(elidedAddress,6,4)
const elidedAddress = StatusQUtils.Utils.elideAndFormatWalletAddress(modelData.address)
return WalletUtils.colorizedChainPrefix(accountItem.chainShortNames) + elidedAddress
}
return ""
}
@ -192,9 +205,7 @@ Loader {
color: Theme.palette.primaryColor1
visible: root.ready
}
ClearButton {
Layout.preferredWidth: 24
Layout.preferredHeight: 24
StatusClearButton {
visible: !!store.plainText(recipientInput.text)
onClicked: {
recipientInput.input.edit.clear()

View File

@ -4,6 +4,7 @@ import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import utils 1.0
import shared.controls 1.0 as SharedControls
import shared.stores 1.0
import AppLayouts.Wallet 1.0
@ -117,17 +118,30 @@ Item {
id: myAccounts
objectName: "myAccountsList"
delegate: WalletAccountListItem {
delegate: SharedControls.WalletAccountListItem {
required property var model
implicitWidth: ListView.view.width
modelData: model
getNetworkShortNames: root.store.getNetworkShortNames
onClicked: recipientSelected({name: modelData.name,
address: modelData.address,
color: modelData.color,
emoji: modelData.emoji,
walletType: modelData.walletType,
currencyBalance: modelData.currencyBalance,
preferredSharingChainIds: modelData.preferredSharingChainIds},
name: model.name
address: model.address
emoji: model.emoji
walletColor: Utils.getColorForId(model.colorId)
currencyBalance: model.currencyBalance
walletType: model.walletType
migratedToKeycard: model.migratedToKeycard ?? false
accountBalance: model.accountBalance ?? null
chainShortNames: {
const chainShortNames = store.getNetworkShortNames(model.preferredSharingChainIds)
return WalletUtils.colorizedChainPrefix(chainShortNames)
}
onClicked: recipientSelected({name: model.name,
address: model.address,
color: model.color,
emoji: model.emoji,
walletType: model.walletType,
currencyBalance: model.currencyBalance,
preferredSharingChainIds: model.preferredSharingChainIds},
TabAddressSelectorView.Type.Account)
}

View File

@ -13,6 +13,7 @@ import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import shared.controls 1.0
// TODO extract the components to StatusQ
import shared.popups.send.controls 1.0
@ -179,27 +180,14 @@ StatusDialog {
Layout.fillWidth: true
}
// TODO: have a reusable component for this
AccountsModalHeader {
AccountSelector {
id: accountsDropdown
Layout.preferredWidth: 204
control.enabled: d.connectionStatus === root.notConnectedStatus && count > 1
model: d.accountsProxy
onCountChanged: {
if (count > 0) {
selectedAccount = d.accountsProxy.get(0)
}
}
selectedAccount: d.accountsProxy.get(0)
onSelectedAccountChanged: d.selectedAccount = selectedAccount
onSelectedIndexChanged: {
d.selectedAccount = model.get(selectedIndex)
selectedAccount = d.selectedAccount
}
onCurrentAccountChanged: d.selectedAccount = currentAccount
}
}
@ -383,7 +371,7 @@ StatusDialog {
sorters: RoleSorter { roleName: "position"; sortOrder: Qt.AscendingOrder }
}
property var selectedAccount: accountsProxy.count > 0 ? accountsProxy.get(0) : null
property var selectedAccount: ({})
readonly property var filteredChains: LeftJoinModel {
leftModel: d.dappChains

View File

@ -188,8 +188,8 @@ QtObject {
}
}
function switchSenderAccount(index) {
walletSectionSendInst.switchSenderAccount(index)
function switchSenderAccountByAddress(address) {
walletSectionSendInst.switchSenderAccountByAddress(address)
}
function getNetworkShortNames(chainIds) {