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
SendModal {
id: sendTransactionWithEns
anchors.centerIn: parent
store: root.rootStore
contactsStore: root.contactsStore
onOpened: {
@ -390,9 +391,8 @@ Item {
onClosed: {
destroy()
}
isContact: root.isContact
selectRecipient.readOnly: true
selectRecipient.selectedRecipient: {
launchedFromChat: true
preSelectedRecipient: {
parentModule.prepareChatContentModuleForChatId(activeChatId)
let chatContentModule = parentModule.getChatContentModule()
@ -405,7 +405,6 @@ Item {
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.Controls 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0

View File

@ -53,6 +53,14 @@ QtObject {
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) {
communitiesModuleInst.createCommunity(communityName, communityDescription, checkedMembership, ensOnlySwitchChecked, communityColor, communityImage, imageCropperModalaX, imageCropperModalaY, imageCropperModalbX, imageCropperModalbY);
}
@ -104,4 +112,8 @@ QtObject {
function suggestedFees() {
return JSON.parse(walletSectionTransactions.suggestedFees())
}
function hex2Eth(value) {
return globalUtils.hex2Eth(value)
}
}

View File

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

View File

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

View File

@ -2,32 +2,32 @@ import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.13
import QtQuick.Dialogs 1.3
import QtGraphicalEffects 1.0
import StatusQ.Controls.Validators 0.1
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"
import "."
// TODO: replace with StatusModal
ModalPopup {
id: root
property var contactsStore
StatusModal {
id: popup
property alias selectFromAccount: selectFromAccount
property alias selectRecipient: selectRecipient
property alias stack: stack
property var store
property bool isContact: false
//% "Send"
title: qsTrId("command-button-send")
height: 540
property var contactsStore
property var preSelectedAccount
property var preSelectedRecipient
property bool launchedFromChat: false
property MessageDialog sendingError: MessageDialog {
id: sendingError
//% "Error sending the transaction"
@ -39,11 +39,11 @@ ModalPopup {
function sendTransaction() {
stack.currentGroup.isPending = true
let success = false
if(txtAmount.selectedAsset.address === "" || txtAmount.selectedAsset.address === Constants.zeroAddress){
success = root.store.transferEth(
selectFromAccount.selectedAccount.address,
selectRecipient.selectedRecipient.address,
txtAmount.selectedAmount,
if(advancedHeader.assetSelector.selectedAsset.address === "" || advancedHeader.assetSelector.selectedAsset.address === Constants.zeroAddress){
success = popup.store.transferEth(
advancedHeader.accountSelector.selectedAccount.address,
advancedHeader.recipientSelector.selectedRecipient.address,
advancedHeader.amountToSendInput.text,
gasSelector.selectedGasLimit,
gasSelector.isEIP1559Enabled ? "" : gasSelector.selectedGasPrice,
gasSelector.selectedTipLimit,
@ -51,11 +51,11 @@ ModalPopup {
transactionSigner.enteredPassword,
stack.uuid)
} else {
success = root.store.transferTokens(
selectFromAccount.selectedAccount.address,
selectRecipient.selectedRecipient.address,
txtAmount.selectedAsset.address,
txtAmount.selectedAmount,
success = popup.store.transferTokens(
advancedHeader.accountSelector.selectedAccount.address,
advancedHeader.recipientSelector.selectedRecipient.address,
advancedHeader.assetSelector.selectedAsset.address,
advancedHeader.amountToSendInput.text,
gasSelector.selectedGasLimit,
gasSelector.isEIP1559Enabled ? "" : gasSelector.selectedGasPrice,
gasSelector.selectedTipLimit,
@ -63,291 +63,246 @@ ModalPopup {
transactionSigner.enteredPassword,
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 {
id: stack
anchors.fill: parent
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
onGroupActivated: {
root.title = group.headerText
btnNext.text = group.footerText
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
property alias currentGroup: stack.currentGroup
anchors.leftMargin: Style.current.xlPadding
anchors.topMargin: (!!advancedHeader ? advancedHeader.height: 0) + Style.current.smallPadding
anchors.rightMargin: Style.current.xlPadding
anchors.bottomMargin: popup.showAdvancedFooter && !!advancedFooter ? advancedFooter.height : Style.current.padding
TransactionFormGroup {
id: group1
//% "Send"
headerText: qsTrId("command-button-send")
//% "Continue"
footerText: qsTrId("continue")
anchors.fill: parent
ScrollView {
height: stack.height
width: parent.width
anchors.top: parent.top
anchors.left: parent.left
StatusAccountSelector {
id: selectFromAccount
accounts: root.store.accounts
selectedAccount: {
const currAcc = root.store.currentAccount
if (currAcc.walletType !== Constants.watchWalletType) {
return currAcc
ScrollBar.vertical.policy: ScrollBar.AlwaysOff
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
contentHeight: addressSelector.height + networkAndFeesSelector.height + gasSelector.height + gasValidator.height
clip: true
TabAddressSelectorView {
id: addressSelector
anchors.top: parent.top
anchors.right: parent.right
anchors.left: parent.left
store: popup.store
onContactSelected: {
if(!!popup.advancedHeader)
advancedHeader.recipientSelector.input.text = address
}
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 {
id: txtAmount
selectedAccount: selectFromAccount.selectedAccount
currentCurrency: root.store.currentCurrency
// TODO make those use a debounce
getFiatValue: root.store.getFiatValue
// getCryptoValue: RootStore.cryptoValue
width: stack.width
onSelectedAssetChanged: if (isValid) { gasSelector.estimateGas() }
onSelectedAmountChanged: if (isValid) { gasSelector.estimateGas() }
}
GasSelector {
id: gasSelector
anchors.top: txtAmount.bottom
anchors.topMargin: Style.current.padding
gasPrice: parseFloat(root.store.gasPrice)
getGasEthValue: root.store.getGasEthValue
getFiatValue: root.store.getFiatValue
defaultCurrency: root.store.currentCurrency
isEIP1559Enabled: root.store.isEIP1559Enabled()
latestBaseFeePerGas: root.store.latestBaseFeePerGas()
suggestedFees: root.store.suggestedFees()
TabNetworkAndFees {
id: networkAndFeesSelector
anchors.top: addressSelector.bottom
anchors.right: parent.right
anchors.left: parent.left
visible: popup.store.isMultiNetworkEnabled
}
width: stack.width
property var estimateGas: Backpressure.debounce(gasSelector, 600, function() {
if (!(selectFromAccount.selectedAccount && selectFromAccount.selectedAccount.address &&
selectRecipient.selectedRecipient && selectRecipient.selectedRecipient.address &&
txtAmount.selectedAsset && txtAmount.selectedAsset.address &&
txtAmount.selectedAmount)) {
selectedGasLimit = 250000
GasSelector {
id: gasSelector
anchors.top: networkAndFeesSelector.visible ? networkAndFeesSelector.bottom : addressSelector.bottom
gasPrice: parseFloat(popup.store.gasPrice)
getGasEthValue: popup.store.getGasEthValue
getFiatValue: popup.store.getFiatValue
defaultCurrency: popup.store.currentCurrency
isEIP1559Enabled: popup.store.isEIP1559Enabled()
latestBaseFeePerGas: popup.store.latestBaseFeePerGas()
suggestedFees: popup.store.suggestedFees()
visible: !popup.store.isMultiNetworkEnabled
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.address &&
advancedHeader.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.address,
advancedHeader.amountToSendInput.text,
""))
if (!gasEstimate.success) {
//% "Error estimating gas: %1"
console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message))
return
}
selectedGasLimit = gasEstimate.result
defaultGasLimit = selectedGasLimit
return
}
let gasEstimate = JSON.parse(root.store.estimateGas(
selectFromAccount.selectedAccount.address,
selectRecipient.selectedRecipient.address,
txtAmount.selectedAsset.address,
txtAmount.selectedAmount,
""))
if (!gasEstimate.success) {
//% "Error estimating gas: %1"
console.warn(qsTrId("error-estimating-gas---1").arg(gasEstimate.error.message))
return
}
selectedGasLimit = gasEstimate.result
defaultGasLimit = selectedGasLimit
})
}
GasValidator {
id: gasValidator
anchors.top: gasSelector.bottom
selectedAccount: selectFromAccount.selectedAccount
selectedAmount: parseFloat(txtAmount.selectedAmount)
selectedAsset: txtAmount.selectedAsset
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
})
}
GasValidator {
id: gasValidator
anchors.top: gasSelector.bottom
selectedAccount: advancedHeader.accountSelector.selectedAccount
selectedAmount: parseFloat(advancedHeader.amountToSendInput.text)
selectedAsset: advancedHeader.assetSelector.selectedAsset
selectedGasEthValue: gasSelector.selectedGasEthValue
}
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 {
id: group4
//% "Sign with password"
headerText: qsTrId("sign-with-password")
//% "Send %1 %2"
footerText: qsTrId("send--1--2").arg(txtAmount.selectedAmount).arg(!!txtAmount.selectedAsset ? txtAmount.selectedAsset.symbol : "")
StackView.onActivated: {
transactionSigner.forceActiveFocus(Qt.MouseFocusReason)
}
TransactionSigner {
id: transactionSigner
Layout.topMargin: Style.current.smallPadding
width: stack.width
signingPhrase: root.store.signingPhrase
signingPhrase: popup.store.signingPhrase
}
}
}
footer: Item {
width: parent.width
height: btnNext.height
StatusRoundButton {
id: btnBack
anchors.left: parent.left
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 (stack.isLastGroup) {
return root.sendTransaction()
}
if(gasSelector.isEIP1559Enabled && stack.currentGroup === group2 && gasSelector.advancedMode){
if(gasSelector.showPriceLimitWarning || gasSelector.showTipLimitWarning){
Global.openPopup(transactionSettingsConfirmationPopupComponent, {
currentBaseFee: gasSelector.latestBaseFeePerGasGwei,
currentMinimumTip: gasSelector.perGasTipLimitFloor,
currentAverageTip: gasSelector.perGasTipLimitAverage,
tipLimit: gasSelector.selectedTipLimit,
suggestedTipLimit: gasSelector.perGasTipLimitFloor,
priceLimit: gasSelector.selectedOverallLimit,
suggestedPriceLimit: gasSelector.latestBaseFeePerGasGwei + gasSelector.perGasTipLimitFloor,
showPriceLimitWarning: gasSelector.showPriceLimitWarning,
showTipLimitWarning: gasSelector.showTipLimitWarning,
onConfirm: function(){
stack.next();
}
})
return
}
}
stack.next()
advancedFooterComponent: SendModalFooter {
maxFiatFees: gasSelector.maxFiatFees
currentGroupPending: popup.contentItem.currentGroup.isPending
currentGroupValid: popup.contentItem.currentGroup.isValid
isLastGroup: popup.contentItem.isLastGroup
onNextButtonClicked: {
const validity = popup.contentItem.currentGroup.validate()
if (validity.isValid && !validity.isPending) {
if (popup.contentItem.isLastGroup) {
return popup.sendTransaction()
}
if(gasSelector.isEIP1559Enabled && popup.contentItem.currentGroup === group1 && gasSelector.advancedMode){
if(gasSelector.showPriceLimitWarning || gasSelector.showTipLimitWarning){
Global.openPopup(transactionSettingsConfirmationPopupComponent, {
currentBaseFee: gasSelector.latestBaseFeePerGasGwei,
currentMinimumTip: gasSelector.perGasTipLimitFloor,
currentAverageTip: gasSelector.perGasTipLimitAverage,
tipLimit: gasSelector.selectedTipLimit,
suggestedTipLimit: gasSelector.perGasTipLimitFloor,
priceLimit: gasSelector.selectedOverallLimit,
suggestedPriceLimit: gasSelector.latestBaseFeePerGasGwei + gasSelector.perGasTipLimitFloor,
showPriceLimitWarning: gasSelector.showPriceLimitWarning,
showTipLimitWarning: gasSelector.showTipLimitWarning,
onConfirm: function(){
popup.contentItem.next();
}
})
return
}
}
popup.contentItem.next()
}
}
}
Component {
id: transactionSettingsConfirmationPopupComponent
TransactionSettingsConfirmationPopup {}
}
Connections {
target: advancedHeader
onIsReadyChanged: {
if(!advancedHeader.isReady && popup.contentItem.isLastGroup)
popup.contentItem.back()
}
}
Connections {
target: popup.store.walletSectionTransactionsInst
onTransactionSent: {
try {
let response = JSON.parse(txResult)
if (response.uuid !== stack.uuid) return
stack.currentGroup.isPending = false
if (!response.success) {
if (Utils.isInvalidPasswordMessage(response.result)){
//% "Wrong password"
transactionSigner.validationError = qsTrId("wrong-password")
return
}
sendingError.text = response.result
return sendingError.open()
}
// % "Transaction pending..."
Global.toastMessage.title = qsTrId("ens-transaction-pending")
Global.toastMessage.source = Style.svg("loading")
Global.toastMessage.iconColor = Style.current.primary
Global.toastMessage.iconRotates = true
Global.toastMessage.link = `${popup.store.getEtherscanLink()}/${response.result}`
Global.toastMessage.open()
popup.close()
} catch (e) {
console.error('Error parsing the response', e)
}
}
// Not Refactored Yet
Connections {
target: root.store.walletSectionTransactionsInst
onTransactionSent: {
try {
let response = JSON.parse(txResult)
if (response.uuid !== stack.uuid) return
stack.currentGroup.isPending = false
if (!response.success) {
if (Utils.isInvalidPasswordMessage(response.result)){
//% "Wrong password"
transactionSigner.validationError = qsTrId("wrong-password")
return
}
sendingError.text = response.result
return sendingError.open()
}
// % "Transaction pending..."
Global.toastMessage.title = qsTrId("ens-transaction-pending")
Global.toastMessage.source = Style.svg("loading")
Global.toastMessage.iconColor = Style.current.primary
Global.toastMessage.iconRotates = true
// Refactor this
// Global.toastMessage.link = `${walletModel.utilsView.etherscanLink}/${response.result}`
Global.toastMessage.open()
root.close()
} catch (e) {
console.error('Error parsing the response', e)
}
}
// onTransactionCompleted: {
// if (success) {
// //% "Transaction completed"
// Global.toastMessage.title = qsTrId("transaction-completed")
// Global.toastMessage.source = Style.svg("check-circle")
// Global.toastMessage.iconColor = Style.current.success
// } else {
// //% "Transaction failed"
// Global.toastMessage.title = qsTrId("ens-registration-failed-title")
// Global.toastMessage.source = Style.svg("block-icon")
// Global.toastMessage.iconColor = Style.current.danger
// }
// Global.toastMessage.link = `${walletModel.utilsView.etherscanLink}/${txHash}`
// Global.toastMessage.open()
// }
}
// onTransactionCompleted: {
// if (success) {
// //% "Transaction completed"
// Global.toastMessage.title = qsTrId("transaction-completed")
// Global.toastMessage.source = Style.svg("check-circle")
// Global.toastMessage.iconColor = Style.current.success
// } else {
// //% "Transaction failed"
// Global.toastMessage.title = qsTrId("ens-registration-failed-title")
// Global.toastMessage.source = Style.svg("block-icon")
// Global.toastMessage.iconColor = Style.current.danger
// }
// 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)
}
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
function isPunct(c) {
return /(!|\@|#|\$|%|\^|&|\*|\(|\)|_|\+|\||-|=|\\|{|}|[|]|"|;|'|<|>|\?|,|\.|\/)/.test(c)