feat(@desktop/wallet): Using Floating Buttons in the Send Modal

fixes #6228
This commit is contained in:
Khushboo Mehta 2022-06-27 15:41:47 +02:00 committed by Khushboo-dev-cpp
parent 9b298e4ace
commit e6f809b921
4 changed files with 294 additions and 274 deletions

View File

@ -895,7 +895,7 @@ Item {
}
onLoaded: {
if (!!sendModal.selectedAccount) {
item.preSelectedAccount = sendModal.selectedAccount
item.selectedAccount = sendModal.selectedAccount
}
}
}

View File

@ -10,7 +10,6 @@ StatusInput {
property string cursorColor: Theme.palette.primaryColor1
height: input.edit.height
leftPadding: 0
rightPadding: 0

View File

@ -25,7 +25,7 @@ StatusModal {
property var store
property var contactsStore
property var preSelectedAccount
property var selectedAccount: store.currentAccount
property var preSelectedRecipient
property bool launchedFromChat: false
property MessageDialog sendingError: MessageDialog {
@ -40,10 +40,10 @@ StatusModal {
stack.currentGroup.isPending = true
let success = false
success = popup.store.transfer(
advancedHeader.accountSelector.selectedAccount.address,
advancedHeader.recipientSelector.selectedRecipient.address,
advancedHeader.assetSelector.selectedAsset.symbol,
advancedHeader.amountToSendInput.text,
popup.selectedAccount.address,
recipientSelector.selectedRecipient.address,
assetSelector.selectedAsset.symbol,
amountToSendInput.text,
gasSelector.selectedGasLimit,
gasSelector.suggestedFees.eip1559Enabled ? "" : gasSelector.selectedGasPrice,
gasSelector.selectedTipLimit,
@ -57,7 +57,7 @@ StatusModal {
property var recalculateRoutesAndFees: Backpressure.debounce(popup, 600, function() {
networkSelector.suggestedRoutes = popup.store.suggestedRoutes(
advancedHeader.accountSelector.selectedAccount.address, advancedHeader.amountToSendInput.text, advancedHeader.assetSelector.selectedAsset.symbol
popup.selectedAccount.address, amountToSendInput.text, assetSelector.selectedAsset.symbol
)
if (networkSelector.suggestedRoutes.length) {
networkSelector.selectedNetwork = networkSelector.suggestedRoutes[0]
@ -70,84 +70,244 @@ StatusModal {
}
})
QtObject {
id: d
readonly property string maxFiatBalance: Utils.stripTrailingZeros(parseFloat(assetSelector.selectedAsset.totalBalance).toFixed(4))
readonly property bool isReady: amountToSendInput.valid && !amountToSendInput.pending && recipientSelector.isValid && !recipientSelector.isPending
onIsReadyChanged: {
if(!isReady && stack.isLastGroup)
stack.back()
}
}
width: 556
// To-Do as per design once the account selector become floating the heigth can be as defined in design as 595
height: 670
height: 595
showHeader: false
showFooter: false
showAdvancedFooter: !!popup.advancedHeader ? popup.advancedHeader.isReady && gasValidator.isValid : false
showAdvancedFooter: d.isReady && gasValidator.isValid
showAdvancedHeader: true
onOpened: {
if(!!advancedHeader) {
advancedHeader.amountToSendInput.input.edit.forceActiveFocus()
onSelectedAccountChanged: popup.recalculateRoutesAndFees()
if(popup.launchedFromChat) {
advancedHeader.recipientSelector.selectedType = RecipientSelector.Type.Contact
advancedHeader.recipientSelector.readOnly = true
advancedHeader.recipientSelector.selectedRecipient = popup.preSelectedRecipient
}
if(popup.preSelectedAccount) {
advancedHeader.accountSelector.selectedAccount = popup.preSelectedAccount
onOpened: {
amountToSendInput.input.edit.forceActiveFocus()
assetSelector.assets = Qt.binding(function() {
if (popup.selectedAccount) {
return popup.selectedAccount.assets
}
})
if(popup.launchedFromChat) {
recipientSelector.selectedType = RecipientSelector.Type.Contact
recipientSelector.readOnly = true
recipientSelector.selectedRecipient = popup.preSelectedRecipient
}
popup.recalculateRoutesAndFees()
}
hasFloatingButtons: true
advancedHeaderComponent: SendModalHeader {
store: popup.store
contactsStore: popup.contactsStore
estimateGas: function() {
if(popup.contentItem.currentGroup.isValid)
gasSelector.estimateGas()
}
onAssetChanged: function() {
popup.recalculateRoutesAndFees()
}
onSelectedAccountChanged: function() {
popup.recalculateRoutesAndFees()
}
onAmountToSendChanged: function() {
popup.recalculateRoutesAndFees()
model: popup.store.accounts
selectedAccount: popup.selectedAccount
onUpdatedSelectedAccount: {
popup.selectedAccount = account
}
}
contentItem: TransactionStackView {
TransactionStackView {
id: stack
property alias currentGroup: stack.currentGroup
anchors.leftMargin: Style.current.xlPadding
anchors.topMargin: (!!advancedHeader ? advancedHeader.height: 0) + Style.current.smallPadding
anchors.topMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
anchors.bottomMargin: popup.showAdvancedFooter && !!advancedFooter ? advancedFooter.height : Style.current.padding
TransactionFormGroup {
id: group1
anchors.fill: parent
ScrollView {
height: stack.height
width: parent.width
ColumnLayout {
id: assetAndAmmountSelector
anchors.top: parent.top
anchors.right: parent.right
anchors.left: parent.left
RowLayout {
spacing: 16
StatusBaseText {
//% "Send"
text: qsTrId("command-button-send")
font.pixelSize: 15
color: Theme.palette.directColor1
Layout.alignment: Qt.AlignVCenter
}
StatusListItemTag {
//% "No balances active"
title: assetSelector.selectedAsset.totalBalance > 0 ? qsTr("Max: ") + (assetSelector.selectedAsset ? d.maxFiatBalance : "0.00") : qsTr("No balances active")
closeButtonVisible: false
titleText.font.pixelSize: 12
Layout.preferredHeight: 22
Layout.preferredWidth: childrenRect.width
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
AmountInputWithCursor {
id: amountToSendInput
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
width: parent.width - assetSelector.width
input.placeholderText: "0.00" + " " + assetSelector.selectedAsset.symbol
errorMessageCmp.anchors.rightMargin: -100
validators: [
StatusFloatValidator{
id: floatValidator
bottom: 0
top: d.maxFiatBalance
errorMessage: qsTr("Please enter a valid amount")
}
]
Keys.onReleased: {
let amount = amountToSendInput.text.trim()
if (isNaN(amount)) {
return
}
if (amount === "") {
txtFiatBalance.text = "0.00"
} else {
txtFiatBalance.text = popup.store.getFiatValue(amount, assetSelector.selectedAsset.symbol, popup.store.currentCurrency)
}
gasSelector.estimateGas()
popup.recalculateRoutesAndFees()
}
}
StatusAssetSelector {
id: assetSelector
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
defaultToken: Style.png("tokens/DEFAULT-TOKEN@3x")
getCurrencyBalanceString: function (currencyBalance) {
return Utils.toLocaleString(currencyBalance.toFixed(2), popup.store.locale, {"currency": true}) + " " + popup.store.currentCurrency.toUpperCase()
}
tokenAssetSourceFn: function (symbol) {
return symbol ? Style.png("tokens/" + symbol) : defaultToken
}
onSelectedAssetChanged: {
if (!assetSelector.selectedAsset) {
return
}
if (amountToSendInput.text === "" || isNaN(amountToSendInput.text)) {
return
}
txtFiatBalance.text = popup.store.getFiatValue(amountToSendInput.text, assetSelector.selectedAsset.symbol, popup.store.currentCurrency)
gasSelector.estimateGas()
popup.recalculateRoutesAndFees()
}
}
}
RowLayout {
Layout.alignment: Qt.AlignLeft
StyledTextField {
id: txtFiatBalance
color: txtFiatBalance.activeFocus ? Style.current.textColor : Style.current.secondaryText
font.weight: Font.Medium
font.pixelSize: 12
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: "0.00"
selectByMouse: true
background: Rectangle {
color: Style.current.transparent
}
padding: 0
Keys.onReleased: {
let balance = txtFiatBalance.text.trim()
if (balance === "" || isNaN(balance)) {
return
}
// To-Do Not refactored yet
// amountToSendInput.text = root.getCryptoValue(balance, popup.store.currentCurrency, assetSelector.selectedAsset.symbol)
}
}
StatusBaseText {
id: currencyText
text: popup.store.currentCurrency.toUpperCase()
font.pixelSize: 13
color: Theme.palette.directColor5
}
}
}
Rectangle {
id: border
anchors.top: assetAndAmmountSelector.bottom
anchors.topMargin: Style.current.padding
anchors.left: parent.left
anchors.leftMargin: -Style.current.xlPadding
width: popup.width
height: 1
color: Theme.palette.directColor8
visible: false
}
DropShadow {
anchors.fill: border
horizontalOffset: 0
verticalOffset: 2
radius: 8.0
samples: 17
color: Theme.palette.directColor1
source: border
}
ScrollView {
id: scrollView
height: stack.height - assetAndAmmountSelector.height
width: parent.width
anchors.top: border.bottom
anchors.topMargin: Style.current.halfPadding
anchors.left: parent.left
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
contentHeight: addressSelector.height + networkSelector.height + gasSelector.height + gasValidator.height
contentHeight: recipientSelector.height + addressSelector.height + networkSelector.height + gasSelector.height + gasValidator.height
clip: true
// To-do use standard StatusInput component once the flow for ens name resolution is clear
RecipientSelector {
anchors.top: assetAndAmmountSelector.bottom
anchors.topMargin: Style.current.halfPadding
anchors.right: parent.right
anchors.left: parent.left
id: recipientSelector
accounts: popup.store.accounts
contactsStore: popup.contactsStore
//% To
label: qsTr("To")
Layout.fillWidth: true
//% "Enter an ENS name or address"
input.placeholderText: qsTr("Enter an ENS name or address")
input.anchors.leftMargin: 0
input.anchors.rightMargin: 0
labelFont.pixelSize: 15
labelFont.weight: Font.Normal
input.height: 56
isSelectorVisible: false
addContactEnabled: false
onSelectedRecipientChanged: gasSelector.estimateGas()
}
TabAddressSelectorView {
id: addressSelector
anchors.top: parent.top
anchors.top: recipientSelector.bottom
anchors.right: parent.right
anchors.left: parent.left
store: popup.store
onContactSelected: {
if(!!popup.advancedHeader)
advancedHeader.recipientSelector.input.text = address
recipientSelector.input.text = address
}
}
@ -172,20 +332,20 @@ StatusModal {
chainId: networkSelector.selectedNetwork.chainId
width: stack.width
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
if (!(advancedHeader.accountSelector.selectedAccount && advancedHeader.accountSelector.selectedAccount.address &&
advancedHeader.recipientSelector.selectedRecipient && advancedHeader.recipientSelector.selectedRecipient.address &&
advancedHeader.assetSelector.selectedAsset && advancedHeader.assetSelector.selectedAsset.symbol &&
advancedHeader.amountToSendInput.text)) {
if (!(popup.selectedAccount && popup.selectedAccount.address &&
recipientSelector.selectedRecipient && recipientSelector.selectedRecipient.address &&
assetSelector.selectedAsset && assetSelector.selectedAsset.symbol &&
amountToSendInput.text)) {
selectedGasLimit = 250000
defaultGasLimit = selectedGasLimit
return
}
let gasEstimate = JSON.parse(popup.store.estimateGas(
advancedHeader.accountSelector.selectedAccount.address,
advancedHeader.recipientSelector.selectedRecipient.address,
advancedHeader.assetSelector.selectedAsset.symbol,
advancedHeader.amountToSendInput.text,
popup.selectedAccount.address,
recipientSelector.selectedRecipient.address,
assetSelector.selectedAsset.symbol,
amountToSendInput.text,
networkSelector.selectedNetwork.chainId || Global.currentChainId,
""))
@ -204,10 +364,10 @@ StatusModal {
GasValidator {
id: gasValidator
anchors.top: gasSelector.bottom
selectedAccount: advancedHeader.accountSelector.selectedAccount
selectedAmount: advancedHeader.amountToSendInput.text === "" ? 0.0
: parseFloat(advancedHeader.amountToSendInput.text)
selectedAsset: advancedHeader.assetSelector.selectedAsset
selectedAccount: popup.selectedAccount
selectedAmount: amountToSendInput.text === "" ? 0.0
: parseFloat(amountToSendInput.text)
selectedAsset: assetSelector.selectedAsset
selectedGasEthValue: gasSelector.selectedGasEthValue
selectedNetwork: networkSelector.selectedNetwork
}
@ -232,17 +392,17 @@ StatusModal {
advancedFooterComponent: SendModalFooter {
maxFiatFees: gasSelector.maxFiatFees
estimatedTxTimeFlag: gasSelector.estimatedTxTimeFlag
currentGroupPending: popup.contentItem.currentGroup.isPending
currentGroupValid: popup.contentItem.currentGroup.isValid
isLastGroup: popup.contentItem.isLastGroup
currentGroupPending: stack.currentGroup.isPending
currentGroupValid: stack.currentGroup.isValid
isLastGroup: stack.isLastGroup
onNextButtonClicked: {
const validity = popup.contentItem.currentGroup.validate()
const validity = stack.currentGroup.validate()
if (validity.isValid && !validity.isPending) {
if (popup.contentItem.isLastGroup) {
if (stack.isLastGroup) {
return popup.sendTransaction()
}
if(gasSelector.suggestedFees.eip1559Enabled && popup.contentItem.currentGroup === group1 && gasSelector.advancedMode){
if(gasSelector.suggestedFees.eip1559Enabled && stack.currentGroup === group1 && gasSelector.advancedMode){
if(gasSelector.showPriceLimitWarning || gasSelector.showTipLimitWarning){
Global.openPopup(transactionSettingsConfirmationPopupComponent, {
currentBaseFee: gasSelector.suggestedFees.baseFee,
@ -255,14 +415,14 @@ StatusModal {
showPriceLimitWarning: gasSelector.showPriceLimitWarning,
showTipLimitWarning: gasSelector.showTipLimitWarning,
onConfirm: function(){
popup.contentItem.next();
stack.next();
}
})
return
}
}
popup.contentItem.next()
stack.next()
}
}
}
@ -272,15 +432,6 @@ StatusModal {
TransactionSettingsConfirmationPopup {}
}
Connections {
target: advancedHeader
onIsReadyChanged: {
if(!advancedHeader.isReady && popup.contentItem.isLastGroup)
popup.contentItem.back()
}
}
Connections {
target: popup.store.walletSectionTransactionsInst
onTransactionSent: {

View File

@ -10,217 +10,87 @@ import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import "../controls"
import "../views"
Rectangle {
id: header
StatusFloatingButtonsSelector {
id: floatingHeader
property alias accountSelector: accountSelector
property alias recipientSelector: recipientSelector
property alias amountToSendInput: amountToSendInput
property alias assetSelector: assetSelector
property var selectedAccount
property var store
property var contactsStore
property var estimateGas: function() {}
property bool isReady: amountToSendInput.valid && !amountToSendInput.pending && recipientSelector.isValid && !recipientSelector.isPending
signal assetChanged()
signal selectedAccountChanged()
signal amountToSendChanged()
signal updatedSelectedAccount(var account)
QtObject {
id: _internal
property string maxFiatBalance: Utils.stripTrailingZeros(parseFloat(assetSelector.selectedAsset.totalBalance).toFixed(4))
//% "Please enter a valid amount"
property string sendAmountInputErrorMessage: qsTr("Please enter a valid amount")
//% "Max:"
property string maxString: qsTr("Max: ")
id: d
property var firstModelData: null
}
radius: 8
delegate: Rectangle {
width: button.width
height: button.height
radius: 8
visible: floatingHeader.visibleIndices.includes(index) && walletType !== Constants.watchWalletType
color: Theme.palette.statusAppLayout.backgroundColor
StatusButton {
id: button
topPadding: 8
bottomPadding: 0
implicitHeight: 32
defaultLeftPadding: 4
text: name
icon.emoji: !!emoji ? emoji: ""
icon.emojiSize: StatusQUtils.Emoji.size.middle
icon.name: !emoji ? "filled-account": ""
normalColor: "transparent"
highlighted: index === floatingHeader.currentIndex
onClicked: {
updatedSelectedAccount(model)
floatingHeader.currentIndex = index
}
Component.onCompleted: {
// On startup make the preseected wallet in the floating menu,
// and if the selectedAccount is watch only then select 0th item
if(index === 0) {
d.firstModelData = model
}
color: Theme.palette.statusModal.backgroundColor
width: parent.width
height: headerLayout.height + Style.current.xlPadding + (!!recipientSelector.input.text && recipientSelector.input.hasValidSearchResult ? 70 : 0)
if(name !== floatingHeader.selectedAccount.name) {
return
}
Rectangle {
id: border
anchors.bottom: parent.bottom
width: parent.width
height: parent.radius
color: parent.color
StatusModalDivider {
anchors.bottom: parent.bottom
width: parent.width
if(name === floatingHeader.selectedAccount.name) {
if(walletType !== Constants.watchWalletType) {
// If the selected index wont be displayed, added it to the visible indices
if(index > 2) {
visibleIndices = [0, 1, index]
}
floatingHeader.currentIndex = index
}
else {
floatingHeader.currentIndex = 0
updatedSelectedAccount(d.firstModelData)
}
}
}
}
}
ColumnLayout {
id: headerLayout
spacing: 8
width: parent.width
anchors.top: parent.top
anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.xlPadding
anchors.topMargin: Style.current.padding
anchors.left: parent.left
anchors.right: parent.right
StatusAccountSelector {
id: accountSelector
accounts: header.store.accounts
selectedAccount: {
const currAcc = header.store.currentAccount
if (currAcc.walletType !== Constants.watchWalletType) {
return currAcc
}
return null
}
currency: header.store.currentCurrency
width: parent.width
label: ""
onSelectedAccountChanged: {
assetSelector.assets = Qt.binding(function() {
if (selectedAccount) {
return selectedAccount.assets
}
})
if (isValid) { estimateGas() }
header.selectedAccountChanged()
}
showAccountDetails: false
selectField.select.height: 32
}
ColumnLayout {
id: assetAndAmmountSelector
RowLayout {
spacing: 16
StatusBaseText {
//% "Send"
text: qsTrId("command-button-send")
font.pixelSize: 15
color: Theme.palette.directColor1
Layout.alignment: Qt.AlignVCenter
}
StatusListItemTag {
//% "No balances active"
title: assetSelector.selectedAsset.totalBalance > 0 ? _internal.maxString + (assetSelector.selectedAsset ? _internal.maxFiatBalance : "0.00") : qsTr("No balances active")
closeButtonVisible: false
titleText.font.pixelSize: 12
height: 22
}
}
Item {
Layout.fillWidth: true
Layout.preferredHeight: childrenRect.height
AmountInputWithCursor {
id: amountToSendInput
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
width: parent.width - assetSelector.width
input.placeholderText: "0.00" + " " + assetSelector.selectedAsset.symbol
errorMessageCmp.anchors.rightMargin: -100
validators: [
StatusFloatValidator{
id: floatValidator
bottom: 0
top: _internal.maxFiatBalance
errorMessage: _internal.sendAmountInputErrorMessage
}
]
Keys.onReleased: {
let amount = amountToSendInput.text.trim()
if (isNaN(amount)) {
return
}
if (amount === "") {
txtFiatBalance.text = "0.00"
} else {
txtFiatBalance.text = header.store.getFiatValue(amount, assetSelector.selectedAsset.symbol, header.store.currentCurrency)
}
estimateGas()
header.amountToSendChanged()
}
}
StatusAssetSelector {
id: assetSelector
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
defaultToken: Style.png("tokens/DEFAULT-TOKEN@3x")
getCurrencyBalanceString: function (currencyBalance) {
return Utils.toLocaleString(currencyBalance.toFixed(2), header.store.locale, {"currency": true}) + " " + header.store.currentCurrency.toUpperCase()
}
tokenAssetSourceFn: function (symbol) {
return symbol ? Style.png("tokens/" + symbol) : defaultToken
}
onSelectedAssetChanged: {
if (!assetSelector.selectedAsset) {
return
}
if (amountToSendInput.text === "" || isNaN(amountToSendInput.text)) {
return
}
txtFiatBalance.text = header.store.getFiatValue(amountToSendInput.text, assetSelector.selectedAsset.symbol, header.store.currentCurrency)
estimateGas()
header.assetChanged()
}
}
}
RowLayout {
StyledTextField {
id: txtFiatBalance
color: txtFiatBalance.activeFocus ? Style.current.textColor : Style.current.secondaryText
font.weight: Font.Medium
font.pixelSize: 12
inputMethodHints: Qt.ImhFormattedNumbersOnly
text: "0.00"
selectByMouse: true
background: Rectangle {
color: Style.current.transparent
}
padding: 0
Keys.onReleased: {
let balance = txtFiatBalance.text.trim()
if (balance === "" || isNaN(balance)) {
return
}
// To-Do Not refactored yet
// amountToSendInput.text = root.getCryptoValue(balance, header.store.currentCurrency, assetSelector.selectedAsset.symbol)
}
}
StatusBaseText {
id: currencyText
text: header.store.currentCurrency.toUpperCase()
font.pixelSize: 13
color: Theme.palette.directColor5
}
}
}
// To-do use standard StatusInput component once the flow for ens name resolution is clear
RecipientSelector {
id: recipientSelector
accounts: header.store.accounts
contactsStore: header.contactsStore
//% To
label: qsTr("To")
Layout.fillWidth: true
//% "Enter an ENS name or address"
input.placeholderText: qsTr("Enter an ENS name or address")
input.anchors.leftMargin: 0
input.anchors.rightMargin: 0
labelFont.pixelSize: 15
labelFont.weight: Font.Normal
input.height: 56
isSelectorVisible: false
addContactEnabled: false
onSelectedRecipientChanged: estimateGas()
popupMenuDelegate: StatusListItem {
implicitWidth: 272
title: name
subTitle: currencyBalance
icon.emoji: !!emoji ? emoji: ""
icon.color: model.color
icon.name: !emoji ? "filled-account": ""
icon.letterSize: 14
icon.isLetterIdenticon: !!model.emoji
icon.background.color: Theme.palette.indirectColor1
onClicked: {
updatedSelectedAccount(model)
floatingHeader.itemSelected(index)
}
visible: !floatingHeader.visibleIndices.includes(index) && walletType !== Constants.watchWalletType
}
}