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-08-14 13:04:28 +00:00
|
|
|
onTokenAmountChanged: Qt.callLater(d.updateInputText) // FIXME remove the callLater(), shouldn't be needed now
|
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-08-14 13:04:28 +00:00
|
|
|
// FIXME drop after using ModelEntry, shouldn't be needed
|
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-08-14 13:04:28 +00:00
|
|
|
readonly property double value: amountToSendInput.asNumber
|
|
|
|
readonly property string rawValue: {
|
|
|
|
if (!d.isSelectedHoldingValidAsset || !d.selectedHolding.marketDetails || !d.selectedHolding.marketDetails.currencyPrice) {
|
|
|
|
return "0"
|
|
|
|
}
|
|
|
|
return amountToSendInput.amount
|
|
|
|
}
|
2024-06-25 13:37:42 +00:00
|
|
|
readonly property int rawValueMultiplierIndex: amountToSendInput.multiplierIndex
|
2024-08-14 13:04:28 +00:00
|
|
|
readonly property bool valueValid: value > 0 && amountToSendInput.valid &&
|
|
|
|
(swapSide === SwapInputPanel.SwapSide.Pay ? !amountEnteredGreaterThanBalance : true)
|
|
|
|
readonly property bool amountEnteredGreaterThanBalance: amountToSendInput.balanceExceeded
|
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() {
|
2024-08-14 13:04:28 +00:00
|
|
|
amountToSendInput.forceActiveFocus()
|
2024-07-03 22:08:03 +00:00
|
|
|
}
|
|
|
|
|
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-08-14 13:04:28 +00:00
|
|
|
// FIXME use ModelEntry
|
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-08-14 13:04:28 +00:00
|
|
|
readonly property double maxInputBalance: amountToSendInput.fiatMode ? maxFiatBalance : maxCryptoBalance
|
|
|
|
readonly property string inputSymbol: amountToSendInput.fiatMode ? root.currencyStore.currentCurrency
|
|
|
|
: (!!selectedHolding ? selectedHolding.symbol : "")
|
2024-06-18 22:51:49 +00:00
|
|
|
|
|
|
|
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) {
|
2024-08-14 13:04:28 +00:00
|
|
|
amountToSendInput.clear()
|
2024-06-18 17:24:07 +00:00
|
|
|
return
|
|
|
|
}
|
2024-07-30 09:18:15 +00:00
|
|
|
let amountToSet = SQUtils.AmountsArithmetic.fromString(tokenAmount).toFixed()
|
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
|
2024-07-30 09:18:15 +00:00
|
|
|
instead it should remain empty as entered by the user */
|
2024-08-14 13:04:28 +00:00
|
|
|
let currentInputTextAmount = SQUtils.AmountsArithmetic.fromString(amountToSendInput.text.replace(amountToSendInput.locale.decimalPoint,'.')).toFixed()
|
2024-07-30 09:18:15 +00:00
|
|
|
if (currentInputTextAmount !== amountToSet &&
|
2024-08-14 13:04:28 +00:00
|
|
|
!(amountToSet === "0" && !amountToSendInput.text)) {
|
|
|
|
amountToSendInput.setValue(tokenAmount)
|
2024-06-18 17:24:07 +00:00
|
|
|
}
|
|
|
|
}
|
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
|
2024-08-14 13:04:28 +00:00
|
|
|
strokeColor: amountToSendInput.cursorVisible ? Theme.palette.directColor7 : Theme.palette.directColor8
|
2024-05-28 17:39:41 +00:00
|
|
|
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
|
|
|
|
|
2024-08-14 21:56:48 +00:00
|
|
|
AmountToSend {
|
2024-08-14 13:04:28 +00:00
|
|
|
readonly property bool balanceExceeded:
|
|
|
|
SQUtils.AmountsArithmetic.fromNumber(maxSendButton.maxSafeCryptoValue, multiplierIndex).cmp(amount) === -1
|
|
|
|
|
|
|
|
readonly property double asNumber: {
|
|
|
|
if (!valid)
|
|
|
|
return 0
|
|
|
|
|
|
|
|
return parseFloat(text.replace(LocaleUtils.userInputLocale.decimalPoint, "."))
|
|
|
|
}
|
|
|
|
|
2024-05-28 17:39:41 +00:00
|
|
|
Layout.fillWidth: true
|
|
|
|
id: amountToSendInput
|
|
|
|
objectName: "amountToSendInput"
|
|
|
|
caption: root.caption
|
2024-06-13 00:45:33 +00:00
|
|
|
interactive: root.interactive
|
2024-08-14 13:04:28 +00:00
|
|
|
markAsInvalid: (root.swapSide === SwapInputPanel.SwapSide.Pay && (balanceExceeded || d.maxInputBalance === 0)) || (!!text && !valid)
|
2024-05-28 17:39:41 +00:00
|
|
|
fiatInputInteractive: root.fiatInputInteractive
|
2024-08-14 13:04:28 +00:00
|
|
|
multiplierIndex: d.isSelectedHoldingValidAsset && !!d.selectedHolding && !!d.selectedHolding.decimals ? d.selectedHolding.decimals : 18
|
|
|
|
price: d.isSelectedHoldingValidAsset ? (!!d.selectedHolding && !!d.selectedHolding.marketDetails ? d.selectedHolding.marketDetails.currencyPrice.amount : 1)
|
|
|
|
: 1
|
|
|
|
formatFiat: amount => root.currencyStore.formatCurrencyAmount(amount, root.currencyStore.currentCurrency)
|
|
|
|
formatBalance: amount => root.currencyStore.formatCurrencyAmount(amount, d.inputSymbol)
|
2024-05-28 17:39:41 +00:00
|
|
|
|
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
|
2024-08-14 13:04:28 +00:00
|
|
|
onActivated: if (root.interactive) root.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-08-14 13:04:28 +00:00
|
|
|
readonly property double maxSafeValue: WalletUtils.calculateMaxSafeSendAmount(d.maxInputBalance, d.inputSymbol)
|
|
|
|
readonly property double maxSafeCryptoValue: WalletUtils.calculateMaxSafeSendAmount(d.maxCryptoBalance, d.inputSymbol)
|
2024-07-17 15:48:56 +00:00
|
|
|
|
2024-08-14 13:04:28 +00:00
|
|
|
markAsInvalid: amountToSendInput.markAsInvalid
|
2024-07-17 15:48:56 +00:00
|
|
|
|
2024-08-14 13:04:28 +00:00
|
|
|
formattedValue: d.maxInputBalance === 0 ? LocaleUtils.userInputLocale.zeroDigit
|
|
|
|
: root.currencyStore.formatCurrencyAmount(
|
|
|
|
maxSafeValue, d.inputSymbol,
|
|
|
|
{ noSymbol: !amountToSendInput.fiatMode })
|
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-07-24 23:20:27 +00:00
|
|
|
// FIXME: This should be enabled after #15709 is resolved
|
|
|
|
enabled: false
|
2024-06-10 10:37:39 +00:00
|
|
|
|
|
|
|
onClicked: {
|
|
|
|
if (maxSafeValue)
|
2024-08-14 13:04:28 +00:00
|
|
|
amountToSendInput.setValue(SQUtils.AmountsArithmetic.fromNumber(maxSafeValue).toString())
|
2024-05-28 17:39:41 +00:00
|
|
|
else
|
2024-08-14 13:04:28 +00:00
|
|
|
amountToSendInput.clear()
|
|
|
|
root.forceActiveFocus()
|
2024-05-28 17:39:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|