feat: [UI - Swap] Create swap input component

- new panel created: `SwapInputPanel`
- some cleanups to the needed stores
- created a SB page demonstrating the use of 2 panels and the
`SwapExchangeButton`
- created QML tests

Fixes #14781
This commit is contained in:
Lukáš Tinkl 2024-05-28 19:39:41 +02:00 committed by Lukáš Tinkl
parent a7b9a62745
commit a3c9012f4a
36 changed files with 1045 additions and 227 deletions

View File

@ -38,7 +38,7 @@ SplitView {
maxInputBalance: inputIsFiat ? root.maxCryptoBalance*amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount maxInputBalance: inputIsFiat ? root.maxCryptoBalance*amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount
: root.maxCryptoBalance : root.maxCryptoBalance
currentCurrency: "Fiat" currentCurrency: "USD"
formatCurrencyAmount: function(amount, symbol, options, locale) { formatCurrencyAmount: function(amount, symbol, options, locale) {
const currencyAmount = { const currencyAmount = {
amount: amount, amount: amount,
@ -57,15 +57,9 @@ SplitView {
LogsAndControlsPanel { LogsAndControlsPanel {
id: logsAndControlsPanel id: logsAndControlsPanel
SplitView.minimumHeight: 100 SplitView.minimumHeight: 250
logsView.logText: logs.logText logsView.logText: logs.logText
}
}
Pane {
SplitView.minimumWidth: 300
SplitView.preferredWidth: 300
ColumnLayout { ColumnLayout {
Label { Label {
@ -101,6 +95,7 @@ SplitView {
} }
} }
} }
}
} }
// category: Components // category: Components

View File

@ -47,7 +47,7 @@ SplitView {
ColumnLayout { ColumnLayout {
Repeater { Repeater {
model: [0.1, 0.5, 0.24, 0.8, 120.84] model: [0, 0.1, 0.5, 0.24, 0.8, 120.84]
Button { Button {
text: "set " + modelData text: "set " + modelData

View File

@ -0,0 +1,203 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import StatusQ 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
import shared.stores 1.0
import shared.stores.send 1.0
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.panels 1.0
import AppLayouts.Wallet.controls 1.0
import AppLayouts.Wallet.popups.swap 1.0
import Models 1.0
import Storybook 1.0
import SortFilterProxyModel 0.2
SplitView {
id: root
Logs { id: logs }
QtObject {
id: d
readonly property SwapInputParamsForm swapInputParamsForm: SwapInputParamsForm {
fromTokensKey: ctrlFromTokensKey.text
fromTokenAmount: ctrlFromTokenAmount.text
toTokenKey: ctrlToTokenKey.text
toTokenAmount: ctrlToTokenAmount.text
}
readonly property SwapModalAdaptor adaptor: SwapModalAdaptor {
swapStore: SwapStore {
readonly property var accounts: WalletAccountsModel {}
readonly property var flatNetworks: NetworksModel.flatNetworks
readonly property bool areTestNetworksEnabled: false
}
walletAssetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
currencyStore: CurrenciesStore {}
swapFormData: d.swapInputParamsForm
}
}
Rectangle {
SplitView.fillWidth: true
SplitView.fillHeight: true
color: Theme.palette.baseColor3
Item {
width: 492
height: payPanel.height + receivePanel.height + 4
anchors.centerIn: parent
SwapInputPanel {
id: payPanel
anchors {
left: parent.left
right: parent.right
top: parent.top
}
currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.filteredFlatNetworksModel
processedAssetsModel: d.adaptor.processedAssetsModel
tokenKey: d.swapInputParamsForm.fromTokensKey
tokenAmount: d.swapInputParamsForm.fromTokenAmount
swapSide: SwapInputPanel.SwapSide.Pay
fiatInputInteractive: ctrlFiatInputInteractive.checked
swapExchangeButtonWidth: swapButton.width
loading: ctrlLoading.checked
}
SwapInputPanel {
id: receivePanel
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.filteredFlatNetworksModel
processedAssetsModel: d.adaptor.processedAssetsModel
tokenKey: d.swapInputParamsForm.toTokenKey
tokenAmount: d.swapInputParamsForm.toTokenAmount
swapSide: SwapInputPanel.SwapSide.Receive
fiatInputInteractive: ctrlFiatInputInteractive.checked
swapExchangeButtonWidth: swapButton.width
loading: ctrlLoading.checked
}
SwapExchangeButton {
id: swapButton
anchors.centerIn: parent
}
}
}
LogsAndControlsPanel {
id: logsAndControlsPanel
SplitView.minimumWidth: 250
SplitView.preferredWidth: 250
logsView.logText: logs.logText
ColumnLayout {
anchors.fill: parent
RowLayout {
Layout.fillWidth: true
Label {
text: "Pay symbol:"
}
TextField {
Layout.fillWidth: true
id: ctrlFromTokensKey
}
}
RowLayout {
Layout.fillWidth: true
Label {
text: "Pay amount:"
}
TextField {
Layout.fillWidth: true
id: ctrlFromTokenAmount
}
}
RowLayout {
Layout.fillWidth: true
Label {
text: "Receive symbol:"
}
TextField {
Layout.fillWidth: true
id: ctrlToTokenKey
text: "STT"
}
}
RowLayout {
Layout.fillWidth: true
Label {
text: "Receive amount:"
}
TextField {
Layout.fillWidth: true
id: ctrlToTokenAmount
}
}
Switch {
id: ctrlFiatInputInteractive
text: "Fiat input interactive"
checked: false
}
Switch {
id: ctrlLoading
text: "Loading"
}
Label {
Layout.fillWidth: true
font.weight: Font.Medium
text: "<b>Pay:</b><ul><li>Symbol: %1<li>Amount: %2<li>Valid: %3"
.arg(payPanel.selectedHoldingId || "N/A")
.arg(payPanel.cryptoValue.toString())
.arg(payPanel.cryptoValueValid ? "true" : "false")
}
Label {
Layout.fillWidth: true
font.weight: Font.Medium
text: "<b>Receive:</b><ul><li>Symbol: %1<li>Amount: %2<li>Valid: %3"
.arg(receivePanel.selectedHoldingId || "N/A")
.arg(receivePanel.cryptoValue.toString())
.arg(receivePanel.cryptoValueValid ? "true" : "false")
}
Item { Layout.fillHeight: true }
}
}
}
// category: Panels
// https://www.figma.com/design/TS0eQX9dAZXqZtELiwKIoK/Swap---Milestone-1?node-id=3404-111405&t=G96tBLQr2j73HT9X-0

View File

@ -170,7 +170,7 @@ SplitView {
StatusInput { StatusInput {
id: swapInput id: swapInput
Layout.preferredWidth: 100 Layout.preferredWidth: 100
label: "Token mount to swap" label: "Token amount to swap"
text: "100" text: "100"
} }

View File

@ -51,9 +51,7 @@ SplitView {
return currencyStore.formatCurrencyAmount(balance, "USD") return currencyStore.formatCurrencyAmount(balance, "USD")
} }
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){ formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
let bigIntBalance = AmountsArithmetic.fromString(balance) return currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
let decimalBalance = AmountsArithmetic.toNumber(bigIntBalance, decimals)
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
} }
} }
} }

View File

@ -10,6 +10,10 @@ class Setup : public QObject
public slots: public slots:
void qmlEngineAvailable(QQmlEngine *engine) { void qmlEngineAvailable(QQmlEngine *engine) {
// custom code that needs QQmlEngine, register QML types, add import paths,... // custom code that needs QQmlEngine, register QML types, add import paths,...
QGuiApplication::setOrganizationName(QStringLiteral("Status"));
QGuiApplication::setOrganizationDomain(QStringLiteral("status.im"));
const QStringList additionalImportPaths { const QStringList additionalImportPaths {
STATUSQ_MODULE_IMPORT_PATH, STATUSQ_MODULE_IMPORT_PATH,
QML_IMPORT_ROOT + QStringLiteral("/../ui/app"), QML_IMPORT_ROOT + QStringLiteral("/../ui/app"),

View File

@ -80,6 +80,7 @@ Item {
keyClick(Qt.Key_3) keyClick(Qt.Key_3)
compare(controlUnderTest.text, "13") compare(controlUnderTest.text, "13")
compare(controlUnderTest.valid, true)
} }
function test_defaultValidation() { function test_defaultValidation() {

View File

@ -0,0 +1,252 @@
import QtQuick 2.15
import QtTest 1.15
import StatusQ 0.1
import AppLayouts.Wallet.stores 1.0
import AppLayouts.Wallet.panels 1.0
import AppLayouts.Wallet.popups.swap 1.0
import shared.stores 1.0
import SortFilterProxyModel 0.2
import Models 1.0
import Storybook 1.0
Item {
id: root
width: 600
height: 400
QtObject {
id: d
readonly property SwapModalAdaptor adaptor: SwapModalAdaptor {
swapStore: SwapStore {
readonly property var accounts: WalletAccountsModel {}
readonly property var flatNetworks: NetworksModel.flatNetworks
readonly property bool areTestNetworksEnabled: false
}
walletAssetsStore: WalletAssetsStore {
id: thisWalletAssetStore
walletTokensStore: TokensStore {
plainTokensBySymbolModel: TokensBySymbolModel {}
}
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
}
currencyStore: CurrenciesStore {}
swapFormData: SwapInputParamsForm {}
}
}
Component {
id: componentUnderTest
SwapInputPanel {
anchors.centerIn: parent
currencyStore: d.adaptor.currencyStore
flatNetworksModel: d.adaptor.filteredFlatNetworksModel
processedAssetsModel: d.adaptor.processedAssetsModel
}
}
property SwapInputPanel controlUnderTest: null
TestCase {
name: "SwapInputPanel"
when: windowShown
function test_basicSetupAndDefaults() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
verify(!!controlUnderTest)
verify(controlUnderTest.width > 0)
verify(controlUnderTest.height > 0)
tryCompare(controlUnderTest, "swapSide", SwapInputPanel.SwapSide.Pay)
tryCompare(controlUnderTest, "caption", qsTr("Pay"))
tryCompare(controlUnderTest, "selectedHoldingId", "")
tryCompare(controlUnderTest, "cryptoValue", 0)
tryCompare(controlUnderTest, "cryptoValueRaw", "0")
}
function test_basicSetupReceiveSide() {
controlUnderTest = createTemporaryObject(componentUnderTest, root, {swapSide: SwapInputPanel.SwapSide.Receive})
verify(!!controlUnderTest)
verify(controlUnderTest.width > 0)
verify(controlUnderTest.height > 0)
tryCompare(controlUnderTest, "swapSide", SwapInputPanel.SwapSide.Receive)
tryCompare(controlUnderTest, "caption", qsTr("Receive"))
tryCompare(controlUnderTest, "selectedHoldingId", "")
tryCompare(controlUnderTest, "cryptoValue", 0)
tryCompare(controlUnderTest, "cryptoValueRaw", "0")
}
function test_basicSetupWithInitialProperties() {
controlUnderTest = createTemporaryObject(componentUnderTest, root,
{
swapSide: SwapInputPanel.SwapSide.Pay,
tokenKey: "STT",
tokenAmount: "10000000.0000001"
})
verify(!!controlUnderTest)
waitForRendering(controlUnderTest)
tryCompare(controlUnderTest, "swapSide", SwapInputPanel.SwapSide.Pay)
tryCompare(controlUnderTest, "selectedHoldingId", "STT")
tryCompare(controlUnderTest, "cryptoValue", 10000000.0000001)
verify(controlUnderTest.cryptoValueValid)
}
function test_setTokenKeyAndAmounts_data() {
return [
{ tag: "1.42", tokenAmount: "1.42", valid: true },
{ tag: "0.00001", tokenAmount: "0.00001", valid: true },
{ tag: "1234567890", tokenAmount: "1234567890", valid: true },
{ tag: "1234567890.1234567890", tokenAmount: "1234567890.1234567890", valid: true },
{ tag: "abc", tokenAmount: "abc", valid: false },
{ tag: "NaN", tokenAmount: "NaN", valid: false }
]
}
function test_setTokenKeyAndAmounts(data) {
const valid = data.valid
const tokenAmount = data.tokenAmount
const tokenSymbol = "STT"
controlUnderTest = createTemporaryObject(componentUnderTest, root)
verify(!!controlUnderTest)
controlUnderTest.tokenKey = tokenSymbol
controlUnderTest.tokenAmount = tokenAmount
tryCompare(controlUnderTest, "selectedHoldingId", tokenSymbol)
if (!valid)
expectFail(data.tag, "Invalid data expected to fail: %1".arg(tokenAmount))
tryCompare(controlUnderTest, "cryptoValue", parseFloat(tokenAmount))
tryCompare(controlUnderTest, "cryptoValueValid", true)
const holdingSelector = findChild(controlUnderTest, "holdingSelector")
verify(!!holdingSelector)
tryCompare(holdingSelector.selectedItem, "symbol", tokenSymbol)
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
tryCompare(amountToSendInput.input, "text", Number(tokenAmount).toLocaleString(Qt.locale(), 'f', -128))
}
function test_enterTokenAmountLocalizedNumber() {
controlUnderTest = createTemporaryObject(componentUnderTest, root, {tokenKey: "STT"})
verify(!!controlUnderTest)
waitForRendering(controlUnderTest)
tryCompare(controlUnderTest, "selectedHoldingId", "STT")
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
mouseClick(amountToSendInput)
waitForRendering(amountToSendInput)
verify(amountToSendInput.input.input.edit.activeFocus)
amountToSendInput.input.locale = Qt.locale("cs_CZ")
compare(amountToSendInput.input.locale.name, "cs_CZ")
// manually entering "1000000,00000042" meaning "1000000,00000042"; `,` being the decimal separator
keyClick(Qt.Key_1)
for (let i = 0; i < 6; i++)
keyClick(Qt.Key_0)
keyClick(Qt.Key_Comma)
for (let i = 0; i < 6; i++)
keyClick(Qt.Key_0)
keyClick(Qt.Key_4)
keyClick(Qt.Key_2)
tryCompare(amountToSendInput.input, "text", "1000000,00000042")
tryCompare(controlUnderTest, "cryptoValue", 1000000.00000042)
verify(controlUnderTest.cryptoValueValid)
}
function test_selectSTTHoldingAndTypeAmount() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
verify(!!controlUnderTest)
const holdingSelector = findChild(controlUnderTest, "holdingSelector")
verify(!!holdingSelector)
mouseClick(holdingSelector)
waitForRendering(holdingSelector)
const assetSelectorList = findChild(holdingSelector, "assetSelectorList")
verify(!!assetSelectorList)
waitForRendering(assetSelectorList)
const sttDelegate = findChild(assetSelectorList, "AssetSelector_ItemDelegate_STT")
verify(!!sttDelegate)
mouseClick(sttDelegate, 40, 40) // center might be covered by tags
tryCompare(controlUnderTest, "selectedHoldingId", "STT")
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
mouseClick(amountToSendInput)
waitForRendering(amountToSendInput)
verify(amountToSendInput.input.input.edit.activeFocus)
keyClick(Qt.Key_1)
keyClick(Qt.Key_Period)
keyClick(Qt.Key_4)
keyClick(Qt.Key_2)
tryCompare(controlUnderTest, "cryptoValue", 1.42)
verify(controlUnderTest.cryptoValueValid)
}
function test_clickingMaxButton() {
controlUnderTest = createTemporaryObject(componentUnderTest, root, {tokenKey: "ETH"})
verify(!!controlUnderTest)
waitForRendering(controlUnderTest)
tryCompare(controlUnderTest, "selectedHoldingId", "ETH")
const maxTagButton = findChild(controlUnderTest, "maxTagButton")
verify(!!maxTagButton)
waitForRendering(maxTagButton)
verify(maxTagButton.visible)
mouseClick(maxTagButton)
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
waitForRendering(amountToSendInput)
const maxValue = amountToSendInput.maxInputBalance
tryCompare(amountToSendInput.input, "text", maxValue.toLocaleString(Qt.locale(), 'f', -128))
tryCompare(controlUnderTest, "cryptoValue", maxValue)
verify(controlUnderTest.cryptoValueValid)
}
function test_loadingState() {
controlUnderTest = createTemporaryObject(componentUnderTest, root)
verify(!!controlUnderTest)
controlUnderTest.loading = true
const amountToSendInput = findChild(controlUnderTest, "amountToSendInput")
verify(!!amountToSendInput)
const amountInput = findChild(amountToSendInput, "amountInput")
verify(!!amountInput)
verify(!amountInput.visible)
const topAmountToSendInputLoadingComponent = findChild(amountToSendInput, "topAmountToSendInputLoadingComponent")
verify(!!topAmountToSendInputLoadingComponent)
verify(topAmountToSendInputLoadingComponent.visible)
const bottomItemText = findChild(amountToSendInput, "bottomItemText")
verify(!!bottomItemText)
verify(!bottomItemText.visible)
const bottomItemTextLoadingComponent = findChild(amountToSendInput, "bottomItemTextLoadingComponent")
verify(!!bottomItemTextLoadingComponent)
verify(bottomItemTextLoadingComponent.visible)
}
}
}

View File

@ -33,7 +33,7 @@ Item {
walletAssetsStore: WalletAssetsStore { walletAssetsStore: WalletAssetsStore {
id: thisWalletAssetStore id: thisWalletAssetStore
walletTokensStore: TokensStore { walletTokensStore: TokensStore {
readonly property var plainTokensBySymbolModel: TokensBySymbolModel {} plainTokensBySymbolModel: TokensBySymbolModel {}
} }
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {} readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel

View File

@ -1,4 +1,4 @@
import QtQuick 2.15 import QtQml 2.15
QtObject { QtObject {
id: root id: root

View File

@ -3,6 +3,8 @@ import QtQuick 2.15
QtObject { QtObject {
id: root id: root
property bool displayAssetsBelowBalance: false property var plainTokensBySymbolModel
property bool displayAssetsBelowBalance
property var getDisplayAssetsBelowBalanceThresholdDisplayAmount property var getDisplayAssetsBelowBalanceThresholdDisplayAmount
property double tokenListUpdatedAt
} }

View File

@ -8,7 +8,7 @@ import Models 1.0
QtObject { QtObject {
id: root id: root
property TokensStore walletTokensStore property TokensStore walletTokensStore: TokensStore {}
readonly property var groupedAccountsAssetsModel: GroupedAccountsAssetsModel {} readonly property var groupedAccountsAssetsModel: GroupedAccountsAssetsModel {}
property var assetsWithFilteredBalances property var assetsWithFilteredBalances
@ -56,5 +56,11 @@ QtObject {
joinRole: "communityId" joinRole: "communityId"
} }
property var assetsController property var assetsController: QtObject {
property int revision
function filterAcceptsSymbol(symbol) {
return true
}
}
} }

View File

@ -1,6 +1,7 @@
import QtQuick 2.15 import QtQuick 2.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
QtObject { QtObject {
id: root id: root
@ -16,6 +17,12 @@ QtObject {
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale) return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
} }
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
let bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
return formatCurrencyAmount(decimalBalance, symbol)
}
function getFiatValue(balance, cryptoSymbol) { function getFiatValue(balance, cryptoSymbol) {
return balance return balance
} }

View File

@ -12,7 +12,7 @@ import AppLayouts.Wallet.stores 1.0
QtObject { QtObject {
id: root id: root
readonly property var currencyStore: CurrenciesStore{} readonly property CurrenciesStore currencyStore: CurrenciesStore {}
readonly property var senderAccounts: WalletSendAccountsModel { readonly property var senderAccounts: WalletSendAccountsModel {
Component.onCompleted: selectedSenderAccount = senderAccounts.get(0) Component.onCompleted: selectedSenderAccount = senderAccounts.get(0)
} }
@ -280,7 +280,7 @@ QtObject {
}, },
FastExpressionRole { FastExpressionRole {
name: "currentBalance" name: "currentBalance"
expression: __getTotalBalance(model.balances, model.decimals, model.symbol, root.selectedSenderAccount) expression: __getTotalBalance(model.balances, model.decimals)
expectedRoles: ["balances", "decimals", "symbol"] expectedRoles: ["balances", "decimals", "symbol"]
}, },
FastExpressionRole { FastExpressionRole {
@ -302,7 +302,7 @@ QtObject {
name.toUpperCase().startsWith(searchString.toUpperCase()) || __searchAddressInList(addressPerChain, searchString) name.toUpperCase().startsWith(searchString.toUpperCase()) || __searchAddressInList(addressPerChain, searchString)
) )
} }
expression: search(symbol, name, addressPerChain, root.assetSearchString) expression: search(model.symbol, model.name, model.addressPerChain, root.assetSearchString)
expectedRoles: ["symbol", "name", "addressPerChain"] expectedRoles: ["symbol", "name", "addressPerChain"]
}, },
ValueFilter { ValueFilter {
@ -339,7 +339,7 @@ QtObject {
} }
/* Internal function to calculate total balance */ /* Internal function to calculate total balance */
function __getTotalBalance(balances, decimals, symbol) { function __getTotalBalance(balances, decimals) {
let totalBalance = 0 let totalBalance = 0
for(let i=0; i<balances.count; i++) { for(let i=0; i<balances.count; i++) {
let balancePerAddressPerChain = SQUtils.ModelUtils.get(balances, i) let balancePerAddressPerChain = SQUtils.ModelUtils.get(balances, i)

View File

@ -94,7 +94,9 @@ Rectangle {
property alias subTitleBadgeComponent: subTitleBadgeLoader.sourceComponent property alias subTitleBadgeComponent: subTitleBadgeLoader.sourceComponent
property alias errorIcon: errorIcon property alias errorIcon: errorIcon
property alias statusListItemTagsRowLayout: statusListItemSubtitleTagsRow property alias statusListItemTagsRowLayout: statusListItemSubtitleTagsRow
property bool showLoadingIndicator: false property bool showLoadingIndicator: false
property bool tagsScrollBarVisible: true
property int subTitleBadgeLoaderAlignment: Qt.AlignVCenter property int subTitleBadgeLoaderAlignment: Qt.AlignVCenter
@ -391,6 +393,7 @@ Rectangle {
width: Math.min(statusListItemTagsSlotInline.width, statusListItemTagsSlotInline.availableWidth, parent.width) width: Math.min(statusListItemTagsSlotInline.width, statusListItemTagsSlotInline.availableWidth, parent.width)
height: visible ? contentHeight : 0 height: visible ? contentHeight : 0
padding: 0 padding: 0
ScrollBar.horizontal.policy: root.tagsScrollBarVisible ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
Row { Row {
id: statusListItemTagsSlotInline id: statusListItemTagsSlotInline
@ -399,7 +402,7 @@ Rectangle {
Repeater { Repeater {
id: tagsRepeater id: tagsRepeater
delegate: tagsDelegate delegate: root.tagsDelegate
} }
} }
} }

View File

@ -81,7 +81,6 @@ Control {
icon: "close-circle" icon: "close-circle"
visible: closeButtonVisible visible: closeButtonVisible
MouseArea { MouseArea {
id: mouseArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor

View File

@ -21,14 +21,16 @@ StatusInput {
} }
] ]
onKeyPressed: (event) => { onKeyPressed:
(event) => {
// additionally accept dot (.) and convert it to the correct decimal point char // additionally accept dot (.) and convert it to the correct decimal point char
if (event.key === Qt.Key_Period || event.key === Qt.Key_Comma) { if (event.key === Qt.Key_Period || event.key === Qt.Key_Comma) {
// Only one decimal point is allowed // Only one decimal point is allowed
if(root.text.indexOf(root.locale.decimalPoint) === -1) if(root.text.indexOf(root.locale.decimalPoint) === -1) {
root.input.insert(root.input.cursorPosition, root.locale.decimalPoint) root.input.insert(root.input.cursorPosition, root.locale.decimalPoint)
event.accepted = true event.accepted = true
} else if ((event.key > Qt.Key_9 && event.key <= Qt.Key_BraceRight) || event.key === Qt.Key_Space || event.key === Qt.Key_Tab) { }
} else if (event.modifiers === Qt.NoModifier && ((event.key > Qt.Key_9 && event.key <= Qt.Key_BraceRight) || event.key === Qt.Key_Space || event.key === Qt.Key_Tab)) {
event.accepted = true event.accepted = true
} }
} }

View File

@ -400,7 +400,7 @@ Item {
KeyNavigation.tab: root.tabNavItem KeyNavigation.tab: root.tabNavItem
Keys.onPressed: { Keys.onPressed: {
edit.keyEvent = event.key edit.keyEvent = event.key
root.keyPressed(event); root.keyPressed(event)
} }
onCursorRectangleChanged: Utils.ensureVisible(flick, cursorRectangle) onCursorRectangleChanged: Utils.ensureVisible(flick, cursorRectangle)
onActiveFocusChanged: if (root.pristine) root.pristine = false onActiveFocusChanged: if (root.pristine) root.pristine = false

View File

@ -72,6 +72,7 @@ QtObject {
function stripTrailingZeroes(numStr, locale) { function stripTrailingZeroes(numStr, locale) {
locale = locale || Qt.locale()
let regEx = locale.decimalPoint == "." ? /(\.[0-9]*[1-9])0+$|\.0*$/ : /(\,[0-9]*[1-9])0+$|\,0*$/ let regEx = locale.decimalPoint == "." ? /(\.[0-9]*[1-9])0+$|\.0*$/ : /(\,[0-9]*[1-9])0+$|\,0*$/
return numStr.replace(regEx, '$1') return numStr.replace(regEx, '$1')
} }
@ -157,10 +158,10 @@ QtObject {
var optDisplayDecimals = currencyAmount.displayDecimals var optDisplayDecimals = currencyAmount.displayDecimals
var optStripTrailingZeroes = currencyAmount.stripTrailingZeroes var optStripTrailingZeroes = currencyAmount.stripTrailingZeroes
if (options) { if (options) {
if (options.noSymbol !== undefined) { if (options.noSymbol !== undefined && options.noSymbol === true) {
optNoSymbol = true optNoSymbol = true
} }
if (options.rawAmount !== undefined) { if (options.rawAmount !== undefined && options.rawAmount === true) {
optRawAmount = true optRawAmount = true
} }
if (options.minDecimals !== undefined && options.minDecimals > optDisplayDecimals) { if (options.minDecimals !== undefined && options.minDecimals > optDisplayDecimals) {
@ -175,8 +176,7 @@ QtObject {
var amountSuffix = "" var amountSuffix = ""
let minAmount = 10**-optDisplayDecimals let minAmount = 10**-optDisplayDecimals
if (currencyValue > 0 && currencyValue < minAmount && !optRawAmount) if (currencyValue > 0 && currencyValue < minAmount && !optRawAmount) {
{
// Handle amounts smaller than resolution // Handle amounts smaller than resolution
amountStr = "<%1".arg(numberToLocaleString(minAmount, optDisplayDecimals, locale)) amountStr = "<%1".arg(numberToLocaleString(minAmount, optDisplayDecimals, locale))
} else { } else {

View File

@ -2,7 +2,6 @@ import QtQuick 2.15
import QtQuick.Controls 2.15 import QtQuick.Controls 2.15
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
StatusButton { StatusButton {
@ -12,6 +11,7 @@ StatusButton {
icon.name: hovered ? "arrow-up" : "arrow-down" icon.name: hovered ? "arrow-up" : "arrow-down"
icon.color: Theme.palette.baseColor1 icon.color: Theme.palette.baseColor1
focusPolicy: Qt.NoFocus
isRoundIcon: true isRoundIcon: true
radius: height/2 radius: height/2
normalColor: Theme.palette.indirectColor3 normalColor: Theme.palette.indirectColor3

View File

@ -0,0 +1,277 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Shapes 1.15
import StatusQ 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Core.Theme 0.1
import shared.popups.send.views 1.0
import shared.popups.send.panels 1.0
import utils 1.0
import shared.stores 1.0
import SortFilterProxyModel 0.2
Control {
id: root
// input API
required property CurrenciesStore currencyStore
required property var flatNetworksModel
required property var processedAssetsModel
property string tokenKey
onTokenKeyChanged: {
if (!!tokenKey)
Qt.callLater(d.setSelectedHoldingId, tokenKey, Constants.TokenType.ERC20)
}
property string tokenAmount
onTokenAmountChanged: {
if (!!tokenAmount)
Qt.callLater(() => amountToSendInput.input.text = Number(tokenAmount).toLocaleString(Qt.locale(), 'f', -128))
}
property int swapSide: SwapInputPanel.SwapSide.Pay
property bool fiatInputInteractive
property bool loading
// output API
readonly property string selectedHoldingId: d.selectedHoldingId
readonly property double cryptoValue: amountToSendInput.cryptoValueToSendFloat
readonly property string cryptoValueRaw: amountToSendInput.cryptoValueToSend
readonly property bool cryptoValueValid: amountToSendInput.inputNumberValid
// visual properties
property int swapExchangeButtonWidth: 44
property string caption: swapSide === SwapInputPanel.SwapSide.Pay ? qsTr("Pay") : qsTr("Receive")
enum SwapSide {
Pay = 0,
Receive = 1
}
padding: Style.current.padding
// by design
implicitWidth: 492
implicitHeight: 131
Component.onCompleted: {
if (root.swapSide === SwapInputPanel.SwapSide.Pay)
amountToSendInput.input.forceActiveFocus()
}
QtObject {
id: d
function setSelectedHoldingId(holdingId, holdingType) {
let holding = SQUtils.ModelUtils.getByKey(root.processedAssetsModel, "symbol", holdingId)
d.selectedHoldingId = holdingId
d.setSelectedHolding(holding, holdingType)
}
function setSelectedHolding(holding, holdingType) {
d.selectedHoldingType = holdingType
d.selectedHolding = holding
holdingSelector.setSelectedItem(holding, holdingType)
}
property var selectedHolding: null
property var selectedHoldingType: Constants.TokenType.Unknown
property string selectedHoldingId
readonly property bool isSelectedHoldingValidAsset: !!selectedHolding && selectedHoldingType === Constants.TokenType.ERC20
readonly property double maxFiatBalance: isSelectedHoldingValidAsset ? selectedHolding.currentCurrencyBalance : 0
readonly property double maxCryptoBalance: isSelectedHoldingValidAsset ? selectedHolding.currentBalance : 0
readonly property double maxInputBalance: amountToSendInput.inputIsFiat ? maxFiatBalance : maxCryptoBalance
readonly property string inputSymbol: amountToSendInput.inputIsFiat ? root.currencyStore.currentCurrency :
!!d.selectedHolding && !!d.selectedHolding.symbol ? d.selectedHolding.symbol: ""
readonly property string maxInputBalanceFormatted:
root.currencyStore.formatCurrencyAmount(Math.trunc(prepareForMaxSend(d.maxInputBalance, d.inputSymbol)*100)/100, d.inputSymbol, {noSymbol: !amountToSendInput.inputIsFiat})
function prepareForMaxSend(value, symbol) {
if (symbol !== Constants.ethToken) {
return value
}
return value - Math.max(0.0001, Math.min(0.01, value * 0.1))
}
property string searchText
}
background: Shape {
id: shape
property int radius: 16
property int leftTopRadius: radius
property int rightTopRadius: radius
property int leftBottomRadius: radius
property int rightBottomRadius: radius
readonly property int cutoutGap: 4
scale: swapSide === SwapInputPanel.SwapSide.Pay ? -1 : 1
ShapePath {
id: path
fillColor: Theme.palette.indirectColor3
strokeColor: amountToSendInput.input.input.edit.activeFocus ? Theme.palette.directColor7 : Theme.palette.directColor8
strokeWidth: 1
capStyle: ShapePath.RoundCap
startX: shape.leftTopRadius
startY: 0
PathLine {
x: shape.width/2 - root.swapExchangeButtonWidth/2 - (shape.cutoutGap/2 + path.strokeWidth)
y: 0
}
PathArc { // the cutout
relativeX: root.swapExchangeButtonWidth + (shape.cutoutGap + path.strokeWidth*2)
direction: PathArc.Counterclockwise
radiusX: root.swapExchangeButtonWidth/2 + path.strokeWidth
radiusY: root.swapExchangeButtonWidth/2 - path.strokeWidth/2
}
PathLine {
x: shape.width - shape.rightTopRadius
y: 0
}
PathArc {
x: shape.width
y: shape.rightTopRadius
radiusX: shape.rightTopRadius
radiusY: shape.rightTopRadius
}
PathLine {
x: shape.width
y: shape.height - shape.rightBottomRadius
}
PathArc {
x: shape.width - shape.rightBottomRadius
y: shape.height
radiusX: shape.rightBottomRadius
radiusY: shape.rightBottomRadius
}
PathLine {
x: shape.leftBottomRadius
y: shape.height
}
PathArc {
x: 0
y: shape.height - shape.leftBottomRadius
radiusX: shape.leftBottomRadius
radiusY: shape.leftBottomRadius
}
PathLine {
x: 0
y: shape.leftTopRadius
}
PathArc {
x: shape.leftTopRadius
y: 0
radiusX: shape.leftTopRadius
radiusY: shape.leftTopRadius
}
}
}
contentItem: RowLayout {
spacing: 20
ColumnLayout {
Layout.preferredWidth: parent.width*.66
Layout.fillHeight: true
AmountToSend {
Layout.fillWidth: true
id: amountToSendInput
objectName: "amountToSendInput"
caption: root.caption
interactive: true
selectedHolding: d.selectedHolding
fiatInputInteractive: root.fiatInputInteractive
multiplierIndex: d.isSelectedHoldingValidAsset && !!holdingSelector.selectedItem && !!holdingSelector.selectedItem.decimals
? holdingSelector.selectedItem.decimals
: 0
maxInputBalance: (root.swapSide === SwapInputPanel.SwapSide.Receive || !d.isSelectedHoldingValidAsset) ? Number.POSITIVE_INFINITY
: d.prepareForMaxSend(d.maxInputBalance, d.inputSymbol)
currentCurrency: root.currencyStore.currentCurrency
formatCurrencyAmount: root.currencyStore.formatCurrencyAmount
loading: root.loading
}
}
ColumnLayout {
Layout.preferredWidth: parent.width*.33
Item { Layout.fillHeight: true }
HoldingSelector {
id: holdingSelector
objectName: "holdingSelector"
Layout.rightMargin: d.isSelectedHoldingValidAsset ? -root.padding : 0
Layout.alignment: Qt.AlignRight
Layout.preferredHeight: 38
searchPlaceholderText: qsTr("Search asset name or symbol")
assetsModel: SortFilterProxyModel {
sourceModel: root.processedAssetsModel
filters: FastExpressionFilter {
function search(symbol, name, searchString) {
return (symbol.toUpperCase().includes(searchString.toUpperCase())
|| name.toUpperCase().includes(searchString.toUpperCase()))
}
expression: search(model.symbol, model.name, d.searchText)
expectedRoles: ["symbol", "name"]
}
}
networksModel: root.flatNetworksModel
formatCurrentCurrencyAmount: function(balance) {
return root.currencyStore.formatCurrencyAmount(balance, root.currencyStore.currentCurrency)
}
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals) {
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
}
onItemSelected: {
d.setSelectedHoldingId(holdingId, holdingType)
amountToSendInput.input.forceActiveFocus()
}
onSearchTextChanged: d.searchText = searchText
}
Item { Layout.fillHeight: !itemTag.visible }
StatusListItemTag {
id: itemTag
objectName: "maxTagButton"
Layout.alignment: Qt.AlignRight
Layout.maximumWidth: parent.width
Layout.preferredHeight: 22
visible: d.isSelectedHoldingValidAsset && root.swapSide === SwapInputPanel.SwapSide.Pay
title: d.maxInputBalance > 0 ? qsTr("Max: %1").arg(d.maxInputBalanceFormatted)
: qsTr("No balances active")
tagClickable: true
closeButtonVisible: false
titleText.font.pixelSize: 12
bgColor: amountToSendInput.input.valid || !amountToSendInput.input.text ? Theme.palette.primaryColor3 : Theme.palette.dangerColor2
titleText.color: amountToSendInput.input.valid || !amountToSendInput.input.text ? Theme.palette.primaryColor1 : Theme.palette.dangerColor1
onTagClicked: {
const max = d.prepareForMaxSend(d.maxInputBalance, d.inputSymbol)
if (max > 0)
amountToSendInput.input.text = max.toLocaleString(Qt.locale(), 'f', -128)
else
amountToSendInput.input.input.edit.clear()
amountToSendInput.input.forceActiveFocus()
}
}
}
}
}

View File

@ -6,3 +6,4 @@ ManageAssetsPanel 1.0 ManageAssetsPanel.qml
ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.qml ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.qml
ManageHiddenPanel 1.0 ManageHiddenPanel.qml ManageHiddenPanel 1.0 ManageHiddenPanel.qml
DAppsWorkflow 1.0 DAppsWorkflow.qml DAppsWorkflow 1.0 DAppsWorkflow.qml
SwapInputPanel 1.0 SwapInputPanel.qml

View File

@ -1,6 +1,4 @@
import QtQuick 2.13 import QtQml 2.15
import utils 1.0
/* This is used so that there is an easy way to fill in the data /* This is used so that there is an easy way to fill in the data
needed to launch the Swap Modal with pre-filled requisites. */ needed to launch the Swap Modal with pre-filled requisites. */
@ -11,5 +9,5 @@ QtObject {
property string fromTokensKey: "" property string fromTokensKey: ""
property string fromTokenAmount: "" property string fromTokenAmount: ""
property string toTokenKey: "" property string toTokenKey: ""
property string toTokenAmount
} }

View File

@ -1,4 +1,4 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
import utils 1.0 import utils 1.0

View File

@ -55,8 +55,12 @@ QObject {
return networkString return networkString
} }
function formatCurrencyAmount(balance, symbol) { function formatCurrencyAmount(balance, symbol, options = null, locale = null) {
return root.currencyStore.formatCurrencyAmount(balance, symbol) return root.currencyStore.formatCurrencyAmount(balance, symbol, options, locale)
}
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
return root.currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
} }
// TODO: remove once the AccountsModalHeader is reworked!! // TODO: remove once the AccountsModalHeader is reworked!!
@ -67,9 +71,80 @@ QObject {
return null return null
} }
// Model prepared to provide filtered and sorted assets as per the advanced Settings in token management
readonly property var processedAssetsModel: SortFilterProxyModel {
property real displayAssetsBelowBalanceThresholdAmount: root.walletAssetsStore.walletTokensStore.getDisplayAssetsBelowBalanceThresholdDisplayAmount()
sourceModel: __assetsWithFilteredBalances
proxyRoles: [
FastExpressionRole {
name: "isCommunityAsset"
expression: !!model.communityId
expectedRoles: ["communityId"]
},
FastExpressionRole {
name: "currentBalance"
expression: __getTotalBalance(model.balances, model.decimals)
expectedRoles: ["balances", "decimals"]
},
FastExpressionRole {
name: "currentCurrencyBalance"
expression: {
if (!!model.marketDetails) {
return model.currentBalance * model.marketDetails.currencyPrice.amount
}
return 0
}
expectedRoles: ["marketDetails", "currentBalance"]
}
]
filters: [
FastExpressionFilter {
expression: {
root.walletAssetsStore.assetsController.revision
if (!root.walletAssetsStore.assetsController.filterAcceptsSymbol(model.symbol)) // explicitely hidden
return false
if (model.isCommunityAsset) // do not show community assets
return false
if (root.walletAssetsStore.walletTokensStore.displayAssetsBelowBalance)
return model.currentCurrencyBalance > processedAssetsModel.displayAssetsBelowBalanceThresholdAmount
return true
}
expectedRoles: ["symbol", "isCommunityAsset", "currentCurrencyBalance"]
}
]
// FIXME sort by assetsController instead, to have the sorting/order as in the main wallet view
// sorters: RoleSorter {
// roleName: "isCommunityAsset"
// }
}
// Internal properties and functions ----------------------------------------------------------------------------------------------------------------------------- // Internal properties and functions -----------------------------------------------------------------------------------------------------------------------------
readonly property var __fromToken: ModelUtils.getByKey(root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel, "key", root.swapFormData.fromTokensKey) readonly property var __fromToken: ModelUtils.getByKey(root.walletAssetsStore.walletTokensStore.plainTokensBySymbolModel, "key", root.swapFormData.fromTokensKey)
// Internal model filtering balances by the account selected in the AccountsModalHeader
SubmodelProxyModel {
id: __assetsWithFilteredBalances
sourceModel: root.walletAssetsStore.groupedAccountAssetsModel
submodelRoleName: "balances"
delegateModel: SortFilterProxyModel {
sourceModel: submodel
filters: [
ValueFilter {
roleName: "chainId"
value: root.swapFormData.selectedNetworkChainId
enabled: root.swapFormData.selectedNetworkChainId !== -1
}/*,
// TODO enable once AccountsModalHeader is reworked!!
ValueFilter {
roleName: "account"
value: root.selectedSenderAccount.address
}*/
]
}
}
SubmodelProxyModel { SubmodelProxyModel {
id: filteredBalancesModel id: filteredBalancesModel
sourceModel: root.walletAssetsStore.baseGroupedAccountAssetModel sourceModel: root.walletAssetsStore.baseGroupedAccountAssetModel
@ -104,4 +179,14 @@ QObject {
} }
return null return null
} }
/* Internal function to calculate total balance */
function __getTotalBalance(balances, decimals) {
let totalBalance = 0
for(let i=0; i<balances.count; i++) {
let balancePerAddressPerChain = ModelUtils.get(balances, i)
totalBalance+=AmountsArithmetic.toNumber(balancePerAddressPerChain.balance, decimals)
}
return totalBalance
}
} }

View File

@ -18,12 +18,13 @@ QtObject {
ex. uniswap list, status tokens list */ ex. uniswap list, status tokens list */
readonly property var sourcesOfTokensModel: SortFilterProxyModel { readonly property var sourcesOfTokensModel: SortFilterProxyModel {
sourceModel: !!root._allTokensModule ? root._allTokensModule.sourcesOfTokensModel : null sourceModel: !!root._allTokensModule ? root._allTokensModule.sourcesOfTokensModel : null
proxyRoles: ExpressionRole { proxyRoles: FastExpressionRole {
function sourceImage(sourceKey) { function sourceImage(sourceKey) {
return Constants.getSupportedTokenSourceImage(sourceKey) return Constants.getSupportedTokenSourceImage(sourceKey)
} }
name: "image" name: "image"
expression: sourceImage(model.key) expression: sourceImage(model.key)
expectedRoles: ["key"]
} }
filters: AnyOf { filters: AnyOf {
ValueFilter { ValueFilter {
@ -56,16 +57,18 @@ QtObject {
sourceModel: root._joinFlatTokensModel sourceModel: root._joinFlatTokensModel
proxyRoles: [ proxyRoles: [
ExpressionRole { FastExpressionRole {
name: "explorerUrl" name: "explorerUrl"
expression: model.blockExplorerURL + "/token/" + model.address expression: model.blockExplorerURL + "/token/" + model.address
expectedRoles: ["blockExplorerURL", "address"]
}, },
ExpressionRole { FastExpressionRole {
function tokenIcon(symbol) { function tokenIcon(symbol) {
return Constants.tokenIcon(symbol) return Constants.tokenIcon(symbol)
} }
name: "image" name: "image"
expression: tokenIcon(model.symbol) expression: tokenIcon(model.symbol)
expectedRoles: ["symbol"]
} }
] ]
} }
@ -79,24 +82,27 @@ QtObject {
readonly property var assetsBySymbolModel: SortFilterProxyModel { readonly property var assetsBySymbolModel: SortFilterProxyModel {
sourceModel: plainTokensBySymbolModel sourceModel: plainTokensBySymbolModel
proxyRoles: [ proxyRoles: [
ExpressionRole { FastExpressionRole {
function tokenIcon(symbol) { function tokenIcon(symbol) {
return Constants.tokenIcon(symbol) return Constants.tokenIcon(symbol)
} }
name: "iconSource" name: "iconSource"
expression: tokenIcon(model.symbol) expression: tokenIcon(model.symbol)
expectedRoles: ["symbol"]
}, },
// TODO: Review if it can be removed // TODO: Review if it can be removed
ExpressionRole { FastExpressionRole {
name: "shortName" name: "shortName"
expression: model.symbol expression: model.symbol
expectedRoles: ["symbol"]
}, },
ExpressionRole { FastExpressionRole {
function getCategory(index) { function getCategory(index) {
return 0 return 0
} }
name: "category" name: "category"
expression: getCategory(model.communityId) expression: getCategory(model.communityId)
expectedRoles: ["communityId"]
} }
] ]
} }

View File

@ -77,6 +77,7 @@ Item {
readonly property TransactionStore transactionStore: TransactionStore { readonly property TransactionStore transactionStore: TransactionStore {
walletAssetStore: appMain.walletAssetsStore walletAssetStore: appMain.walletAssetsStore
tokensStore: appMain.tokensStore tokensStore: appMain.tokensStore
currencyStore: appMain.currencyStore
} }
// set from main.qml // set from main.qml

View File

@ -175,7 +175,7 @@ StatusDialog {
} }
if(!!popup.preDefinedAmountToSend) { if(!!popup.preDefinedAmountToSend) {
amountToSendInput.input.text = popup.preDefinedAmountToSend amountToSendInput.input.text = Number(popup.preDefinedAmountToSend).toLocaleString(Qt.locale(), 'f', -128)
} }
if(!!popup.preSelectedRecipient) { if(!!popup.preSelectedRecipient) {
@ -261,11 +261,9 @@ StatusDialog {
id: holdingSelector id: holdingSelector
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
selectedSenderAccount: store.selectedSenderAccount.address
assetsModel: popup.store.processedAssetsModel assetsModel: popup.store.processedAssetsModel
collectiblesModel: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null collectiblesModel: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null
networksModel: popup.store.flatNetworksModel networksModel: popup.store.flatNetworksModel
currentCurrencySymbol: d.currencyStore.currentCurrencySymbol
visible: (!!d.selectedHolding && d.selectedHoldingType !== Constants.TokenType.Unknown) || visible: (!!d.selectedHolding && d.selectedHoldingType !== Constants.TokenType.Unknown) ||
(!!d.hoveredHolding && d.hoveredHoldingType !== Constants.TokenType.Unknown) (!!d.hoveredHolding && d.hoveredHoldingType !== Constants.TokenType.Unknown)
onItemSelected: { onItemSelected: {
@ -291,7 +289,8 @@ StatusDialog {
const max = d.prepareForMaxSend(input, d.hoveredHolding.symbol) const max = d.prepareForMaxSend(input, d.hoveredHolding.symbol)
if (max <= 0) if (max <= 0)
return qsTr("No balances active") return qsTr("No balances active")
const balance = d.currencyStore.formatCurrencyAmount(max , d.hoveredHolding.symbol) const balance = d.currencyStore.formatCurrencyAmount(max, amountToSendInput.inputIsFiat ? amountToSendInput.currentCurrency
: d.selectedHolding.symbol)
return qsTr("Max: %1").arg(balance.toString()) return qsTr("Max: %1").arg(balance.toString())
} }
const max = d.prepareForMaxSend(d.maxInputBalance, d.inputSymbol) const max = d.prepareForMaxSend(d.maxInputBalance, d.inputSymbol)
@ -394,7 +393,6 @@ StatusDialog {
Layout.bottomMargin: Style.current.xlPadding Layout.bottomMargin: Style.current.xlPadding
visible: !d.selectedHolding visible: !d.selectedHolding
selectedSenderAccount: store.selectedSenderAccount.address
assets: popup.store.processedAssetsModel assets: popup.store.processedAssetsModel
collectibles: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null collectibles: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null
networksModel: popup.store.flatNetworksModel networksModel: popup.store.flatNetworksModel
@ -528,4 +526,3 @@ StatusDialog {
} }
} }
} }

View File

@ -16,7 +16,6 @@ StatusAmountInput {
bottomPadding: 0 bottomPadding: 0
placeholderText: "" placeholderText: ""
input.edit.cursorVisible: true
input.edit.font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text) input.edit.font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text)
input.placeholderFont.pixelSize: 34 input.placeholderFont.pixelSize: 34
input.edit.padding: 0 input.edit.padding: 0

View File

@ -19,15 +19,10 @@ StatusListItem {
property var formatCurrentCurrencyAmount: function(balance){} property var formatCurrentCurrencyAmount: function(balance){}
property var formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){} property var formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){}
property var balancesModel property var balancesModel
property string selectedSenderAccount
QtObject { QtObject {
id: d id: d
readonly property int indexesThatCanBeShown:
Math.floor((root.statusListItemInlineTagsSlot.availableWidth
- compactRow.width) / statusListItemInlineTagsSlot.children[0].width) - 1
function selectToken() { function selectToken() {
root.tokenSelected({name, symbol, balances, decimals}) root.tokenSelected({name, symbol, balances, decimals})
} }
@ -62,30 +57,13 @@ StatusListItem {
statusListItemInlineTagsSlot.spacing: 0 statusListItemInlineTagsSlot.spacing: 0
tagsModel: root.balancesModel tagsModel: root.balancesModel
tagsDelegate: expandedItem tagsDelegate: expandedItem
statusListItemInlineTagsSlot.children: Row { tagsScrollBarVisible: false
id: compactRow
spacing: -6
Repeater {
model: root.balancesModel
delegate: compactItem
}
}
radius: sensor.containsMouse || root.highlighted ? 0 : 8 radius: sensor.containsMouse || highlighted ? 0 : 8
color: sensor.containsMouse || highlighted ? Theme.palette.baseColor2 : "transparent" color: sensor.containsMouse || highlighted ? Theme.palette.statusListItem.highlightColor : "transparent"
onClicked: d.selectToken() onClicked: d.selectToken()
Component {
id: compactItem
StatusRoundedImage {
z: index + 1
width: 16
height: 16
image.source: Style.svg("tiny/%1".arg(model.iconUrl))
visible: !root.sensor.containsMouse || index > d.indexesThatCanBeShown
}
}
Component { Component {
id: expandedItem id: expandedItem
StatusListItemTag { StatusListItemTag {
@ -98,8 +76,9 @@ StatusListItem {
asset.width: 16 asset.width: 16
asset.height: 16 asset.height: 16
asset.isImage: true asset.isImage: true
asset.name: Style.svg("tiny/%1".arg(iconUrl)) asset.name: Style.svg("tiny/%1".arg(model.iconUrl))
visible: root.sensor.containsMouse && index <= d.indexesThatCanBeShown tagClickable: true
onTagClicked: d.selectToken()
} }
} }
} }

View File

@ -1,17 +1,12 @@
import QtQuick 2.13 import QtQuick 2.15
import QtQuick.Controls 2.13 import QtQuick.Controls 2.15
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.15
import SortFilterProxyModel 0.2
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Components 0.1 import StatusQ.Components 0.1
import StatusQ.Core.Backpressure 1.0
import shared.controls 1.0
import utils 1.0 import utils 1.0
Item { Item {
@ -40,12 +35,6 @@ Item {
property int contentIconSize: 21 property int contentIconSize: 21
property int contentTextSize: 28 property int contentTextSize: 28
function resetInternal() {
items = null
selectedItem = null
hoveredItem = null
}
function openPopup() { function openPopup() {
root.comboBoxControl.popup.open() root.comboBoxControl.popup.open()
} }
@ -68,9 +57,8 @@ Item {
property string iconSource: "" property string iconSource: ""
onIconSourceChanged: tokenIcon.image.source = iconSource onIconSourceChanged: tokenIcon.image.source = iconSource
property string text: "" property string text: qsTr("Select asset")
readonly property bool isItemSelected: !!root.selectedItem || !!root.hoveredItem readonly property bool isItemSelected: !!root.selectedItem || !!root.hoveredItem
} }
StatusComboBox { StatusComboBox {
@ -80,9 +68,7 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: Math.min(implicitWidth, parent.width) control.padding: 12
control.padding: 4
control.popup.width: 492 control.popup.width: 492
control.popup.x: -root.x control.popup.x: -root.x
control.popup.verticalPadding: 0 control.popup.verticalPadding: 0
@ -92,20 +78,21 @@ Item {
model: root.comboBoxModel model: root.comboBoxModel
control.background: Rectangle { control.background: Rectangle {
color: "transparent" color: !d.isItemSelected ? Theme.palette.primaryColor3 : "transparent"
border.width: d.isItemSelected ? 0 : 1 border.width: d.isItemSelected ? 0 : 1
border.color: Theme.palette.directColor7 border.color: Theme.palette.directColor7
radius: 12 radius: 8
HoverHandler {
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
}
} }
contentItem: RowLayout { contentItem: RowLayout {
id: rowLayout
implicitHeight: 38
StatusRoundedImage { StatusRoundedImage {
id: tokenIcon id: tokenIcon
Layout.preferredWidth: root.contentIconSize Layout.preferredWidth: root.contentIconSize
Layout.preferredHeight: root.contentIconSize Layout.preferredHeight: root.contentIconSize
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
visible: !!d.iconSource visible: !!d.iconSource
image.source: d.iconSource image.source: d.iconSource
image.onStatusChanged: { image.onStatusChanged: {
@ -116,22 +103,17 @@ Item {
} }
StatusBaseText { StatusBaseText {
Layout.fillWidth: true Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
font.pixelSize: root.contentTextSize font.pixelSize: root.contentTextSize
elide: Text.ElideRight elide: Text.ElideRight
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
color: Theme.palette.miscColor1 color: Theme.palette.primaryColor1
text: d.text text: d.text
visible: d.isItemSelected
} }
StatusIcon { StatusIcon {
Layout.leftMargin: -3
Layout.alignment: Qt.AlignVCenter
Layout.preferredWidth: 16 Layout.preferredWidth: 16
Layout.preferredHeight: 16 Layout.preferredHeight: 16
icon: "chevron-down" icon: "chevron-down"
color: Theme.palette.miscColor1 color: Theme.palette.primaryColor1
visible: !!root.selectedItem
} }
} }

View File

@ -1,4 +1,3 @@
import QtQml 2.15 import QtQml 2.15
import QtQuick 2.15 import QtQuick 2.15
import QtQuick.Layouts 1.15 import QtQuick.Layouts 1.15
@ -22,10 +21,8 @@ Item {
id: root id: root
property var assetsModel property var assetsModel
property string selectedSenderAccount
property var collectiblesModel property var collectiblesModel
property var networksModel property var networksModel
property string currentCurrencySymbol
property bool onlyAssets: true property bool onlyAssets: true
property string searchText property string searchText
@ -41,6 +38,16 @@ Item {
property alias selectedItem: holdingItemSelector.selectedItem property alias selectedItem: holdingItemSelector.selectedItem
property alias hoveredItem: holdingItemSelector.hoveredItem property alias hoveredItem: holdingItemSelector.hoveredItem
property string searchPlaceholderText: {
if (d.isCurrentBrowsingTypeAsset) {
return qsTr("Search for token or enter token address")
} else if (d.isBrowsingGroup) {
return qsTr("Search %1").arg(d.currentBrowsingGroupName ?? qsTr("collectibles in collection"))
} else {
return qsTr("Search collectibles")
}
}
function setSelectedItem(item, holdingType) { function setSelectedItem(item, holdingType) {
d.browsingHoldingType = holdingType d.browsingHoldingType = holdingType
holdingItemSelector.selectedItem = null holdingItemSelector.selectedItem = null
@ -66,8 +73,8 @@ Item {
[qsTr("Assets")] : [qsTr("Assets")] :
[qsTr("Assets"), qsTr("Collectibles")] [qsTr("Assets"), qsTr("Collectibles")]
readonly property var updateSearchText: Backpressure.debounce(root, 1000, function(inputText) { readonly property var updateSearchText: Backpressure.debounce(root, 500, function(inputText) {
searchText = inputText root.searchText = inputText
}) })
function isAsset(type) { function isAsset(type) {
@ -103,10 +110,8 @@ Item {
} else if (asset.image) { } else if (asset.image) {
// Community assets have a dedicated image streamed from status-go // Community assets have a dedicated image streamed from status-go
return asset.image return asset.image
} else {
return Constants.tokenIcon(asset.symbol)
} }
return "" return Constants.tokenIcon(asset.symbol)
} }
property var collectibleTextFn: function (item) { property var collectibleTextFn: function (item) {
@ -167,16 +172,6 @@ Item {
] ]
} }
readonly property string searchPlaceholderText: {
if (isCurrentBrowsingTypeAsset) {
return qsTr("Search for token or enter token address")
} else if (isBrowsingGroup) {
return qsTr("Search %1").arg(d.currentBrowsingGroupName ?? qsTr("collectibles in collection"))
} else {
return qsTr("Search collectibles")
}
}
// By design values: // By design values:
readonly property int padding: 16 readonly property int padding: 16
readonly property int headerTopMargin: 5 readonly property int headerTopMargin: 5
@ -187,7 +182,6 @@ Item {
readonly property int collectibleContentIconSize: 28 readonly property int collectibleContentIconSize: 28
readonly property int assetContentTextSize: 28 readonly property int assetContentTextSize: 28
readonly property int collectibleContentTextSize: 15 readonly property int collectibleContentTextSize: 15
} }
HoldingItemSelector { HoldingItemSelector {
@ -246,6 +240,7 @@ Item {
contentIconSize: d.isAsset(d.currentHoldingType) ? d.assetContentIconSize : d.collectibleContentIconSize contentIconSize: d.isAsset(d.currentHoldingType) ? d.assetContentIconSize : d.collectibleContentIconSize
contentTextSize: d.isAsset(d.currentHoldingType) ? d.assetContentTextSize : d.collectibleContentTextSize contentTextSize: d.isAsset(d.currentHoldingType) ? d.assetContentTextSize : d.collectibleContentTextSize
comboBoxListViewSection.property: "isCommunityAsset" comboBoxListViewSection.property: "isCommunityAsset"
// TODO allow for different header/sections for the Swap modal
comboBoxListViewSection.delegate: AssetsSectionDelegate { comboBoxListViewSection.delegate: AssetsSectionDelegate {
height: !!text ? 52 : 0 // if we bind to some property instead of hardcoded value it wont work nice when switching tabs or going inside collection and back height: !!text ? 52 : 0 // if we bind to some property instead of hardcoded value it wont work nice when switching tabs or going inside collection and back
width: ListView.view.width width: ListView.view.width
@ -253,7 +248,10 @@ Item {
text: Helpers.assetsSectionTitle(section, holdingItemSelector.hasCommunityTokens, d.isBrowsingGroup, d.isCurrentBrowsingTypeAsset) text: Helpers.assetsSectionTitle(section, holdingItemSelector.hasCommunityTokens, d.isBrowsingGroup, d.isCurrentBrowsingTypeAsset)
onOpenInfoPopup: Global.openPopup(communityInfoPopupCmp) onOpenInfoPopup: Global.openPopup(communityInfoPopupCmp)
} }
comboBoxControl.popup.onOpened: comboBoxControl.popup.contentItem.headerItem.focusSearch()
comboBoxControl.popup.onClosed: comboBoxControl.popup.contentItem.headerItem.clear() comboBoxControl.popup.onClosed: comboBoxControl.popup.contentItem.headerItem.clear()
comboBoxControl.popup.x: root.width - comboBoxControl.popup.width
} }
Component { Component {
@ -264,6 +262,10 @@ Item {
Component { Component {
id: headerComponent id: headerComponent
ColumnLayout { ColumnLayout {
function focusSearch() {
searchInput.input.forceActiveFocus()
}
function clear() { function clear() {
searchInput.input.edit.clear() searchInput.input.edit.clear()
} }
@ -303,7 +305,7 @@ Item {
CollectibleBackButtonWithInfo { CollectibleBackButtonWithInfo {
Layout.fillWidth: true Layout.fillWidth: true
visible: d.isBrowsingGroup visible: d.isBrowsingGroup
count: collectiblesModel.count count: collectiblesModel ? collectiblesModel.count : 0
name: d.currentBrowsingGroupName name: d.currentBrowsingGroupName
onBackClicked: { onBackClicked: {
if (!d.isCurrentBrowsingTypeAsset) { if (!d.isCurrentBrowsingTypeAsset) {
@ -325,7 +327,7 @@ Item {
anchors.fill: parent anchors.fill: parent
input.showBackground: false input.showBackground: false
placeholderText: d.searchPlaceholderText placeholderText: root.searchPlaceholderText
onTextChanged: Qt.callLater(d.updateSearchText, text) onTextChanged: Qt.callLater(d.updateSearchText, text)
input.clearable: true input.clearable: true
input.implicitHeight: 56 input.implicitHeight: 56
@ -344,7 +346,7 @@ Item {
TokenBalancePerChainDelegate { TokenBalancePerChainDelegate {
objectName: "AssetSelector_ItemDelegate_" + symbol objectName: "AssetSelector_ItemDelegate_" + symbol
width: holdingItemSelector.comboBoxControl.popup.width width: holdingItemSelector.comboBoxControl.popup.width
selectedSenderAccount: root.selectedSenderAccount highlighted: !!holdingItemSelector.selectedItem && symbol === holdingItemSelector.selectedItem.symbol
balancesModel: LeftJoinModel { balancesModel: LeftJoinModel {
leftModel: balances leftModel: balances
rightModel: root.networksModel rightModel: root.networksModel
@ -370,6 +372,7 @@ Item {
CollectibleNestedDelegate { CollectibleNestedDelegate {
objectName: "CollectibleSelector_ItemDelegate_" + groupId objectName: "CollectibleSelector_ItemDelegate_" + groupId
width: holdingItemSelector.comboBoxControl.popup.width width: holdingItemSelector.comboBoxControl.popup.width
highlighted: !!holdingItemSelector.selectedItem && uid === holdingItemSelector.selectedItem.uid
onItemSelected: { onItemSelected: {
if (isGroup) { if (isGroup) {
d.currentBrowsingGroupName = groupName d.currentBrowsingGroupName = groupName

View File

@ -4,6 +4,8 @@ import QtQuick.Layouts 1.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1 import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as SQUtils import StatusQ.Core.Utils 0.1 as SQUtils
import StatusQ.Controls 0.1
import StatusQ.Components 0.1
import StatusQ.Controls.Validators 0.1 import StatusQ.Controls.Validators 0.1
import "../controls" import "../controls"
@ -14,7 +16,7 @@ ColumnLayout {
id: root id: root
readonly property alias input: topAmountToSendInput readonly property alias input: topAmountToSendInput
readonly property bool inputNumberValid: !!input.text && !isNaN(d.parsedInput) readonly property bool inputNumberValid: !!input.text && !isNaN(d.parsedInput) && input.valid
readonly property int minSendCryptoDecimals: readonly property int minSendCryptoDecimals:
!inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0 !inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0
@ -36,6 +38,10 @@ ColumnLayout {
property bool interactive: false property bool interactive: false
property bool inputIsFiat: false property bool inputIsFiat: false
property string caption: isBridgeTx ? qsTr("Amount to bridge") : qsTr("Amount to send")
property bool fiatInputInteractive: true
// Crypto value to send expressed in base units (like wei for ETH), // Crypto value to send expressed in base units (like wei for ETH),
// as a string representing integer decimal // as a string representing integer decimal
readonly property alias cryptoValueToSend: d.cryptoValueRawToSend readonly property alias cryptoValueToSend: d.cryptoValueRawToSend
@ -45,6 +51,8 @@ ColumnLayout {
property var formatCurrencyAmount: property var formatCurrencyAmount:
(amount, symbol, options = null, locale = null) => {} (amount, symbol, options = null, locale = null) => {}
property bool loading
signal reCalculateSuggestedRoute() signal reCalculateSuggestedRoute()
QtObject { QtObject {
@ -88,11 +96,11 @@ ColumnLayout {
} }
readonly property string zeroString: readonly property string zeroString:
LocaleUtils.numberToLocaleString(0, 2, LocaleUtils.userInputLocale) LocaleUtils.numberToLocaleString(0, 2, topAmountToSendInput.locale)
readonly property double parsedInput: readonly property double parsedInput:
LocaleUtils.numberFromLocaleString(topAmountToSendInput.text, LocaleUtils.numberFromLocaleString(topAmountToSendInput.text,
LocaleUtils.userInputLocale) topAmountToSendInput.locale)
readonly property double inputNumber: readonly property double inputNumber:
root.inputNumberValid ? d.parsedInput : 0 root.inputNumberValid ? d.parsedInput : 0
@ -108,10 +116,7 @@ ColumnLayout {
} }
StatusBaseText { StatusBaseText {
Layout.alignment: Qt.AlignLeft | Qt.AlignTop text: root.caption
text: root.isBridgeTx ? qsTr("Amount to bridge")
: qsTr("Amount to send")
font.pixelSize: 13 font.pixelSize: 13
lineHeight: 18 lineHeight: 18
lineHeightMode: Text.FixedHeight lineHeightMode: Text.FixedHeight
@ -119,17 +124,16 @@ ColumnLayout {
} }
RowLayout { RowLayout {
Layout.fillWidth: true
id: topItem id: topItem
property double topAmountToSend: !inputIsFiat ? d.cryptoValueToSend property double topAmountToSend: !inputIsFiat ? d.cryptoValueToSend
: d.fiatValueToSend : d.fiatValueToSend
property string topAmountSymbol: !inputIsFiat ? d.selectedSymbol property string topAmountSymbol: !inputIsFiat ? d.selectedSymbol
: root.currentCurrency : root.currentCurrency
Layout.alignment: Qt.AlignLeft
AmountInputWithCursor { AmountInputWithCursor {
id: topAmountToSendInput id: topAmountToSendInput
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft Layout.fillWidth: true
Layout.maximumWidth: 250 Layout.maximumWidth: 250
Layout.preferredWidth: !!text ? input.edit.paintedWidth + 2 Layout.preferredWidth: !!text ? input.edit.paintedWidth + 2
: textMetrics.advanceWidth : textMetrics.advanceWidth
@ -138,23 +142,21 @@ ColumnLayout {
: Theme.palette.dangerColor1 : Theme.palette.dangerColor1
input.edit.readOnly: !root.interactive input.edit.readOnly: !root.interactive
validationMode: StatusInput.ValidationMode.Always
validators: [ validators: [
StatusFloatValidator {
id: floatValidator
bottom: 0
errorMessage: ""
locale: topAmountToSendInput.locale
},
StatusValidator { StatusValidator {
errorMessage: "" errorMessage: ""
validate: (text) => { validate: (text) => {
const num = parseFloat(text) var num = 0
try {
num = Number.fromLocaleString(topAmountToSendInput.locale, text)
} catch (e) {
console.warn(e, "(Error parsing number from text: %1)".arg(text))
return false
}
if (isNaN(num)) return num > 0 && num <= root.maxInputBalance
return true
return num <= root.maxInputBalance
} }
} }
] ]
@ -162,7 +164,7 @@ ColumnLayout {
TextMetrics { TextMetrics {
id: textMetrics id: textMetrics
text: topAmountToSendInput.placeholderText text: topAmountToSendInput.placeholderText
font: topAmountToSendInput.input.placeholder.font font: topAmountToSendInput.placeholderFont
} }
Keys.onReleased: { Keys.onReleased: {
@ -172,33 +174,35 @@ ColumnLayout {
if (!isNaN(amount)) if (!isNaN(amount))
d.waitTimer.restart() d.waitTimer.restart()
} }
visible: !root.loading
}
LoadingComponent {
objectName: "topAmountToSendInputLoadingComponent"
Layout.preferredWidth: topAmountToSendInput.width
Layout.preferredHeight: topAmountToSendInput.height
visible: root.loading
} }
} }
Item {
id: bottomItem
property double bottomAmountToSend: inputIsFiat ? d.cryptoValueToSend
: d.fiatValueToSend
property string bottomAmountSymbol: inputIsFiat ? d.selectedSymbol
: currentCurrency
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
Layout.preferredWidth: txtBottom.width
Layout.preferredHeight: txtBottom.height
StatusBaseText { StatusBaseText {
id: txtBottom Layout.maximumWidth: parent.width
anchors.top: parent.top id: bottomItem
anchors.left: parent.left objectName: "bottomItemText"
text: root.formatCurrencyAmount(bottomItem.bottomAmountToSend,
bottomItem.bottomAmountSymbol) readonly property double bottomAmountToSend: inputIsFiat ? d.cryptoValueToSend
: d.fiatValueToSend
readonly property string bottomAmountSymbol: inputIsFiat ? d.selectedSymbol
: root.currentCurrency
elide: Text.ElideMiddle
text: root.formatCurrencyAmount(bottomAmountToSend, bottomAmountSymbol)
font.pixelSize: 13 font.pixelSize: 13
color: Theme.palette.directColor5 color: Theme.palette.directColor5
}
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
cursorShape: Qt.PointingHandCursor cursorShape: enabled ? Qt.PointingHandCursor : undefined
enabled: root.fiatInputInteractive && !!root.selectedHolding
onClicked: { onClicked: {
topAmountToSendInput.validate() topAmountToSendInput.validate()
@ -207,12 +211,19 @@ ColumnLayout {
bottomItem.bottomAmountToSend, bottomItem.bottomAmountToSend,
bottomItem.bottomAmountSymbol, bottomItem.bottomAmountSymbol,
{ noSymbol: true, rawAmount: true }, { noSymbol: true, rawAmount: true },
LocaleUtils.userInputLocale) topAmountToSendInput.locale)
} }
inputIsFiat = !inputIsFiat root.inputIsFiat = !root.inputIsFiat
d.waitTimer.restart() d.waitTimer.restart()
} }
} }
visible: !root.loading
}
LoadingComponent {
objectName: "bottomItemTextLoadingComponent"
Layout.preferredWidth: bottomItem.width
Layout.preferredHeight: bottomItem.height
visible: root.loading
} }
} }

View File

@ -20,7 +20,6 @@ import "../controls"
Item { Item {
id: root id: root
property string selectedSenderAccount
property var assets: null property var assets: null
property var collectibles: null property var collectibles: null
property var networksModel property var networksModel
@ -221,7 +220,6 @@ Item {
TokenBalancePerChainDelegate { TokenBalancePerChainDelegate {
width: tokenList.width width: tokenList.width
selectedSenderAccount: root.selectedSenderAccount
balancesModel: LeftJoinModel { balancesModel: LeftJoinModel {
leftModel: !!model & !!model.balances ? model.balances : null leftModel: !!model & !!model.balances ? model.balances : null
rightModel: root.networksModel rightModel: root.networksModel
@ -230,7 +228,7 @@ Item {
onTokenSelected: function (selectedToken) { onTokenSelected: function (selectedToken) {
root.tokenSelected(selectedToken.symbol, Constants.TokenType.ERC20) root.tokenSelected(selectedToken.symbol, Constants.TokenType.ERC20)
} }
onTokenHovered: root.tokenHovered(symbol, Constants.TokenType.ERC20, hovered) onTokenHovered: root.tokenHovered(selectedToken.symbol, Constants.TokenType.ERC20, hovered)
formatCurrentCurrencyAmount: function(balance){ formatCurrentCurrencyAmount: function(balance){
return root.formatCurrentCurrencyAmount(balance) return root.formatCurrentCurrencyAmount(balance)
} }
@ -253,13 +251,13 @@ Item {
id: collectiblesDelegate id: collectiblesDelegate
CollectibleNestedDelegate { CollectibleNestedDelegate {
width: tokenList.width width: tokenList.width
onItemHovered: root.tokenHovered(selectedItem.uid, tokenType, hovered) onItemHovered: root.tokenHovered(selectedItem.uid, Constants.TokenType.ERC721, hovered)
onItemSelected: { onItemSelected: {
if (isGroup) { if (isGroup) {
d.currentBrowsingGroupName = groupName d.currentBrowsingGroupName = groupName
root.collectibles.currentGroupId = groupId root.collectibles.currentGroupId = groupId
} else { } else {
root.tokenSelected(selectedItem.uid, tokenType) root.tokenSelected(selectedItem.uid, Constants.TokenType.ERC721)
} }
} }
} }

View File

@ -1,6 +1,7 @@
import QtQuick 2.15 import QtQuick 2.15
import StatusQ.Core 0.1 import StatusQ.Core 0.1
import StatusQ.Core.Utils 0.1 as SQUtils
import utils 1.0 import utils 1.0
import AppLayouts.Profile.stores 1.0 import AppLayouts.Profile.stores 1.0
@ -982,12 +983,18 @@ QtObject {
function formatCurrencyAmount(amount, symbol, options = null, locale = null) { function formatCurrencyAmount(amount, symbol, options = null, locale = null) {
if (isNaN(amount)) { if (isNaN(amount)) {
return "N/A" return qsTr("N/A")
} }
var currencyAmount = getCurrencyAmount(amount, symbol) var currencyAmount = getCurrencyAmount(amount, symbol)
return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale) return LocaleUtils.currencyAmountToLocaleString(currencyAmount, options, locale)
} }
function formatCurrencyAmountFromBigInt(balance, symbol, decimals) {
let bigIntBalance = SQUtils.AmountsArithmetic.fromString(balance)
let decimalBalance = SQUtils.AmountsArithmetic.toNumber(bigIntBalance, decimals)
return formatCurrencyAmount(decimalBalance, symbol)
}
function getFiatValue(cryptoAmount, cryptoSymbol) { function getFiatValue(cryptoAmount, cryptoSymbol) {
var amount = _profileSectionModuleInst.ensUsernamesModule.getFiatValue(cryptoAmount, cryptoSymbol) var amount = _profileSectionModuleInst.ensUsernamesModule.getFiatValue(cryptoAmount, cryptoSymbol)
return parseFloat(amount) return parseFloat(amount)

View File

@ -14,7 +14,7 @@ import AppLayouts.Wallet.stores 1.0
QtObject { QtObject {
id: root id: root
property CurrenciesStore currencyStore: CurrenciesStore {} property CurrenciesStore currencyStore
property WalletAssetsStore walletAssetStore property WalletAssetsStore walletAssetStore
property TokensStore tokensStore property TokensStore tokensStore
@ -276,10 +276,12 @@ QtObject {
submodelRoleName: "balances" submodelRoleName: "balances"
delegateModel: SortFilterProxyModel { delegateModel: SortFilterProxyModel {
sourceModel: submodel sourceModel: submodel
filters: FastExpressionFilter { filters: [
expression: root.selectedSenderAccount.address === model.account ValueFilter {
expectedRoles: ["account"] roleName: "account"
value: root.selectedSenderAccount.address
} }
]
} }
} }
@ -302,7 +304,7 @@ QtObject {
}, },
FastExpressionRole { FastExpressionRole {
name: "currentBalance" name: "currentBalance"
expression: __getTotalBalance(model.balances, model.decimals, root.selectedSenderAccount) expression: __getTotalBalance(model.balances, model.decimals)
expectedRoles: ["balances", "decimals"] expectedRoles: ["balances", "decimals"]
}, },
FastExpressionRole { FastExpressionRole {
@ -324,7 +326,7 @@ QtObject {
name.toUpperCase().startsWith(searchString.toUpperCase()) || __searchAddressInList(addressPerChain, searchString) name.toUpperCase().startsWith(searchString.toUpperCase()) || __searchAddressInList(addressPerChain, searchString)
) )
} }
expression: search(symbol, name, addressPerChain, assetSearchString) expression: search(symbol, name, addressPerChain, root.assetSearchString)
expectedRoles: ["symbol", "name", "addressPerChain"] expectedRoles: ["symbol", "name", "addressPerChain"]
}, },
ValueFilter { ValueFilter {