2024-05-28 17:39:41 +00:00
|
|
|
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
|
|
|
|
|
2024-07-17 15:48:56 +00:00
|
|
|
import AppLayouts.Wallet 1.0
|
2024-06-10 10:37:39 +00:00
|
|
|
import AppLayouts.Wallet.controls 1.0
|
2024-06-18 22:51:49 +00:00
|
|
|
import AppLayouts.Wallet.stores 1.0
|
|
|
|
import AppLayouts.Wallet.adaptors 1.0
|
2024-06-10 10:37:39 +00:00
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
import shared.popups.send.views 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
|
2024-06-25 13:37:42 +00:00
|
|
|
property var plainTokensBySymbolModel // optional all tokens model, no balances
|
2024-05-28 17:39:41 +00:00
|
|
|
|
2024-06-18 22:51:49 +00:00
|
|
|
property int selectedNetworkChainId: -1
|
|
|
|
property string selectedAccountAddress
|
|
|
|
property string nonInteractiveTokensKey
|
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
property string tokenKey
|
2024-06-18 22:51:49 +00:00
|
|
|
onTokenKeyChanged: Qt.callLater(reevaluateSelectedId)
|
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
property string tokenAmount
|
2024-06-18 17:24:07 +00:00
|
|
|
onTokenAmountChanged: Qt.callLater(d.updateInputText)
|
2024-05-28 17:39:41 +00:00
|
|
|
|
|
|
|
property int swapSide: SwapInputPanel.SwapSide.Pay
|
|
|
|
property bool fiatInputInteractive
|
2024-06-14 11:34:20 +00:00
|
|
|
property bool mainInputLoading
|
|
|
|
property bool bottomTextLoading
|
2024-06-13 00:45:33 +00:00
|
|
|
property bool interactive: true
|
2024-05-28 17:39:41 +00:00
|
|
|
|
2024-06-18 22:51:49 +00:00
|
|
|
function reevaluateSelectedId() {
|
2024-06-18 17:24:07 +00:00
|
|
|
holdingSelector.selectToken(tokenKey)
|
|
|
|
d.selectedHolding = SQUtils.ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey)
|
2024-06-18 22:51:49 +00:00
|
|
|
}
|
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
// output API
|
2024-06-18 22:51:49 +00:00
|
|
|
readonly property string selectedHoldingId: holdingSelector.currentTokensKey
|
2024-06-14 11:34:20 +00:00
|
|
|
readonly property double value: amountToSendInput.cryptoValueToSendFloat
|
|
|
|
readonly property string rawValue: amountToSendInput.cryptoValueToSend
|
2024-06-25 13:37:42 +00:00
|
|
|
readonly property int rawValueMultiplierIndex: amountToSendInput.multiplierIndex
|
2024-06-14 11:34:20 +00:00
|
|
|
readonly property bool valueValid: amountToSendInput.inputNumberValid
|
|
|
|
readonly property bool amountEnteredGreaterThanBalance: value > maxSendButton.maxSafeValue
|
2024-07-10 18:35:24 +00:00
|
|
|
readonly property string accountBalanceFormatted: root.currencyStore.formatCurrencyAmount(d.maxCryptoBalance, d.selectedHolding.symbol)
|
2024-05-28 17:39:41 +00:00
|
|
|
|
|
|
|
// visual properties
|
|
|
|
property int swapExchangeButtonWidth: 44
|
|
|
|
property string caption: swapSide === SwapInputPanel.SwapSide.Pay ? qsTr("Pay") : qsTr("Receive")
|
|
|
|
|
2024-07-03 22:08:03 +00:00
|
|
|
function forceActiveFocus() {
|
|
|
|
amountToSendInput.input.forceActiveFocus()
|
|
|
|
}
|
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
enum SwapSide {
|
|
|
|
Pay = 0,
|
|
|
|
Receive = 1
|
|
|
|
}
|
|
|
|
|
|
|
|
padding: Style.current.padding
|
|
|
|
|
|
|
|
// by design
|
|
|
|
implicitWidth: 492
|
|
|
|
implicitHeight: 131
|
|
|
|
|
|
|
|
QtObject {
|
|
|
|
id: d
|
|
|
|
|
2024-06-18 22:51:49 +00:00
|
|
|
property var selectedHolding: SQUtils.ModelUtils.getByKey(holdingSelector.model, "tokensKey", holdingSelector.currentTokensKey)
|
2024-05-28 17:39:41 +00:00
|
|
|
|
2024-06-18 22:51:49 +00:00
|
|
|
readonly property bool isSelectedHoldingValidAsset: !!selectedHolding
|
2024-06-25 13:37:42 +00:00
|
|
|
readonly property double maxFiatBalance: isSelectedHoldingValidAsset && !!selectedHolding.currencyBalance ? selectedHolding.currencyBalance : 0
|
|
|
|
readonly property double maxCryptoBalance: isSelectedHoldingValidAsset && !!selectedHolding.currentBalance ? selectedHolding.currentBalance : 0
|
2024-05-28 17:39:41 +00:00
|
|
|
readonly property double maxInputBalance: amountToSendInput.inputIsFiat ? maxFiatBalance : maxCryptoBalance
|
2024-06-18 22:51:49 +00:00
|
|
|
readonly property string inputSymbol: amountToSendInput.inputIsFiat ? root.currencyStore.currentCurrency
|
|
|
|
: (!!selectedHolding ? selectedHolding.symbol : "")
|
|
|
|
|
|
|
|
readonly property var adaptor: TokenSelectorViewAdaptor {
|
|
|
|
assetsModel: root.processedAssetsModel
|
2024-06-25 13:37:42 +00:00
|
|
|
plainTokensBySymbolModel: root.plainTokensBySymbolModel
|
2024-06-18 22:51:49 +00:00
|
|
|
flatNetworksModel: root.flatNetworksModel
|
|
|
|
currentCurrency: root.currencyStore.currentCurrency
|
|
|
|
|
2024-06-25 13:37:42 +00:00
|
|
|
showAllTokens: true
|
2024-06-18 22:51:49 +00:00
|
|
|
enabledChainIds: root.selectedNetworkChainId !== -1 ? [root.selectedNetworkChainId] : []
|
2024-07-10 18:35:24 +00:00
|
|
|
accountAddress: root.selectedAccountAddress
|
2024-06-18 22:51:49 +00:00
|
|
|
searchString: holdingSelector.searchString
|
|
|
|
}
|
2024-06-18 17:24:07 +00:00
|
|
|
|
|
|
|
function updateInputText() {
|
|
|
|
if (!tokenAmount) {
|
|
|
|
amountToSendInput.input.input.edit.clear()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
let amountToSet = SQUtils.AmountsArithmetic.fromString(tokenAmount).toFixed().replace('.', LocaleUtils.userInputLocale.decimalPoint)
|
2024-07-11 12:55:04 +00:00
|
|
|
/* When deleting characters after a decimal point
|
|
|
|
eg: 0.000001 being deleted we have 0.00000 and it should not be updated to 0
|
|
|
|
and thats why we compare with toFixed()
|
|
|
|
also when deleting a numbers last digit, we should not update the text to 0
|
|
|
|
instead it should remain empty as entered by the user*/
|
|
|
|
if (SQUtils.AmountsArithmetic.fromString(amountToSendInput.input.text).toFixed() !== amountToSet &&
|
|
|
|
!(amountToSet === "0" && !amountToSendInput.input.text)) {
|
2024-06-18 17:24:07 +00:00
|
|
|
amountToSendInput.input.text = amountToSet
|
|
|
|
}
|
|
|
|
}
|
2024-05-28 17:39:41 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 11:30:48 +00:00
|
|
|
/* TODO: remove after https://github.com/status-im/status-desktop/issues/15604 is
|
|
|
|
implemented as this is hack to set token values correctly when model is reset */
|
|
|
|
Connections {
|
|
|
|
target: holdingSelector.model
|
|
|
|
function onRowsInserted() {
|
|
|
|
if(!!tokenKey) {
|
|
|
|
root.reevaluateSelectedId()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
background: Shape {
|
|
|
|
id: shape
|
|
|
|
|
2024-06-18 22:51:49 +00:00
|
|
|
property int radius: Style.current.radius
|
2024-05-28 17:39:41 +00:00
|
|
|
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
|
2024-06-13 00:45:33 +00:00
|
|
|
interactive: root.interactive
|
2024-06-18 22:51:49 +00:00
|
|
|
selectedHolding: d.selectedHolding // FIXME shouldn't be necesary to pass the whole object
|
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
fiatInputInteractive: root.fiatInputInteractive
|
2024-06-10 10:37:39 +00:00
|
|
|
input.input.edit.color: !input.valid ? Theme.palette.dangerColor1 : maxSendButton.hovered ? Theme.palette.baseColor1
|
|
|
|
: Theme.palette.directColor1
|
2024-05-28 17:39:41 +00:00
|
|
|
|
2024-06-25 13:37:42 +00:00
|
|
|
multiplierIndex: d.selectedHolding && d.selectedHolding.decimals ? d.selectedHolding.decimals : 0
|
2024-05-28 17:39:41 +00:00
|
|
|
|
|
|
|
maxInputBalance: (root.swapSide === SwapInputPanel.SwapSide.Receive || !d.isSelectedHoldingValidAsset) ? Number.POSITIVE_INFINITY
|
2024-06-10 10:37:39 +00:00
|
|
|
: maxSendButton.maxSafeValue
|
2024-05-28 17:39:41 +00:00
|
|
|
currentCurrency: root.currencyStore.currentCurrency
|
|
|
|
formatCurrencyAmount: root.currencyStore.formatCurrencyAmount
|
2024-06-14 11:34:20 +00:00
|
|
|
mainInputLoading: root.mainInputLoading
|
|
|
|
bottomTextLoading: root.bottomTextLoading
|
2024-05-28 17:39:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ColumnLayout {
|
|
|
|
Layout.preferredWidth: parent.width*.33
|
|
|
|
|
|
|
|
Item { Layout.fillHeight: true }
|
|
|
|
|
2024-06-18 22:51:49 +00:00
|
|
|
TokenSelector {
|
2024-05-28 17:39:41 +00:00
|
|
|
id: holdingSelector
|
|
|
|
objectName: "holdingSelector"
|
|
|
|
Layout.rightMargin: d.isSelectedHoldingValidAsset ? -root.padding : 0
|
|
|
|
Layout.alignment: Qt.AlignRight
|
2024-06-18 22:51:49 +00:00
|
|
|
model: d.adaptor.outputAssetsModel
|
|
|
|
nonInteractiveDelegateKey: root.nonInteractiveTokensKey
|
|
|
|
onActivated: amountToSendInput.input.forceActiveFocus()
|
2024-05-28 17:39:41 +00:00
|
|
|
}
|
|
|
|
|
2024-06-10 10:37:39 +00:00
|
|
|
Item { Layout.fillHeight: !maxSendButton.visible }
|
2024-05-28 17:39:41 +00:00
|
|
|
|
2024-06-10 10:37:39 +00:00
|
|
|
MaxSendButton {
|
|
|
|
id: maxSendButton
|
2024-07-17 15:48:56 +00:00
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
Layout.alignment: Qt.AlignRight
|
|
|
|
Layout.maximumWidth: parent.width
|
2024-06-10 10:37:39 +00:00
|
|
|
objectName: "maxTagButton"
|
|
|
|
|
2024-07-17 15:48:56 +00:00
|
|
|
readonly property double maxSafeValue: WalletUtils.calculateMaxSafeSendAmount(
|
|
|
|
d.maxInputBalance, d.inputSymbol)
|
|
|
|
readonly property string maxSafeValueAsString: maxSafeValue.toLocaleString(
|
|
|
|
LocaleUtils.userInputLocale, 'f', -128)
|
|
|
|
|
|
|
|
markAsInvalid: (!amountToSendInput.input.valid && !!amountToSendInput.input.text)
|
|
|
|
|| d.maxInputBalance === 0
|
|
|
|
|
|
|
|
formattedValue:
|
|
|
|
d.maxInputBalance === 0 ? LocaleUtils.userInputLocale.zeroDigit
|
|
|
|
: root.currencyStore.formatCurrencyAmount(
|
|
|
|
maxSafeValue, d.inputSymbol,
|
|
|
|
{ noSymbol: !amountToSendInput.inputIsFiat })
|
2024-06-10 10:37:39 +00:00
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
visible: d.isSelectedHoldingValidAsset && root.swapSide === SwapInputPanel.SwapSide.Pay
|
2024-06-10 10:37:39 +00:00
|
|
|
|
|
|
|
onClicked: {
|
|
|
|
if (maxSafeValue)
|
|
|
|
amountToSendInput.input.text = maxSafeValueAsString
|
2024-05-28 17:39:41 +00:00
|
|
|
else
|
|
|
|
amountToSendInput.input.input.edit.clear()
|
|
|
|
amountToSendInput.input.forceActiveFocus()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|