feat(@desktop/wallet): Adapt to new SendModal Design

fixes #5040
This commit is contained in:
Khushboo Mehta 2022-03-18 15:47:51 +01:00 committed by Iuri Matias
parent f29e825af4
commit 0903fa6ce2
14 changed files with 1152 additions and 288 deletions

@ -1 +1 @@
Subproject commit 41f67b59e954550207fb8882a5b58dd705cc4a38 Subproject commit b9aa49853adfb0015b7b9572d0a72ad6ebe54e7c

View File

@ -381,6 +381,7 @@ Item {
id: cmpSendTransactionWithEns id: cmpSendTransactionWithEns
SendModal { SendModal {
id: sendTransactionWithEns id: sendTransactionWithEns
anchors.centerIn: parent
store: root.rootStore store: root.rootStore
contactsStore: root.contactsStore contactsStore: root.contactsStore
onOpened: { onOpened: {
@ -390,9 +391,8 @@ Item {
onClosed: { onClosed: {
destroy() destroy()
} }
isContact: root.isContact launchedFromChat: true
selectRecipient.readOnly: true preSelectedRecipient: {
selectRecipient.selectedRecipient: {
parentModule.prepareChatContentModuleForChatId(activeChatId) parentModule.prepareChatContentModuleForChatId(activeChatId)
let chatContentModule = parentModule.getChatContentModule() let chatContentModule = parentModule.getChatContentModule()
@ -405,7 +405,6 @@ Item {
ensVerified: true ensVerified: true
} }
} }
selectRecipient.selectedType: RecipientSelector.Type.Contact
} }
} }

View File

@ -4,6 +4,7 @@ import QtQuick.Layouts 1.13
import StatusQ.Popups 0.1 import StatusQ.Popups 0.1
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0 import utils 1.0

View File

@ -53,6 +53,14 @@ QtObject {
property var walletSectionTransactionsInst: walletSectionTransactions property var walletSectionTransactionsInst: walletSectionTransactions
property bool isMultiNetworkEnabled: localAccountSensitiveSettings.isMultiNetworkEnabled
property var savedAddressesModel: walletSectionSavedAddresses.model
function getEtherscanLink() {
return profileSectionModule.ensUsernamesModule.getEtherscanLink()
}
function createCommunity(communityName, communityDescription, checkedMembership, ensOnlySwitchChecked, communityColor, communityImage, imageCropperModalaX, imageCropperModalaY, imageCropperModalbX, imageCropperModalbY) { function createCommunity(communityName, communityDescription, checkedMembership, ensOnlySwitchChecked, communityColor, communityImage, imageCropperModalaX, imageCropperModalaY, imageCropperModalbX, imageCropperModalbY) {
communitiesModuleInst.createCommunity(communityName, communityDescription, checkedMembership, ensOnlySwitchChecked, communityColor, communityImage, imageCropperModalaX, imageCropperModalaY, imageCropperModalbX, imageCropperModalbY); communitiesModuleInst.createCommunity(communityName, communityDescription, checkedMembership, ensOnlySwitchChecked, communityColor, communityImage, imageCropperModalaX, imageCropperModalaY, imageCropperModalbX, imageCropperModalbY);
} }
@ -104,4 +112,8 @@ QtObject {
function suggestedFees() { function suggestedFees() {
return JSON.parse(walletSectionTransactions.suggestedFees()) return JSON.parse(walletSectionTransactions.suggestedFees())
} }
function hex2Eth(value) {
return globalUtils.hex2Eth(value)
}
} }

View File

@ -801,6 +801,7 @@ Item {
} }
property var selectedAccount property var selectedAccount
sourceComponent: SendModal { sourceComponent: SendModal {
anchors.centerIn: parent
store: appMain.rootStore store: appMain.rootStore
contactsStore: appMain.rootStore.profileSectionStore.contactsStore contactsStore: appMain.rootStore.profileSectionStore.contactsStore
onOpened: { onOpened: {
@ -813,7 +814,7 @@ Item {
} }
onLoaded: { onLoaded: {
if(!!sendModal.selectedAccount) { if(!!sendModal.selectedAccount) {
item.selectFromAccount.selectedAccount = sendModal.selectedAccount item.preSelectedAccount = sendModal.selectedAccount
} }
} }
} }

View File

@ -0,0 +1,64 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
StatusInput {
id: cursorInput
property string cursorColor: Theme.palette.primaryColor1
height: input.edit.height
leftPadding: 0
rightPadding: 0
input.placeholderText: ""
input.edit.cursorVisible: true
input.edit.font.pixelSize: 32
input.placeholderFont.pixelSize: 32
input.leftPadding: 0
input.rightPadding: 0
input.topPadding: 0
input.bottomPadding: 0
input.edit.padding: 0
input.background.color: "transparent"
input.background.border.width: 0
// To-do this needs to be removed once https://github.com/status-im/StatusQ/issues/578 is implemented and cursor is moved to StatusInput
input.edit.cursorDelegate: Rectangle {
id: cursor
visible: input.edit.cursorVisible
color: cursorColor
width: 2
SequentialAnimation {
loops: Animation.Infinite
running: input.edit.cursorVisible
PropertyAction {
target: cursor
property: 'visible'
value: true
}
PauseAnimation {
duration: 600
}
PropertyAction {
target: cursor
property: 'visible'
value: false
}
PauseAnimation {
duration: 600
}
onStopped: {
cursor.visible = false
}
}
}
}

View File

@ -62,6 +62,8 @@ Item {
Input { Input {
id: chatKey id: chatKey
property bool hasValidSearchResult: false
//% "Enter ENS username or chat key" //% "Enter ENS username or chat key"
placeholderText: qsTrId("enter-contact-code") placeholderText: qsTrId("enter-contact-code")
visible: showSearch visible: showSearch
@ -90,6 +92,7 @@ Item {
return; return;
} }
chatKey.hasValidSearchResult = false
Qt.callLater(resolveENS, chatKey.text); Qt.callLater(resolveENS, chatKey.text);
} else { } else {
root.validationError = ""; root.validationError = "";
@ -100,6 +103,7 @@ Item {
Connections { Connections {
target: mainModule target: mainModule
onResolvedENS: { onResolvedENS: {
chatKey.hasValidSearchResult = false
if (chatKey.text == "") { if (chatKey.text == "") {
ensUsername.text = ""; ensUsername.text = "";
pubKey = ""; pubKey = "";
@ -113,6 +117,7 @@ Item {
//% "Can't chat with yourself" //% "Can't chat with yourself"
root.validationError = qsTrId("can-t-chat-with-yourself"); root.validationError = qsTrId("can-t-chat-with-yourself");
} else { } else {
chatKey.hasValidSearchResult = true
searchResults.username = Utils.addStatusEns(chatKey.text.trim()) searchResults.username = Utils.addStatusEns(chatKey.text.trim())
let userAlias = globalUtils.generateAlias(resolvedPubKey) let userAlias = globalUtils.generateAlias(resolvedPubKey)
userAlias = userAlias.length > 20 ? userAlias.substring(0, 19) + "..." : userAlias userAlias = userAlias.length > 20 ? userAlias.substring(0, 19) + "..." : userAlias
@ -151,6 +156,7 @@ Item {
noContactsRect.visible = false; noContactsRect.visible = false;
searchResults.loading = false; searchResults.loading = false;
root.validationError = ""; root.validationError = "";
chatKey.hasValidSearchResult = false
} }
} }
} }
@ -199,6 +205,7 @@ Item {
} }
root.pubKeys = pubKeysCopy root.pubKeys = pubKeysCopy
chatKey.hasValidSearchResult = false
userClicked(contact.pubKey, contact.isContact, contact.name, contact.address) userClicked(contact.pubKey, contact.isContact, contact.name, contact.address)
} }
expanded: !searchResults.loading && pubKey === "" && !searchResults.showProfileNotFoundMessage expanded: !searchResults.loading && pubKey === "" && !searchResults.showProfileNotFoundMessage
@ -217,6 +224,7 @@ Item {
if (!validate()) { if (!validate()) {
return return
} }
chatKey.hasValidSearchResult = false
userClicked(pubKey, isAddedContact, username, searchResults.address) userClicked(pubKey, isAddedContact, username, searchResults.address)
} }
onAddToContactsButtonClicked: { onAddToContactsButtonClicked: {

View File

@ -11,8 +11,8 @@ import StatusQ.Controls 0.1
Item { Item {
id: root id: root
width: parent.width width: parent.width
height: Style.current.smallPadding + prioritytext.height + height: visible ? Style.current.smallPadding + prioritytext.height +
(advancedMode ? advancedModeItemGroup.height : selectorButtons.height) (advancedMode ? advancedModeItemGroup.height : selectorButtons.height) : 0
property double gasPrice: 0 property double gasPrice: 0
@ -36,6 +36,7 @@ Item {
property alias selectedGasPrice: inputGasPrice.text property alias selectedGasPrice: inputGasPrice.text
property alias selectedGasLimit: inputGasLimit.text property alias selectedGasLimit: inputGasLimit.text
property string defaultGasLimit: "0" property string defaultGasLimit: "0"
property string maxFiatFees: selectedGasFiatValue + root.defaultCurrency.toUpperCase()
property alias selectedTipLimit: inputPerGasTipLimit.text property alias selectedTipLimit: inputPerGasTipLimit.text
@ -441,7 +442,7 @@ Item {
StyledText { StyledText {
id: maxPriorityFeeFiatText id: maxPriorityFeeFiatText
text: `${selectedGasFiatValue} ${root.defaultCurrency.toUpperCase()}` text: root.maxFiatFees
anchors.verticalCenter: maxPriorityFeeText.verticalCenter anchors.verticalCenter: maxPriorityFeeText.verticalCenter
anchors.left: maxPriorityFeeText.right anchors.left: maxPriorityFeeText.right
anchors.leftMargin: 6 anchors.leftMargin: 6

View File

@ -2,32 +2,32 @@ import QtQuick 2.13
import QtQuick.Controls 2.13 import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13 import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3 import QtQuick.Dialogs 1.3
import QtGraphicalEffects 1.0
import StatusQ.Controls.Validators 0.1
import utils 1.0 import utils 1.0
import shared.stores 1.0 import shared.stores 1.0
import StatusQ.Controls 0.1 import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import "../panels" import "../panels"
import "../controls" import "../controls"
import "../views" import "../views"
import "."
// TODO: replace with StatusModal StatusModal {
ModalPopup { id: popup
id: root
property var contactsStore
property alias selectFromAccount: selectFromAccount
property alias selectRecipient: selectRecipient
property alias stack: stack property alias stack: stack
property var store property var store
property bool isContact: false property var contactsStore
property var preSelectedAccount
//% "Send" property var preSelectedRecipient
title: qsTrId("command-button-send") property bool launchedFromChat: false
height: 540
property MessageDialog sendingError: MessageDialog { property MessageDialog sendingError: MessageDialog {
id: sendingError id: sendingError
//% "Error sending the transaction" //% "Error sending the transaction"
@ -39,11 +39,11 @@ ModalPopup {
function sendTransaction() { function sendTransaction() {
stack.currentGroup.isPending = true stack.currentGroup.isPending = true
let success = false let success = false
if(txtAmount.selectedAsset.address === "" || txtAmount.selectedAsset.address === Constants.zeroAddress){ if(advancedHeader.assetSelector.selectedAsset.address === "" || advancedHeader.assetSelector.selectedAsset.address === Constants.zeroAddress){
success = root.store.transferEth( success = popup.store.transferEth(
selectFromAccount.selectedAccount.address, advancedHeader.accountSelector.selectedAccount.address,
selectRecipient.selectedRecipient.address, advancedHeader.recipientSelector.selectedRecipient.address,
txtAmount.selectedAmount, advancedHeader.amountToSendInput.text,
gasSelector.selectedGasLimit, gasSelector.selectedGasLimit,
gasSelector.isEIP1559Enabled ? "" : gasSelector.selectedGasPrice, gasSelector.isEIP1559Enabled ? "" : gasSelector.selectedGasPrice,
gasSelector.selectedTipLimit, gasSelector.selectedTipLimit,
@ -51,11 +51,11 @@ ModalPopup {
transactionSigner.enteredPassword, transactionSigner.enteredPassword,
stack.uuid) stack.uuid)
} else { } else {
success = root.store.transferTokens( success = popup.store.transferTokens(
selectFromAccount.selectedAccount.address, advancedHeader.accountSelector.selectedAccount.address,
selectRecipient.selectedRecipient.address, advancedHeader.recipientSelector.selectedRecipient.address,
txtAmount.selectedAsset.address, advancedHeader.assetSelector.selectedAsset.address,
txtAmount.selectedAmount, advancedHeader.amountToSendInput.text,
gasSelector.selectedGasLimit, gasSelector.selectedGasLimit,
gasSelector.isEIP1559Enabled ? "" : gasSelector.selectedGasPrice, gasSelector.isEIP1559Enabled ? "" : gasSelector.selectedGasPrice,
gasSelector.selectedTipLimit, gasSelector.selectedTipLimit,
@ -63,110 +63,110 @@ ModalPopup {
transactionSigner.enteredPassword, transactionSigner.enteredPassword,
stack.uuid) stack.uuid)
} }
// Till the method is moved to thread this is handled by a signal to which connection is made in the end of the file
// if(!success){
// //% "Invalid transaction parameters"
// sendingError.text = qsTrId("invalid-transaction-parameters")
// sendingError.open()
// }
} }
TransactionStackView { 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
showHeader: false
showFooter: false
showAdvancedFooter: !!popup.advancedHeader ? popup.advancedHeader.isReady && gasValidator.isValid : false
showAdvancedHeader: true
onOpened: {
if(!!advancedHeader) {
advancedHeader.amountToSendInput.input.edit.forceActiveFocus()
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
}
}
}
advancedHeaderComponent: SendModalHeader {
store: popup.store
contactsStore: popup.contactsStore
estimateGas: function() {
if(popup.contentItem.currentGroup.isValid)
gasSelector.estimateGas()
}
}
contentItem: TransactionStackView {
id: stack id: stack
anchors.fill: parent property alias currentGroup: stack.currentGroup
anchors.leftMargin: Style.current.padding anchors.leftMargin: Style.current.xlPadding
anchors.rightMargin: Style.current.padding anchors.topMargin: (!!advancedHeader ? advancedHeader.height: 0) + Style.current.smallPadding
onGroupActivated: { anchors.rightMargin: Style.current.xlPadding
root.title = group.headerText anchors.bottomMargin: popup.showAdvancedFooter && !!advancedFooter ? advancedFooter.height : Style.current.padding
btnNext.text = group.footerText
}
TransactionFormGroup { TransactionFormGroup {
id: group1 id: group1
//% "Send" anchors.fill: parent
headerText: qsTrId("command-button-send") ScrollView {
//% "Continue" height: stack.height
footerText: qsTrId("continue") width: parent.width
anchors.top: parent.top
anchors.left: parent.left
StatusAccountSelector { ScrollBar.vertical.policy: ScrollBar.AlwaysOff
id: selectFromAccount ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
accounts: root.store.accounts contentHeight: addressSelector.height + networkAndFeesSelector.height + gasSelector.height + gasValidator.height
selectedAccount: { clip: true
const currAcc = root.store.currentAccount
if (currAcc.walletType !== Constants.watchWalletType) {
return currAcc
}
return null
}
currency: root.store.currentCurrency
width: stack.width
//% "From account"
label: qsTrId("from-account")
onSelectedAccountChanged: if (isValid) { gasSelector.estimateGas() }
}
SeparatorWithIcon {
id: separator
anchors.top: selectFromAccount.bottom
anchors.topMargin: 19
}
RecipientSelector {
id: selectRecipient
accounts: root.store.accounts
contactsStore: root.contactsStore
//% "Recipient"
label: qsTrId("recipient")
anchors.top: separator.bottom
anchors.topMargin: 10
width: stack.width
onSelectedRecipientChanged: if (isValid) { gasSelector.estimateGas() }
}
}
TransactionFormGroup {
id: group2
//% "Send"
headerText: qsTrId("command-button-send")
//% "Preview"
footerText: qsTr("Continue")
AssetAndAmountInput { TabAddressSelectorView {
id: txtAmount id: addressSelector
selectedAccount: selectFromAccount.selectedAccount anchors.top: parent.top
currentCurrency: root.store.currentCurrency anchors.right: parent.right
// TODO make those use a debounce anchors.left: parent.left
getFiatValue: root.store.getFiatValue store: popup.store
// getCryptoValue: RootStore.cryptoValue onContactSelected: {
width: stack.width if(!!popup.advancedHeader)
onSelectedAssetChanged: if (isValid) { gasSelector.estimateGas() } advancedHeader.recipientSelector.input.text = address
onSelectedAmountChanged: if (isValid) { gasSelector.estimateGas() }
} }
}
TabNetworkAndFees {
id: networkAndFeesSelector
anchors.top: addressSelector.bottom
anchors.right: parent.right
anchors.left: parent.left
visible: popup.store.isMultiNetworkEnabled
}
GasSelector { GasSelector {
id: gasSelector id: gasSelector
anchors.top: txtAmount.bottom anchors.top: networkAndFeesSelector.visible ? networkAndFeesSelector.bottom : addressSelector.bottom
anchors.topMargin: Style.current.padding gasPrice: parseFloat(popup.store.gasPrice)
gasPrice: parseFloat(root.store.gasPrice) getGasEthValue: popup.store.getGasEthValue
getGasEthValue: root.store.getGasEthValue getFiatValue: popup.store.getFiatValue
getFiatValue: root.store.getFiatValue defaultCurrency: popup.store.currentCurrency
defaultCurrency: root.store.currentCurrency isEIP1559Enabled: popup.store.isEIP1559Enabled()
isEIP1559Enabled: root.store.isEIP1559Enabled() latestBaseFeePerGas: popup.store.latestBaseFeePerGas()
latestBaseFeePerGas: root.store.latestBaseFeePerGas() suggestedFees: popup.store.suggestedFees()
suggestedFees: root.store.suggestedFees()
visible: !popup.store.isMultiNetworkEnabled
width: stack.width width: stack.width
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() { property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address && if (!(advancedHeader.accountSelector.selectedAccount && advancedHeader.accountSelector.selectedAccount.address &&
selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address && advancedHeader.recipientSelector.selectedRecipient && advancedHeader.recipientSelector.selectedRecipient.address &&
txtAmount.selectedAsset && txtAmount.selectedAsset.address && advancedHeader.assetSelector.selectedAsset && advancedHeader.assetSelector.selectedAsset.address &&
txtAmount.selectedAmount)) { advancedHeader.amountToSendInput.text)) {
selectedGasLimit = 250000 selectedGasLimit = 250000
defaultGasLimit = selectedGasLimit defaultGasLimit = selectedGasLimit
return return
} }
let gasEstimate = JSON.parse(root.store.estimateGas( let gasEstimate = JSON.parse(popup.store.estimateGas(
selectFromAccount.selectedAccount.address, advancedHeader.accountSelector.selectedAccount.address,
selectRecipient.selectedRecipient.address, advancedHeader.recipientSelector.selectedRecipient.address,
txtAmount.selectedAsset.address, advancedHeader.assetSelector.selectedAsset.address,
txtAmount.selectedAmount, advancedHeader.amountToSendInput.text,
"")) ""))
if (!gasEstimate.success) { if (!gasEstimate.success) {
@ -182,93 +182,42 @@ ModalPopup {
GasValidator { GasValidator {
id: gasValidator id: gasValidator
anchors.top: gasSelector.bottom anchors.top: gasSelector.bottom
selectedAccount: selectFromAccount.selectedAccount selectedAccount: advancedHeader.accountSelector.selectedAccount
selectedAmount: parseFloat(txtAmount.selectedAmount) selectedAmount: parseFloat(advancedHeader.amountToSendInput.text)
selectedAsset: txtAmount.selectedAsset selectedAsset: advancedHeader.assetSelector.selectedAsset
selectedGasEthValue: gasSelector.selectedGasEthValue selectedGasEthValue: gasSelector.selectedGasEthValue
} }
} }
TransactionFormGroup {
id: group3
//% "Transaction preview"
headerText: qsTrId("transaction-preview")
//% "Sign with password"
footerText: qsTrId("sign-with-password")
TransactionPreview {
id: pvwTransaction
width: stack.width
fromAccount: selectFromAccount.selectedAccount
gas: {
"value": gasSelector.selectedGasEthValue,
"symbol": "ETH",
"fiatValue": gasSelector.selectedGasFiatValue
}
toAccount: selectRecipient.selectedRecipient
asset: txtAmount.selectedAsset
amount: { "value": txtAmount.selectedAmount, "fiatValue": txtAmount.selectedFiatAmount }
currency: root.store.currentCurrency
}
SendToContractWarning {
id: sendToContractWarning
anchors.top: pvwTransaction.bottom
selectedRecipient: selectRecipient.selectedRecipient
}
} }
TransactionFormGroup { TransactionFormGroup {
id: group4 id: group4
//% "Sign with password"
headerText: qsTrId("sign-with-password") StackView.onActivated: {
//% "Send %1 %2" transactionSigner.forceActiveFocus(Qt.MouseFocusReason)
footerText: qsTrId("send--1--2").arg(txtAmount.selectedAmount).arg(!!txtAmount.selectedAsset ? txtAmount.selectedAsset.symbol : "") }
TransactionSigner { TransactionSigner {
id: transactionSigner id: transactionSigner
Layout.topMargin: Style.current.smallPadding
width: stack.width width: stack.width
signingPhrase: root.store.signingPhrase signingPhrase: popup.store.signingPhrase
} }
} }
} }
footer: Item { advancedFooterComponent: SendModalFooter {
width: parent.width maxFiatFees: gasSelector.maxFiatFees
height: btnNext.height currentGroupPending: popup.contentItem.currentGroup.isPending
currentGroupValid: popup.contentItem.currentGroup.isValid
StatusRoundButton { isLastGroup: popup.contentItem.isLastGroup
id: btnBack onNextButtonClicked: {
anchors.left: parent.left const validity = popup.contentItem.currentGroup.validate()
visible: !stack.isFirstGroup
icon.name: "arrow-right"
icon.width: 20
icon.height: 16
icon.rotation: 180
onClicked: {
stack.back()
}
}
Component {
id: transactionSettingsConfirmationPopupComponent
TransactionSettingsConfirmationPopup {
}
}
StatusButton {
id: btnNext
anchors.right: parent.right
//% "Next"
text: qsTrId("next")
enabled: stack.currentGroup.isValid && !stack.currentGroup.isPending
loading: stack.currentGroup.isPending
onClicked: {
const validity = stack.currentGroup.validate()
if (validity.isValid && !validity.isPending) { if (validity.isValid && !validity.isPending) {
if (stack.isLastGroup) { if (popup.contentItem.isLastGroup) {
return root.sendTransaction() return popup.sendTransaction()
} }
if(gasSelector.isEIP1559Enabled && stack.currentGroup === group2 && gasSelector.advancedMode){ if(gasSelector.isEIP1559Enabled && popup.contentItem.currentGroup === group1 && gasSelector.advancedMode){
if(gasSelector.showPriceLimitWarning || gasSelector.showTipLimitWarning){ if(gasSelector.showPriceLimitWarning || gasSelector.showTipLimitWarning){
Global.openPopup(transactionSettingsConfirmationPopupComponent, { Global.openPopup(transactionSettingsConfirmationPopupComponent, {
currentBaseFee: gasSelector.latestBaseFeePerGasGwei, currentBaseFee: gasSelector.latestBaseFeePerGasGwei,
@ -281,21 +230,34 @@ ModalPopup {
showPriceLimitWarning: gasSelector.showPriceLimitWarning, showPriceLimitWarning: gasSelector.showPriceLimitWarning,
showTipLimitWarning: gasSelector.showTipLimitWarning, showTipLimitWarning: gasSelector.showTipLimitWarning,
onConfirm: function(){ onConfirm: function(){
stack.next(); popup.contentItem.next();
} }
}) })
return return
} }
} }
stack.next() popup.contentItem.next()
} }
} }
} }
// Not Refactored Yet Component {
id: transactionSettingsConfirmationPopupComponent
TransactionSettingsConfirmationPopup {}
}
Connections { Connections {
target: root.store.walletSectionTransactionsInst target: advancedHeader
onIsReadyChanged: {
if(!advancedHeader.isReady && popup.contentItem.isLastGroup)
popup.contentItem.back()
}
}
Connections {
target: popup.store.walletSectionTransactionsInst
onTransactionSent: { onTransactionSent: {
try { try {
let response = JSON.parse(txResult) let response = JSON.parse(txResult)
@ -318,36 +280,29 @@ ModalPopup {
Global.toastMessage.source = Style.svg("loading") Global.toastMessage.source = Style.svg("loading")
Global.toastMessage.iconColor = Style.current.primary Global.toastMessage.iconColor = Style.current.primary
Global.toastMessage.iconRotates = true Global.toastMessage.iconRotates = true
// Refactor this Global.toastMessage.link = `${popup.store.getEtherscanLink()}/${response.result}`
// Global.toastMessage.link = `${walletModel.utilsView.etherscanLink}/${response.result}`
Global.toastMessage.open() Global.toastMessage.open()
root.close() popup.close()
} catch (e) { } catch (e) {
console.error('Error parsing the response', e) console.error('Error parsing the response', e)
} }
} }
// onTransactionCompleted: { // Not Refactored Yet
// if (success) { // onTransactionCompleted: {
// //% "Transaction completed" // if (success) {
// Global.toastMessage.title = qsTrId("transaction-completed") // //% "Transaction completed"
// Global.toastMessage.source = Style.svg("check-circle") // Global.toastMessage.title = qsTrId("transaction-completed")
// Global.toastMessage.iconColor = Style.current.success // Global.toastMessage.source = Style.svg("check-circle")
// } else { // Global.toastMessage.iconColor = Style.current.success
// //% "Transaction failed" // } else {
// Global.toastMessage.title = qsTrId("ens-registration-failed-title") // //% "Transaction failed"
// Global.toastMessage.source = Style.svg("block-icon") // Global.toastMessage.title = qsTrId("ens-registration-failed-title")
// Global.toastMessage.iconColor = Style.current.danger // Global.toastMessage.source = Style.svg("block-icon")
// } // Global.toastMessage.iconColor = Style.current.danger
// Global.toastMessage.link = `${walletModel.utilsView.etherscanLink}/${txHash}` // }
// Global.toastMessage.open() // Global.toastMessage.link = `${walletModel.utilsView.etherscanLink}/${txHash}`
// } // Global.toastMessage.open()
} // }
} }
} }
/*##^##
Designer {
D{i:0;autoSize:true;height:480;width:640}
}
##^##*/

View File

@ -0,0 +1,103 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
Rectangle {
id: footer
//% "Unknown"
property string estimatedTime: qsTr("Unknown")
property string maxFiatFees: ""
property bool currentGroupPending: true
property bool currentGroupValid: false
property bool isLastGroup: false
signal nextButtonClicked()
width: parent.width
height: 82
radius: 8
color: Theme.palette.statusModal.backgroundColor
Rectangle {
anchors.top: parent.top
width: parent.width
height: parent.radius
color: parent.color
StatusModalDivider {
anchors.top: parent.top
width: parent.width
}
}
RowLayout {
anchors.verticalCenter: parent.verticalCenter
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: 32
anchors.rightMargin: 32
ColumnLayout {
StatusBaseText {
font.pixelSize: 15
color: Theme.palette.directColor5
//% "Estimated Time:"
text: qsTr("Estimated Time:")
wrapMode: Text.WordWrap
}
// To-do not implemented yet
StatusBaseText {
font.pixelSize: 15
color: Theme.palette.directColor1
text: estimatedTime
wrapMode: Text.WordWrap
}
}
// To fill gap in between
Item {
Layout.fillWidth: true
implicitHeight: 1
}
RowLayout {
spacing: 16
ColumnLayout {
StatusBaseText {
font.pixelSize: 15
color: Theme.palette.directColor5
//% "Max Fees:"
text: qsTr("Max Fees:")
wrapMode: Text.WordWrap
}
StatusBaseText {
font.pixelSize: 15
color: Theme.palette.directColor1
text: maxFiatFees
wrapMode: Text.WordWrap
}
}
StatusFlatButton {
icon.name: isLastGroup ? "" : "password"
//% "Send"
text: qsTrId("command-button-send")
size: StatusBaseButton.Size.Large
normalColor: Theme.palette.primaryColor2
disaledColor: Theme.palette.baseColor2
enabled: currentGroupValid && !currentGroupPending
loading: currentGroupPending
onClicked: nextButtonClicked()
}
}
}
}

View File

@ -0,0 +1,219 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import StatusQ.Controls.Validators 0.1
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import "../controls"
import "../views"
Rectangle {
id: header
property alias accountSelector: accountSelector
property alias recipientSelector: recipientSelector
property alias amountToSendInput: amountToSendInput
property alias assetSelector: assetSelector
property var store
property var contactsStore
property var estimateGas: function() {}
property bool isReady: amountToSendInput.valid && !amountToSendInput.pending && recipientSelector.isValid && !recipientSelector.isPending
QtObject {
id: _internal
property string maxFiatBalance: Utils.stripTrailingZeros(parseFloat(assetSelector.selectedAsset.balance).toFixed(4))
//% "Please enter a valid amount"
property string sendAmountInputErrorMessage: qsTr("Please enter a valid amount")
//% "Max:"
property string maxString: qsTr("Max: ")
}
radius: 8
color: Theme.palette.statusModal.backgroundColor
width: parent.width
height: headerLayout.height + Style.current.xlPadding + (!!recipientSelector.input.text && recipientSelector.input.hasValidSearchResult ? 70 : 0)
Rectangle {
id: border
anchors.bottom: parent.bottom
width: parent.width
height: parent.radius
color: parent.color
StatusModalDivider {
anchors.bottom: parent.bottom
width: parent.width
}
}
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() }
}
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.balance > 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()
}
}
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()
}
}
}
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.implicitHeight: 56
isSelectorVisible: false
addContactEnabled: false
onSelectedRecipientChanged: estimateGas()
}
}
}

View File

@ -0,0 +1,216 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import utils 1.0
import shared.stores 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import "../panels"
import "../controls"
import "../views"
Item {
id: root
clip: true
height: accountSelectionTabBar.height + stackLayout.height + Style.current.xlPadding
property var store
signal contactSelected(string address, int type)
TabBar {
id: accountSelectionTabBar
anchors.top: parent.top
anchors.topMargin: 20
width: parent.width
height: assetBtn.height
background: Rectangle {
color: Style.current.transparent
}
StatusTabButton {
id: assetBtn
//% "Saved"
btnText: qsTr("Saved")
}
StatusTabButton {
id: collectiblesBtn
anchors.left: assetBtn.right
anchors.leftMargin: 32
//% "My Accounts"
btnText: qsTr("My Accounts")
}
StatusTabButton {
id: historyBtn
anchors.left: collectiblesBtn.right
anchors.leftMargin: 32
//% "Recent"
btnText: qsTr("Recent")
}
}
StackLayout {
id: stackLayout
anchors.top: accountSelectionTabBar.bottom
height: stackLayout.childrenRect.height
width: parent.width
currentIndex: accountSelectionTabBar.currentIndex
// To-do adapt to new design and make block white/balck once the list items etc support new color scheme
Rectangle {
Layout.fillWidth: true
height: savedAddresses.height
color: "transparent"
radius: 8
ListView {
id: savedAddresses
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: parent.width
height: Math.min(288, savedAddresses.contentHeight)
model: root.store.savedAddressesModel
clip: true
header: savedAddresses.count > 0 ? search : nothingInList
headerPositioning: ListView.OverlayHeader
boundsBehavior: Flickable.StopAtBounds
delegate: StatusListItem {
id: savedAddress
width: visible ? parent.width: 0
height: visible ? 64 : 0
title: name
subTitle: address
radius: 0
visible: !savedAddresses.headerItem.text || name.toLowerCase().includes(savedAddresses.headerItem.text)
components: [
StatusIcon {
icon: "star-icon"
width: 12
height: 12
}
]
onClicked: contactSelected(address, RecipientSelector.Type.Address )
}
Component {
id: search
StatusBaseInput {
width: parent.width
height: 56
placeholderText: qsTr("Search for saved address")
rightComponent: StatusIcon {
icon: "search"
height: 17
color: Theme.palette.baseColor1
}
}
}
Component {
id: nothingInList
StatusBaseText {
font.pixelSize: 15
color: Theme.palette.directColor1
//% "No Saved Address"
text: qsTr("No Saved Address")
}
}
}
}
Rectangle {
Layout.fillWidth: true
height: myAccounts.height
color: "transparent"
radius: 8
ListView {
id: myAccounts
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: parent.width
height: Math.min(288, myAccounts.contentHeight)
boundsBehavior: Flickable.StopAtBounds
clip: true
delegate: StatusListItem {
width: visible ? parent.width: 0
height: visible ? 64 : 0
title: model.name
subTitle: Utils.toLocaleString(model.currencyBalance.toFixed(2), popup.store.locale, {"model.currency": true}) + " " + popup.store.currentCurrency.toUpperCase()
icon.emoji: !!model.emoji ? model.emoji: ""
icon.color: model.color
icon.name: !model.emoji ? "filled-account": ""
icon.letterSize: 14
icon.isLetterIdenticon: !!model.emoji ? true : false
icon.background.color: Theme.palette.indirectColor1
radius: 0
onClicked: contactSelected(model.address, RecipientSelector.Type.Account )
}
model: root.store.accounts
}
}
Rectangle {
Layout.fillWidth: true
height: recents.height
color: "transparent"
radius: 8
ListView {
id: recents
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: parent.top
width: parent.width
height: Math.min(288, recents.contentHeight)
boundsBehavior: Flickable.StopAtBounds
clip: true
header: StatusBaseText {
font.pixelSize: 15
color: Theme.palette.directColor1
//% "No Recents"
text: qsTr("No Recents")
visible: recents.count <= 0
}
delegate: StatusListItem {
property bool isIncoming: to === popup.store.currentAccount.address
width: visible ? parent.width: 0
height: visible ? 64 : 0
title: isIncoming ? from : to
subTitle: Utils.getTimeDifference(new Date(parseInt(timestamp) * 1000), new Date())
statusListItemTitle.elide: Text.ElideMiddle
statusListItemTitle.wrapMode: Text.NoWrap
radius: 0
components: [
StatusIcon {
id: transferIcon
height: 15
width: 15
color: isIncoming ? Style.current.success : Style.current.danger
icon: isIncoming ? "down" : "up"
rotation: 45
},
StatusBaseText {
id: contactsLabel
font.pixelSize: 15
color: Theme.palette.directColor1
text: popup.store.hex2Eth(value)
}
]
onClicked: contactSelected(title, RecipientSelector.Type.Address)
}
model: root.store.walletSectionTransactionsInst.model
}
}
}
}

View File

@ -0,0 +1,208 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import utils 1.0
import shared.stores 1.0
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import "../panels"
import "../controls"
import "../views"
Item {
id: root
height: visible ? stackLayout.height + modeSelectionTabBar.height + 2*Style.current.xlPadding: 0
signal contactSelected(string address, int type)
StatusSwitchTabBar {
id: modeSelectionTabBar
anchors.horizontalCenter: parent.horizontalCenter
StatusSwitchTabButton {
//% "Simple"
text: qsTr("Simple")
}
StatusSwitchTabButton {
//% "Advanced"
text: qsTr("Advanced")
}
StatusSwitchTabButton {
//% "Custom"
text: qsTr("Custom")
}
}
StackLayout {
id: stackLayout
anchors.top: modeSelectionTabBar.bottom
anchors.topMargin: Style.current.xlPadding
height: simpleLayout.height
width: parent.width
currentIndex: modeSelectionTabBar.currentIndex
ColumnLayout {
id: simpleLayout
Layout.fillWidth: true
spacing: 24
// To-do networks depends on multi networks and fee suggestions not available yet
Rectangle {
id: networksRect
radius: 13
color: Theme.palette.indirectColor1
Layout.fillWidth: true
Layout.preferredHeight: layout.height + 24
ColumnLayout {
id: layout
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 16
spacing: 20
RowLayout {
spacing: 10
StatusRoundIcon {
Layout.alignment: Qt.AlignTop
radius: 8
icon.name: "flash"
}
ColumnLayout {
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
font.weight: Font.Medium
color: Theme.palette.directColor1
//% "Networks"
text: qsTr("Networks")
wrapMode: Text.WordWrap
}
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
color: Theme.palette.baseColor1
//% "The networks where the receipient will receive tokens. Amounts calculated automatically for the lowest cost."
text: qsTr("The networks where the receipient will receive tokens. Amounts calculated automatically for the lowest cost.")
wrapMode: Text.WordWrap
}
}
}
RowLayout {
spacing: 16
Layout.leftMargin: 40
Repeater {
model: 3
StatusListItem {
implicitWidth: 126
title: "PlaceHolder" + index
subTitle: ""
icon.isLetterIdenticon: true
icon.width: 32
icon.height: 32
leftPadding: 0
rightPadding: 0
color: "transparent"
}
}
}
}
}
Rectangle {
id: feesRect
radius: 13
color: Theme.palette.indirectColor1
Layout.fillWidth: true
Layout.preferredHeight: feesLayout.height + 32
RowLayout {
id: feesLayout
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 16
spacing: 10
StatusRoundIcon {
Layout.alignment: Qt.AlignTop
radius: 8
icon.name: "fees"
}
ColumnLayout {
spacing: 12
StatusBaseText {
Layout.maximumWidth: 410
font.pixelSize: 15
font.weight: Font.Medium
color: Theme.palette.directColor1
//% "Fees"
text: qsTr("Fees")
wrapMode: Text.WordWrap
}
RowLayout {
spacing: 16
Repeater {
model: 3
delegate:
RadioDelegate {
id: control
checked: index === 0
contentItem.visible: false
indicator.visible: false
background: Rectangle {
implicitWidth: 128
implicitHeight: 78
radius: 8
color: control.checked ? Theme.palette.indirectColor1: Theme.palette.baseColor4
border.width: control.checked
border.color: Theme.palette.primaryColor2
ColumnLayout {
width: parent.width
anchors.top: parent.top
anchors.left: parent.left
anchors.margins: 8
StatusBaseText {
font.pixelSize: 15
color: Theme.palette.baseColor1
//% "Slow"
text: qsTr("Slow")
wrapMode: Text.WordWrap
}
StatusBaseText {
font.pixelSize: 13
color: Theme.palette.baseColor1
text: "0.24 USD"
wrapMode: Text.WordWrap
}
StatusBaseText {
font.pixelSize: 13
color: Theme.palette.baseColor1
text: "~15 minutes"
wrapMode: Text.WordWrap
}
}
}
}
}
}
}
}
}
}
StatusBaseText {
Layout.alignment: Qt.AlignCenter
font.pixelSize: 15
color: Theme.palette.baseColor1
text: "Not Implemented"
}
StatusBaseText {
Layout.alignment: Qt.AlignCenter
font.pixelSize: 15
color: Theme.palette.baseColor1
text: "Not Implemented"
}
}
}

View File

@ -620,6 +620,83 @@ QtObject {
return JSON.parse(jsonObj) return JSON.parse(jsonObj)
} }
function getTimeDifference(d1, d2) {
var timeString = ""
var day1Year = d1.getFullYear()
var day1Month = d1.getMonth()
var day1Time = d1.getTime()
var day2Year = d2.getFullYear()
var day2Month = d2.getMonth()
var day2Time = d2.getTime()
var inYears = day2Year-day1Year
if(inYears > 0) {
//% "years ago"
//% "year ago"
timeString = inYears > 1 ? qsTr("years ago") : qsTr("year ago")
return inYears + " " + timeString
}
var inMonths = (day2Month+12*day2Year)-(day1Month+12*day1Year)
if(inMonths > 0) {
//% "months ago"
//% "month ago"
timeString = inMonths > 1 ? qsTr("months ago") : qsTr("month ago")
return inMonths + " " + timeString
}
var inWeeks = parseInt((day2Time-day2Time)/(24*3600*1000*7))
if(inWeeks > 0) {
//% "weeks ago"
//% "week ago"
timeString = inWeeks > 1 ? qsTr("weeks ago") : qsTr("week ago")
return inWeeks + " " + timeString
}
var inDays = parseInt((day2Time-day1Time)/(24*3600*1000))
if(inDays > 0) {
//% "days ago"
//% "day ago"
timeString = inDays > 1 ? qsTr("days ago") : qsTr("day ago")
return inDays + " " + timeString
}
var inHours = parseInt((day2Time-day1Time)/(3600*1000));
if(inHours > 0) {
//% "hours ago"
//% "hour ago"
timeString = inHours > 1 ? qsTr("hours ago") : qsTr("hour ago")
return inHours + " " + timeString
}
var inMins = parseInt((day2Time-day1Time)/(60*1000))
if(inMins > 0) {
//% "mins ago"
//% "min ago"
timeString = inMins > 1 ? qsTr("mins ago") : qsTr("min ago")
return inMins + " " + timeString
}
var inSecs = parseInt((day2Time-day1Time)/(1000));
if(inSecs > 0) {
//% "secs ago"
//% "sec ago"
timeString = inSecs > 1 ? qsTr("secs ago") : qsTr("sec ago")
return inSecs + " " + timeString
}
//% "now"
return qsTr("now")
}
// Leave this function at the bottom of the file as QT Creator messes up the code color after this // Leave this function at the bottom of the file as QT Creator messes up the code color after this
function isPunct(c) { function isPunct(c) {
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|_|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c) return /(!|\@|#|\$|%|\^|&|\*|\(|\)|_|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)