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:
parent
a7b9a62745
commit
a3c9012f4a
|
@ -38,7 +38,7 @@ SplitView {
|
|||
|
||||
maxInputBalance: inputIsFiat ? root.maxCryptoBalance*amountToSendInput.selectedHolding.marketDetails.currencyPrice.amount
|
||||
: root.maxCryptoBalance
|
||||
currentCurrency: "Fiat"
|
||||
currentCurrency: "USD"
|
||||
formatCurrencyAmount: function(amount, symbol, options, locale) {
|
||||
const currencyAmount = {
|
||||
amount: amount,
|
||||
|
@ -57,15 +57,9 @@ SplitView {
|
|||
LogsAndControlsPanel {
|
||||
id: logsAndControlsPanel
|
||||
|
||||
SplitView.minimumHeight: 100
|
||||
SplitView.minimumHeight: 250
|
||||
|
||||
logsView.logText: logs.logText
|
||||
}
|
||||
}
|
||||
|
||||
Pane {
|
||||
SplitView.minimumWidth: 300
|
||||
SplitView.preferredWidth: 300
|
||||
|
||||
ColumnLayout {
|
||||
Label {
|
||||
|
@ -101,6 +95,7 @@ SplitView {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// category: Components
|
||||
|
|
|
@ -47,7 +47,7 @@ SplitView {
|
|||
|
||||
ColumnLayout {
|
||||
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 {
|
||||
text: "set " + modelData
|
||||
|
|
|
@ -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
|
|
@ -170,7 +170,7 @@ SplitView {
|
|||
StatusInput {
|
||||
id: swapInput
|
||||
Layout.preferredWidth: 100
|
||||
label: "Token mount to swap"
|
||||
label: "Token amount to swap"
|
||||
text: "100"
|
||||
}
|
||||
|
||||
|
|
|
@ -51,9 +51,7 @@ SplitView {
|
|||
return currencyStore.formatCurrencyAmount(balance, "USD")
|
||||
}
|
||||
formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){
|
||||
let bigIntBalance = AmountsArithmetic.fromString(balance)
|
||||
let decimalBalance = AmountsArithmetic.toNumber(bigIntBalance, decimals)
|
||||
return currencyStore.formatCurrencyAmount(decimalBalance, symbol)
|
||||
return currencyStore.formatCurrencyAmountFromBigInt(balance, symbol, decimals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,10 @@ class Setup : public QObject
|
|||
public slots:
|
||||
void qmlEngineAvailable(QQmlEngine *engine) {
|
||||
// custom code that needs QQmlEngine, register QML types, add import paths,...
|
||||
|
||||
QGuiApplication::setOrganizationName(QStringLiteral("Status"));
|
||||
QGuiApplication::setOrganizationDomain(QStringLiteral("status.im"));
|
||||
|
||||
const QStringList additionalImportPaths {
|
||||
STATUSQ_MODULE_IMPORT_PATH,
|
||||
QML_IMPORT_ROOT + QStringLiteral("/../ui/app"),
|
||||
|
|
|
@ -80,6 +80,7 @@ Item {
|
|||
keyClick(Qt.Key_3)
|
||||
|
||||
compare(controlUnderTest.text, "13")
|
||||
compare(controlUnderTest.valid, true)
|
||||
}
|
||||
|
||||
function test_defaultValidation() {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -33,7 +33,7 @@ Item {
|
|||
walletAssetsStore: WalletAssetsStore {
|
||||
id: thisWalletAssetStore
|
||||
walletTokensStore: TokensStore {
|
||||
readonly property var plainTokensBySymbolModel: TokensBySymbolModel {}
|
||||
plainTokensBySymbolModel: TokensBySymbolModel {}
|
||||
}
|
||||
readonly property var baseGroupedAccountAssetModel: GroupedAccountsAssetsModel {}
|
||||
assetsWithFilteredBalances: thisWalletAssetStore.groupedAccountsAssetsModel
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.15
|
||||
import QtQml 2.15
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
|
|
@ -3,6 +3,8 @@ import QtQuick 2.15
|
|||
QtObject {
|
||||
id: root
|
||||
|
||||
property bool displayAssetsBelowBalance: false
|
||||
property var plainTokensBySymbolModel
|
||||
property bool displayAssetsBelowBalance
|
||||
property var getDisplayAssetsBelowBalanceThresholdDisplayAmount
|
||||
property double tokenListUpdatedAt
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import Models 1.0
|
|||
QtObject {
|
||||
id: root
|
||||
|
||||
property TokensStore walletTokensStore
|
||||
property TokensStore walletTokensStore: TokensStore {}
|
||||
|
||||
readonly property var groupedAccountsAssetsModel: GroupedAccountsAssetsModel {}
|
||||
property var assetsWithFilteredBalances
|
||||
|
@ -56,5 +56,11 @@ QtObject {
|
|||
joinRole: "communityId"
|
||||
}
|
||||
|
||||
property var assetsController
|
||||
property var assetsController: QtObject {
|
||||
property int revision
|
||||
|
||||
function filterAcceptsSymbol(symbol) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
|
@ -16,6 +17,12 @@ QtObject {
|
|||
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) {
|
||||
return balance
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import AppLayouts.Wallet.stores 1.0
|
|||
QtObject {
|
||||
id: root
|
||||
|
||||
readonly property var currencyStore: CurrenciesStore{}
|
||||
readonly property CurrenciesStore currencyStore: CurrenciesStore {}
|
||||
readonly property var senderAccounts: WalletSendAccountsModel {
|
||||
Component.onCompleted: selectedSenderAccount = senderAccounts.get(0)
|
||||
}
|
||||
|
@ -280,7 +280,7 @@ QtObject {
|
|||
},
|
||||
FastExpressionRole {
|
||||
name: "currentBalance"
|
||||
expression: __getTotalBalance(model.balances, model.decimals, model.symbol, root.selectedSenderAccount)
|
||||
expression: __getTotalBalance(model.balances, model.decimals)
|
||||
expectedRoles: ["balances", "decimals", "symbol"]
|
||||
},
|
||||
FastExpressionRole {
|
||||
|
@ -302,7 +302,7 @@ QtObject {
|
|||
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"]
|
||||
},
|
||||
ValueFilter {
|
||||
|
@ -339,7 +339,7 @@ QtObject {
|
|||
}
|
||||
|
||||
/* Internal function to calculate total balance */
|
||||
function __getTotalBalance(balances, decimals, symbol) {
|
||||
function __getTotalBalance(balances, decimals) {
|
||||
let totalBalance = 0
|
||||
for(let i=0; i<balances.count; i++) {
|
||||
let balancePerAddressPerChain = SQUtils.ModelUtils.get(balances, i)
|
||||
|
|
|
@ -94,7 +94,9 @@ Rectangle {
|
|||
property alias subTitleBadgeComponent: subTitleBadgeLoader.sourceComponent
|
||||
property alias errorIcon: errorIcon
|
||||
property alias statusListItemTagsRowLayout: statusListItemSubtitleTagsRow
|
||||
|
||||
property bool showLoadingIndicator: false
|
||||
property bool tagsScrollBarVisible: true
|
||||
|
||||
property int subTitleBadgeLoaderAlignment: Qt.AlignVCenter
|
||||
|
||||
|
@ -391,6 +393,7 @@ Rectangle {
|
|||
width: Math.min(statusListItemTagsSlotInline.width, statusListItemTagsSlotInline.availableWidth, parent.width)
|
||||
height: visible ? contentHeight : 0
|
||||
padding: 0
|
||||
ScrollBar.horizontal.policy: root.tagsScrollBarVisible ? ScrollBar.AsNeeded : ScrollBar.AlwaysOff
|
||||
|
||||
Row {
|
||||
id: statusListItemTagsSlotInline
|
||||
|
@ -399,7 +402,7 @@ Rectangle {
|
|||
|
||||
Repeater {
|
||||
id: tagsRepeater
|
||||
delegate: tagsDelegate
|
||||
delegate: root.tagsDelegate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -81,7 +81,6 @@ Control {
|
|||
icon: "close-circle"
|
||||
visible: closeButtonVisible
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
|
|
@ -21,14 +21,16 @@ StatusInput {
|
|||
}
|
||||
]
|
||||
|
||||
onKeyPressed: (event) => {
|
||||
onKeyPressed:
|
||||
(event) => {
|
||||
// additionally accept dot (.) and convert it to the correct decimal point char
|
||||
if (event.key === Qt.Key_Period || event.key === Qt.Key_Comma) {
|
||||
// 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)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@ Item {
|
|||
KeyNavigation.tab: root.tabNavItem
|
||||
Keys.onPressed: {
|
||||
edit.keyEvent = event.key
|
||||
root.keyPressed(event);
|
||||
root.keyPressed(event)
|
||||
}
|
||||
onCursorRectangleChanged: Utils.ensureVisible(flick, cursorRectangle)
|
||||
onActiveFocusChanged: if (root.pristine) root.pristine = false
|
||||
|
|
|
@ -72,6 +72,7 @@ QtObject {
|
|||
|
||||
|
||||
function stripTrailingZeroes(numStr, locale) {
|
||||
locale = locale || Qt.locale()
|
||||
let regEx = locale.decimalPoint == "." ? /(\.[0-9]*[1-9])0+$|\.0*$/ : /(\,[0-9]*[1-9])0+$|\,0*$/
|
||||
return numStr.replace(regEx, '$1')
|
||||
}
|
||||
|
@ -157,10 +158,10 @@ QtObject {
|
|||
var optDisplayDecimals = currencyAmount.displayDecimals
|
||||
var optStripTrailingZeroes = currencyAmount.stripTrailingZeroes
|
||||
if (options) {
|
||||
if (options.noSymbol !== undefined) {
|
||||
if (options.noSymbol !== undefined && options.noSymbol === true) {
|
||||
optNoSymbol = true
|
||||
}
|
||||
if (options.rawAmount !== undefined) {
|
||||
if (options.rawAmount !== undefined && options.rawAmount === true) {
|
||||
optRawAmount = true
|
||||
}
|
||||
if (options.minDecimals !== undefined && options.minDecimals > optDisplayDecimals) {
|
||||
|
@ -175,8 +176,7 @@ QtObject {
|
|||
var amountSuffix = ""
|
||||
|
||||
let minAmount = 10**-optDisplayDecimals
|
||||
if (currencyValue > 0 && currencyValue < minAmount && !optRawAmount)
|
||||
{
|
||||
if (currencyValue > 0 && currencyValue < minAmount && !optRawAmount) {
|
||||
// Handle amounts smaller than resolution
|
||||
amountStr = "<%1".arg(numberToLocaleString(minAmount, optDisplayDecimals, locale))
|
||||
} else {
|
||||
|
|
|
@ -2,7 +2,6 @@ import QtQuick 2.15
|
|||
import QtQuick.Controls 2.15
|
||||
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
|
||||
StatusButton {
|
||||
|
@ -12,6 +11,7 @@ StatusButton {
|
|||
icon.name: hovered ? "arrow-up" : "arrow-down"
|
||||
icon.color: Theme.palette.baseColor1
|
||||
|
||||
focusPolicy: Qt.NoFocus
|
||||
isRoundIcon: true
|
||||
radius: height/2
|
||||
normalColor: Theme.palette.indirectColor3
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,3 +6,4 @@ ManageAssetsPanel 1.0 ManageAssetsPanel.qml
|
|||
ManageCollectiblesPanel 1.0 ManageCollectiblesPanel.qml
|
||||
ManageHiddenPanel 1.0 ManageHiddenPanel.qml
|
||||
DAppsWorkflow 1.0 DAppsWorkflow.qml
|
||||
SwapInputPanel 1.0 SwapInputPanel.qml
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
import QtQuick 2.13
|
||||
|
||||
import utils 1.0
|
||||
import QtQml 2.15
|
||||
|
||||
/* 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. */
|
||||
|
@ -11,5 +9,5 @@ QtObject {
|
|||
property string fromTokensKey: ""
|
||||
property string fromTokenAmount: ""
|
||||
property string toTokenKey: ""
|
||||
property string toTokenAmount
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
||||
import utils 1.0
|
||||
|
|
|
@ -55,8 +55,12 @@ QObject {
|
|||
return networkString
|
||||
}
|
||||
|
||||
function formatCurrencyAmount(balance, symbol) {
|
||||
return root.currencyStore.formatCurrencyAmount(balance, symbol)
|
||||
function formatCurrencyAmount(balance, symbol, options = null, locale = null) {
|
||||
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!!
|
||||
|
@ -67,9 +71,80 @@ QObject {
|
|||
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 -----------------------------------------------------------------------------------------------------------------------------
|
||||
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 {
|
||||
id: filteredBalancesModel
|
||||
sourceModel: root.walletAssetsStore.baseGroupedAccountAssetModel
|
||||
|
@ -104,4 +179,14 @@ QObject {
|
|||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,12 +18,13 @@ QtObject {
|
|||
ex. uniswap list, status tokens list */
|
||||
readonly property var sourcesOfTokensModel: SortFilterProxyModel {
|
||||
sourceModel: !!root._allTokensModule ? root._allTokensModule.sourcesOfTokensModel : null
|
||||
proxyRoles: ExpressionRole {
|
||||
proxyRoles: FastExpressionRole {
|
||||
function sourceImage(sourceKey) {
|
||||
return Constants.getSupportedTokenSourceImage(sourceKey)
|
||||
}
|
||||
name: "image"
|
||||
expression: sourceImage(model.key)
|
||||
expectedRoles: ["key"]
|
||||
}
|
||||
filters: AnyOf {
|
||||
ValueFilter {
|
||||
|
@ -56,16 +57,18 @@ QtObject {
|
|||
sourceModel: root._joinFlatTokensModel
|
||||
|
||||
proxyRoles: [
|
||||
ExpressionRole {
|
||||
FastExpressionRole {
|
||||
name: "explorerUrl"
|
||||
expression: model.blockExplorerURL + "/token/" + model.address
|
||||
expectedRoles: ["blockExplorerURL", "address"]
|
||||
},
|
||||
ExpressionRole {
|
||||
FastExpressionRole {
|
||||
function tokenIcon(symbol) {
|
||||
return Constants.tokenIcon(symbol)
|
||||
}
|
||||
name: "image"
|
||||
expression: tokenIcon(model.symbol)
|
||||
expectedRoles: ["symbol"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -79,24 +82,27 @@ QtObject {
|
|||
readonly property var assetsBySymbolModel: SortFilterProxyModel {
|
||||
sourceModel: plainTokensBySymbolModel
|
||||
proxyRoles: [
|
||||
ExpressionRole {
|
||||
FastExpressionRole {
|
||||
function tokenIcon(symbol) {
|
||||
return Constants.tokenIcon(symbol)
|
||||
}
|
||||
name: "iconSource"
|
||||
expression: tokenIcon(model.symbol)
|
||||
expectedRoles: ["symbol"]
|
||||
},
|
||||
// TODO: Review if it can be removed
|
||||
ExpressionRole {
|
||||
FastExpressionRole {
|
||||
name: "shortName"
|
||||
expression: model.symbol
|
||||
expectedRoles: ["symbol"]
|
||||
},
|
||||
ExpressionRole {
|
||||
FastExpressionRole {
|
||||
function getCategory(index) {
|
||||
return 0
|
||||
}
|
||||
name: "category"
|
||||
expression: getCategory(model.communityId)
|
||||
expectedRoles: ["communityId"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -77,6 +77,7 @@ Item {
|
|||
readonly property TransactionStore transactionStore: TransactionStore {
|
||||
walletAssetStore: appMain.walletAssetsStore
|
||||
tokensStore: appMain.tokensStore
|
||||
currencyStore: appMain.currencyStore
|
||||
}
|
||||
|
||||
// set from main.qml
|
||||
|
|
|
@ -175,7 +175,7 @@ StatusDialog {
|
|||
}
|
||||
|
||||
if(!!popup.preDefinedAmountToSend) {
|
||||
amountToSendInput.input.text = popup.preDefinedAmountToSend
|
||||
amountToSendInput.input.text = Number(popup.preDefinedAmountToSend).toLocaleString(Qt.locale(), 'f', -128)
|
||||
}
|
||||
|
||||
if(!!popup.preSelectedRecipient) {
|
||||
|
@ -261,11 +261,9 @@ StatusDialog {
|
|||
id: holdingSelector
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
selectedSenderAccount: store.selectedSenderAccount.address
|
||||
assetsModel: popup.store.processedAssetsModel
|
||||
collectiblesModel: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null
|
||||
networksModel: popup.store.flatNetworksModel
|
||||
currentCurrencySymbol: d.currencyStore.currentCurrencySymbol
|
||||
visible: (!!d.selectedHolding && d.selectedHoldingType !== Constants.TokenType.Unknown) ||
|
||||
(!!d.hoveredHolding && d.hoveredHoldingType !== Constants.TokenType.Unknown)
|
||||
onItemSelected: {
|
||||
|
@ -291,7 +289,8 @@ StatusDialog {
|
|||
const max = d.prepareForMaxSend(input, d.hoveredHolding.symbol)
|
||||
if (max <= 0)
|
||||
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())
|
||||
}
|
||||
const max = d.prepareForMaxSend(d.maxInputBalance, d.inputSymbol)
|
||||
|
@ -394,7 +393,6 @@ StatusDialog {
|
|||
Layout.bottomMargin: Style.current.xlPadding
|
||||
visible: !d.selectedHolding
|
||||
|
||||
selectedSenderAccount: store.selectedSenderAccount.address
|
||||
assets: popup.store.processedAssetsModel
|
||||
collectibles: popup.preSelectedAccount ? popup.nestedCollectiblesModel : null
|
||||
networksModel: popup.store.flatNetworksModel
|
||||
|
@ -528,4 +526,3 @@ StatusDialog {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ StatusAmountInput {
|
|||
bottomPadding: 0
|
||||
|
||||
placeholderText: ""
|
||||
input.edit.cursorVisible: true
|
||||
input.edit.font.pixelSize: Utils.getFontSizeBasedOnLetterCount(text)
|
||||
input.placeholderFont.pixelSize: 34
|
||||
input.edit.padding: 0
|
||||
|
|
|
@ -19,15 +19,10 @@ StatusListItem {
|
|||
property var formatCurrentCurrencyAmount: function(balance){}
|
||||
property var formatCurrencyAmountFromBigInt: function(balance, symbol, decimals){}
|
||||
property var balancesModel
|
||||
property string selectedSenderAccount
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
readonly property int indexesThatCanBeShown:
|
||||
Math.floor((root.statusListItemInlineTagsSlot.availableWidth
|
||||
- compactRow.width) / statusListItemInlineTagsSlot.children[0].width) - 1
|
||||
|
||||
function selectToken() {
|
||||
root.tokenSelected({name, symbol, balances, decimals})
|
||||
}
|
||||
|
@ -62,30 +57,13 @@ StatusListItem {
|
|||
statusListItemInlineTagsSlot.spacing: 0
|
||||
tagsModel: root.balancesModel
|
||||
tagsDelegate: expandedItem
|
||||
statusListItemInlineTagsSlot.children: Row {
|
||||
id: compactRow
|
||||
spacing: -6
|
||||
Repeater {
|
||||
model: root.balancesModel
|
||||
delegate: compactItem
|
||||
}
|
||||
}
|
||||
tagsScrollBarVisible: false
|
||||
|
||||
radius: sensor.containsMouse || root.highlighted ? 0 : 8
|
||||
color: sensor.containsMouse || highlighted ? Theme.palette.baseColor2 : "transparent"
|
||||
radius: sensor.containsMouse || highlighted ? 0 : 8
|
||||
color: sensor.containsMouse || highlighted ? Theme.palette.statusListItem.highlightColor : "transparent"
|
||||
|
||||
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 {
|
||||
id: expandedItem
|
||||
StatusListItemTag {
|
||||
|
@ -98,8 +76,9 @@ StatusListItem {
|
|||
asset.width: 16
|
||||
asset.height: 16
|
||||
asset.isImage: true
|
||||
asset.name: Style.svg("tiny/%1".arg(iconUrl))
|
||||
visible: root.sensor.containsMouse && index <= d.indexesThatCanBeShown
|
||||
asset.name: Style.svg("tiny/%1".arg(model.iconUrl))
|
||||
tagClickable: true
|
||||
onTagClicked: d.selectToken()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,12 @@
|
|||
import QtQuick 2.13
|
||||
import QtQuick.Controls 2.13
|
||||
import QtQuick.Layouts 1.13
|
||||
|
||||
import SortFilterProxyModel 0.2
|
||||
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
|
||||
import StatusQ.Core.Utils 0.1
|
||||
import StatusQ.Controls 0.1
|
||||
import StatusQ.Components 0.1
|
||||
import StatusQ.Core.Backpressure 1.0
|
||||
|
||||
import shared.controls 1.0
|
||||
import utils 1.0
|
||||
|
||||
Item {
|
||||
|
@ -40,12 +35,6 @@ Item {
|
|||
property int contentIconSize: 21
|
||||
property int contentTextSize: 28
|
||||
|
||||
function resetInternal() {
|
||||
items = null
|
||||
selectedItem = null
|
||||
hoveredItem = null
|
||||
}
|
||||
|
||||
function openPopup() {
|
||||
root.comboBoxControl.popup.open()
|
||||
}
|
||||
|
@ -68,9 +57,8 @@ Item {
|
|||
|
||||
property string iconSource: ""
|
||||
onIconSourceChanged: tokenIcon.image.source = iconSource
|
||||
property string text: ""
|
||||
property string text: qsTr("Select asset")
|
||||
readonly property bool isItemSelected: !!root.selectedItem || !!root.hoveredItem
|
||||
|
||||
}
|
||||
|
||||
StatusComboBox {
|
||||
|
@ -80,9 +68,7 @@ Item {
|
|||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
|
||||
width: Math.min(implicitWidth, parent.width)
|
||||
|
||||
control.padding: 4
|
||||
control.padding: 12
|
||||
control.popup.width: 492
|
||||
control.popup.x: -root.x
|
||||
control.popup.verticalPadding: 0
|
||||
|
@ -92,20 +78,21 @@ Item {
|
|||
model: root.comboBoxModel
|
||||
|
||||
control.background: Rectangle {
|
||||
color: "transparent"
|
||||
color: !d.isItemSelected ? Theme.palette.primaryColor3 : "transparent"
|
||||
border.width: d.isItemSelected ? 0 : 1
|
||||
border.color: Theme.palette.directColor7
|
||||
radius: 12
|
||||
radius: 8
|
||||
|
||||
HoverHandler {
|
||||
cursorShape: root.enabled ? Qt.PointingHandCursor : undefined
|
||||
}
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
id: rowLayout
|
||||
implicitHeight: 38
|
||||
StatusRoundedImage {
|
||||
id: tokenIcon
|
||||
Layout.preferredWidth: root.contentIconSize
|
||||
Layout.preferredHeight: root.contentIconSize
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
visible: !!d.iconSource
|
||||
image.source: d.iconSource
|
||||
image.onStatusChanged: {
|
||||
|
@ -116,22 +103,17 @@ Item {
|
|||
}
|
||||
StatusBaseText {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
font.pixelSize: root.contentTextSize
|
||||
elide: Text.ElideRight
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: Theme.palette.miscColor1
|
||||
color: Theme.palette.primaryColor1
|
||||
text: d.text
|
||||
visible: d.isItemSelected
|
||||
}
|
||||
StatusIcon {
|
||||
Layout.leftMargin: -3
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
Layout.preferredWidth: 16
|
||||
Layout.preferredHeight: 16
|
||||
icon: "chevron-down"
|
||||
color: Theme.palette.miscColor1
|
||||
visible: !!root.selectedItem
|
||||
color: Theme.palette.primaryColor1
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import QtQml 2.15
|
||||
import QtQuick 2.15
|
||||
import QtQuick.Layouts 1.15
|
||||
|
@ -22,10 +21,8 @@ Item {
|
|||
id: root
|
||||
|
||||
property var assetsModel
|
||||
property string selectedSenderAccount
|
||||
property var collectiblesModel
|
||||
property var networksModel
|
||||
property string currentCurrencySymbol
|
||||
property bool onlyAssets: true
|
||||
property string searchText
|
||||
|
||||
|
@ -41,6 +38,16 @@ Item {
|
|||
property alias selectedItem: holdingItemSelector.selectedItem
|
||||
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) {
|
||||
d.browsingHoldingType = holdingType
|
||||
holdingItemSelector.selectedItem = null
|
||||
|
@ -66,8 +73,8 @@ Item {
|
|||
[qsTr("Assets")] :
|
||||
[qsTr("Assets"), qsTr("Collectibles")]
|
||||
|
||||
readonly property var updateSearchText: Backpressure.debounce(root, 1000, function(inputText) {
|
||||
searchText = inputText
|
||||
readonly property var updateSearchText: Backpressure.debounce(root, 500, function(inputText) {
|
||||
root.searchText = inputText
|
||||
})
|
||||
|
||||
function isAsset(type) {
|
||||
|
@ -103,10 +110,8 @@ Item {
|
|||
} else if (asset.image) {
|
||||
// Community assets have a dedicated image streamed from status-go
|
||||
return asset.image
|
||||
} else {
|
||||
return Constants.tokenIcon(asset.symbol)
|
||||
}
|
||||
return ""
|
||||
return Constants.tokenIcon(asset.symbol)
|
||||
}
|
||||
|
||||
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:
|
||||
readonly property int padding: 16
|
||||
readonly property int headerTopMargin: 5
|
||||
|
@ -187,7 +182,6 @@ Item {
|
|||
readonly property int collectibleContentIconSize: 28
|
||||
readonly property int assetContentTextSize: 28
|
||||
readonly property int collectibleContentTextSize: 15
|
||||
|
||||
}
|
||||
|
||||
HoldingItemSelector {
|
||||
|
@ -246,6 +240,7 @@ Item {
|
|||
contentIconSize: d.isAsset(d.currentHoldingType) ? d.assetContentIconSize : d.collectibleContentIconSize
|
||||
contentTextSize: d.isAsset(d.currentHoldingType) ? d.assetContentTextSize : d.collectibleContentTextSize
|
||||
comboBoxListViewSection.property: "isCommunityAsset"
|
||||
// TODO allow for different header/sections for the Swap modal
|
||||
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
|
||||
width: ListView.view.width
|
||||
|
@ -253,7 +248,10 @@ Item {
|
|||
text: Helpers.assetsSectionTitle(section, holdingItemSelector.hasCommunityTokens, d.isBrowsingGroup, d.isCurrentBrowsingTypeAsset)
|
||||
onOpenInfoPopup: Global.openPopup(communityInfoPopupCmp)
|
||||
}
|
||||
comboBoxControl.popup.onOpened: comboBoxControl.popup.contentItem.headerItem.focusSearch()
|
||||
comboBoxControl.popup.onClosed: comboBoxControl.popup.contentItem.headerItem.clear()
|
||||
|
||||
comboBoxControl.popup.x: root.width - comboBoxControl.popup.width
|
||||
}
|
||||
|
||||
Component {
|
||||
|
@ -264,6 +262,10 @@ Item {
|
|||
Component {
|
||||
id: headerComponent
|
||||
ColumnLayout {
|
||||
function focusSearch() {
|
||||
searchInput.input.forceActiveFocus()
|
||||
}
|
||||
|
||||
function clear() {
|
||||
searchInput.input.edit.clear()
|
||||
}
|
||||
|
@ -303,7 +305,7 @@ Item {
|
|||
CollectibleBackButtonWithInfo {
|
||||
Layout.fillWidth: true
|
||||
visible: d.isBrowsingGroup
|
||||
count: collectiblesModel.count
|
||||
count: collectiblesModel ? collectiblesModel.count : 0
|
||||
name: d.currentBrowsingGroupName
|
||||
onBackClicked: {
|
||||
if (!d.isCurrentBrowsingTypeAsset) {
|
||||
|
@ -325,7 +327,7 @@ Item {
|
|||
anchors.fill: parent
|
||||
|
||||
input.showBackground: false
|
||||
placeholderText: d.searchPlaceholderText
|
||||
placeholderText: root.searchPlaceholderText
|
||||
onTextChanged: Qt.callLater(d.updateSearchText, text)
|
||||
input.clearable: true
|
||||
input.implicitHeight: 56
|
||||
|
@ -344,7 +346,7 @@ Item {
|
|||
TokenBalancePerChainDelegate {
|
||||
objectName: "AssetSelector_ItemDelegate_" + symbol
|
||||
width: holdingItemSelector.comboBoxControl.popup.width
|
||||
selectedSenderAccount: root.selectedSenderAccount
|
||||
highlighted: !!holdingItemSelector.selectedItem && symbol === holdingItemSelector.selectedItem.symbol
|
||||
balancesModel: LeftJoinModel {
|
||||
leftModel: balances
|
||||
rightModel: root.networksModel
|
||||
|
@ -370,6 +372,7 @@ Item {
|
|||
CollectibleNestedDelegate {
|
||||
objectName: "CollectibleSelector_ItemDelegate_" + groupId
|
||||
width: holdingItemSelector.comboBoxControl.popup.width
|
||||
highlighted: !!holdingItemSelector.selectedItem && uid === holdingItemSelector.selectedItem.uid
|
||||
onItemSelected: {
|
||||
if (isGroup) {
|
||||
d.currentBrowsingGroupName = groupName
|
||||
|
|
|
@ -4,6 +4,8 @@ import QtQuick.Layouts 1.15
|
|||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Theme 0.1
|
||||
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 "../controls"
|
||||
|
@ -14,7 +16,7 @@ ColumnLayout {
|
|||
id: root
|
||||
|
||||
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:
|
||||
!inputIsFiat ? LocaleUtils.fractionalPartLength(d.inputNumber) : 0
|
||||
|
@ -36,6 +38,10 @@ ColumnLayout {
|
|||
property bool interactive: 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),
|
||||
// as a string representing integer decimal
|
||||
readonly property alias cryptoValueToSend: d.cryptoValueRawToSend
|
||||
|
@ -45,6 +51,8 @@ ColumnLayout {
|
|||
property var formatCurrencyAmount:
|
||||
(amount, symbol, options = null, locale = null) => {}
|
||||
|
||||
property bool loading
|
||||
|
||||
signal reCalculateSuggestedRoute()
|
||||
|
||||
QtObject {
|
||||
|
@ -88,11 +96,11 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
readonly property string zeroString:
|
||||
LocaleUtils.numberToLocaleString(0, 2, LocaleUtils.userInputLocale)
|
||||
LocaleUtils.numberToLocaleString(0, 2, topAmountToSendInput.locale)
|
||||
|
||||
readonly property double parsedInput:
|
||||
LocaleUtils.numberFromLocaleString(topAmountToSendInput.text,
|
||||
LocaleUtils.userInputLocale)
|
||||
topAmountToSendInput.locale)
|
||||
|
||||
readonly property double inputNumber:
|
||||
root.inputNumberValid ? d.parsedInput : 0
|
||||
|
@ -108,10 +116,7 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
StatusBaseText {
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
|
||||
text: root.isBridgeTx ? qsTr("Amount to bridge")
|
||||
: qsTr("Amount to send")
|
||||
text: root.caption
|
||||
font.pixelSize: 13
|
||||
lineHeight: 18
|
||||
lineHeightMode: Text.FixedHeight
|
||||
|
@ -119,17 +124,16 @@ ColumnLayout {
|
|||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
id: topItem
|
||||
|
||||
property double topAmountToSend: !inputIsFiat ? d.cryptoValueToSend
|
||||
: d.fiatValueToSend
|
||||
property string topAmountSymbol: !inputIsFiat ? d.selectedSymbol
|
||||
: root.currentCurrency
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
|
||||
AmountInputWithCursor {
|
||||
id: topAmountToSendInput
|
||||
Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft
|
||||
Layout.fillWidth: true
|
||||
Layout.maximumWidth: 250
|
||||
Layout.preferredWidth: !!text ? input.edit.paintedWidth + 2
|
||||
: textMetrics.advanceWidth
|
||||
|
@ -138,23 +142,21 @@ ColumnLayout {
|
|||
: Theme.palette.dangerColor1
|
||||
input.edit.readOnly: !root.interactive
|
||||
|
||||
validationMode: StatusInput.ValidationMode.Always
|
||||
validators: [
|
||||
StatusFloatValidator {
|
||||
id: floatValidator
|
||||
bottom: 0
|
||||
errorMessage: ""
|
||||
locale: topAmountToSendInput.locale
|
||||
},
|
||||
StatusValidator {
|
||||
errorMessage: ""
|
||||
|
||||
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 true
|
||||
|
||||
return num <= root.maxInputBalance
|
||||
return num > 0 && num <= root.maxInputBalance
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -162,7 +164,7 @@ ColumnLayout {
|
|||
TextMetrics {
|
||||
id: textMetrics
|
||||
text: topAmountToSendInput.placeholderText
|
||||
font: topAmountToSendInput.input.placeholder.font
|
||||
font: topAmountToSendInput.placeholderFont
|
||||
}
|
||||
|
||||
Keys.onReleased: {
|
||||
|
@ -172,33 +174,35 @@ ColumnLayout {
|
|||
if (!isNaN(amount))
|
||||
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 {
|
||||
id: txtBottom
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
text: root.formatCurrencyAmount(bottomItem.bottomAmountToSend,
|
||||
bottomItem.bottomAmountSymbol)
|
||||
Layout.maximumWidth: parent.width
|
||||
id: bottomItem
|
||||
objectName: "bottomItemText"
|
||||
|
||||
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
|
||||
color: Theme.palette.directColor5
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : undefined
|
||||
enabled: root.fiatInputInteractive && !!root.selectedHolding
|
||||
|
||||
onClicked: {
|
||||
topAmountToSendInput.validate()
|
||||
|
@ -207,12 +211,19 @@ ColumnLayout {
|
|||
bottomItem.bottomAmountToSend,
|
||||
bottomItem.bottomAmountSymbol,
|
||||
{ noSymbol: true, rawAmount: true },
|
||||
LocaleUtils.userInputLocale)
|
||||
topAmountToSendInput.locale)
|
||||
}
|
||||
inputIsFiat = !inputIsFiat
|
||||
root.inputIsFiat = !root.inputIsFiat
|
||||
d.waitTimer.restart()
|
||||
}
|
||||
}
|
||||
visible: !root.loading
|
||||
}
|
||||
|
||||
LoadingComponent {
|
||||
objectName: "bottomItemTextLoadingComponent"
|
||||
Layout.preferredWidth: bottomItem.width
|
||||
Layout.preferredHeight: bottomItem.height
|
||||
visible: root.loading
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@ import "../controls"
|
|||
Item {
|
||||
id: root
|
||||
|
||||
property string selectedSenderAccount
|
||||
property var assets: null
|
||||
property var collectibles: null
|
||||
property var networksModel
|
||||
|
@ -221,7 +220,6 @@ Item {
|
|||
TokenBalancePerChainDelegate {
|
||||
width: tokenList.width
|
||||
|
||||
selectedSenderAccount: root.selectedSenderAccount
|
||||
balancesModel: LeftJoinModel {
|
||||
leftModel: !!model & !!model.balances ? model.balances : null
|
||||
rightModel: root.networksModel
|
||||
|
@ -230,7 +228,7 @@ Item {
|
|||
onTokenSelected: function (selectedToken) {
|
||||
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){
|
||||
return root.formatCurrentCurrencyAmount(balance)
|
||||
}
|
||||
|
@ -253,13 +251,13 @@ Item {
|
|||
id: collectiblesDelegate
|
||||
CollectibleNestedDelegate {
|
||||
width: tokenList.width
|
||||
onItemHovered: root.tokenHovered(selectedItem.uid, tokenType, hovered)
|
||||
onItemHovered: root.tokenHovered(selectedItem.uid, Constants.TokenType.ERC721, hovered)
|
||||
onItemSelected: {
|
||||
if (isGroup) {
|
||||
d.currentBrowsingGroupName = groupName
|
||||
root.collectibles.currentGroupId = groupId
|
||||
} else {
|
||||
root.tokenSelected(selectedItem.uid, tokenType)
|
||||
root.tokenSelected(selectedItem.uid, Constants.TokenType.ERC721)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
import StatusQ.Core 0.1
|
||||
import StatusQ.Core.Utils 0.1 as SQUtils
|
||||
|
||||
import utils 1.0
|
||||
import AppLayouts.Profile.stores 1.0
|
||||
|
@ -982,12 +983,18 @@ QtObject {
|
|||
|
||||
function formatCurrencyAmount(amount, symbol, options = null, locale = null) {
|
||||
if (isNaN(amount)) {
|
||||
return "N/A"
|
||||
return qsTr("N/A")
|
||||
}
|
||||
var currencyAmount = getCurrencyAmount(amount, symbol)
|
||||
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) {
|
||||
var amount = _profileSectionModuleInst.ensUsernamesModule.getFiatValue(cryptoAmount, cryptoSymbol)
|
||||
return parseFloat(amount)
|
||||
|
|
|
@ -14,7 +14,7 @@ import AppLayouts.Wallet.stores 1.0
|
|||
QtObject {
|
||||
id: root
|
||||
|
||||
property CurrenciesStore currencyStore: CurrenciesStore {}
|
||||
property CurrenciesStore currencyStore
|
||||
property WalletAssetsStore walletAssetStore
|
||||
property TokensStore tokensStore
|
||||
|
||||
|
@ -276,10 +276,12 @@ QtObject {
|
|||
submodelRoleName: "balances"
|
||||
delegateModel: SortFilterProxyModel {
|
||||
sourceModel: submodel
|
||||
filters: FastExpressionFilter {
|
||||
expression: root.selectedSenderAccount.address === model.account
|
||||
expectedRoles: ["account"]
|
||||
filters: [
|
||||
ValueFilter {
|
||||
roleName: "account"
|
||||
value: root.selectedSenderAccount.address
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -302,7 +304,7 @@ QtObject {
|
|||
},
|
||||
FastExpressionRole {
|
||||
name: "currentBalance"
|
||||
expression: __getTotalBalance(model.balances, model.decimals, root.selectedSenderAccount)
|
||||
expression: __getTotalBalance(model.balances, model.decimals)
|
||||
expectedRoles: ["balances", "decimals"]
|
||||
},
|
||||
FastExpressionRole {
|
||||
|
@ -324,7 +326,7 @@ QtObject {
|
|||
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"]
|
||||
},
|
||||
ValueFilter {
|
||||
|
|
Loading…
Reference in New Issue