feat/tx-comps: Add RecipientSelector component
Based on the spec in https://www.notion.so/emizzle/Wallet-transaction-components-2003b78a8d0d41c4ab3d21eb2496fb20, this component handles user input for a recipient address, which can be sourced from manual address input, ENS name, contact selection, or another of the user's wallet accounts.
This commit is contained in:
parent
9466714d90
commit
d07daac377
|
@ -11,6 +11,8 @@ type
|
||||||
Identicon = UserRole + 4
|
Identicon = UserRole + 4
|
||||||
IsContact = UserRole + 5
|
IsContact = UserRole + 5
|
||||||
IsBlocked = UserRole + 6
|
IsBlocked = UserRole + 6
|
||||||
|
Alias = UserRole + 7
|
||||||
|
EnsVerified = UserRole + 8
|
||||||
|
|
||||||
QtObject:
|
QtObject:
|
||||||
type ContactList* = ref object of QAbstractListModel
|
type ContactList* = ref object of QAbstractListModel
|
||||||
|
@ -48,6 +50,8 @@ QtObject:
|
||||||
of "pubKey": result = contact.id
|
of "pubKey": result = contact.id
|
||||||
of "isContact": result = $contact.isContact()
|
of "isContact": result = $contact.isContact()
|
||||||
of "isBlocked": result = $contact.isBlocked()
|
of "isBlocked": result = $contact.isBlocked()
|
||||||
|
of "alias": result = contact.alias
|
||||||
|
of "ensVerified": result = $contact.ensVerified
|
||||||
|
|
||||||
method data(self: ContactList, index: QModelIndex, role: int): QVariant =
|
method data(self: ContactList, index: QModelIndex, role: int): QVariant =
|
||||||
if not index.isValid:
|
if not index.isValid:
|
||||||
|
@ -62,6 +66,8 @@ QtObject:
|
||||||
of ContactRoles.PubKey: result = newQVariant(contact.id)
|
of ContactRoles.PubKey: result = newQVariant(contact.id)
|
||||||
of ContactRoles.IsContact: result = newQVariant(contact.isContact())
|
of ContactRoles.IsContact: result = newQVariant(contact.isContact())
|
||||||
of ContactRoles.IsBlocked: result = newQVariant(contact.isBlocked())
|
of ContactRoles.IsBlocked: result = newQVariant(contact.isBlocked())
|
||||||
|
of ContactRoles.Alias: result = newQVariant(contact.alias)
|
||||||
|
of ContactRoles.EnsVerified: result = newQVariant(contact.ensVerified)
|
||||||
|
|
||||||
method roleNames(self: ContactList): Table[int, string] =
|
method roleNames(self: ContactList): Table[int, string] =
|
||||||
{
|
{
|
||||||
|
@ -70,7 +76,9 @@ QtObject:
|
||||||
ContactRoles.Identicon.int:"identicon",
|
ContactRoles.Identicon.int:"identicon",
|
||||||
ContactRoles.PubKey.int:"pubKey",
|
ContactRoles.PubKey.int:"pubKey",
|
||||||
ContactRoles.IsContact.int:"isContact",
|
ContactRoles.IsContact.int:"isContact",
|
||||||
ContactRoles.IsBlocked.int:"isBlocked"
|
ContactRoles.IsBlocked.int:"isBlocked",
|
||||||
|
ContactRoles.Alias.int:"alias",
|
||||||
|
ContactRoles.EnsVerified.int:"ensVerified"
|
||||||
}.toTable
|
}.toTable
|
||||||
|
|
||||||
proc addContactToList*(self: ContactList, contact: Profile) =
|
proc addContactToList*(self: ContactList, contact: Profile) =
|
||||||
|
|
|
@ -19,7 +19,7 @@ Item {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let result = walletModel.onSendTransaction(selectFromAccount.selectedAccount.address,
|
let result = walletModel.onSendTransaction(selectFromAccount.selectedAccount.address,
|
||||||
txtTo.text,
|
selectRecipient.selectedRecipient,
|
||||||
selectAsset.selectedAsset.address,
|
selectAsset.selectedAsset.address,
|
||||||
txtAmount.text,
|
txtAmount.text,
|
||||||
txtPassword.text)
|
txtPassword.text)
|
||||||
|
@ -35,6 +35,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
function validate() {
|
function validate() {
|
||||||
|
selectRecipient.validate()
|
||||||
if (txtPassword.text === "") {
|
if (txtPassword.text === "") {
|
||||||
//% "You need to enter a password"
|
//% "You need to enter a password"
|
||||||
passwordValidationError = qsTrId("you-need-to-enter-a-password")
|
passwordValidationError = qsTrId("you-need-to-enter-a-password")
|
||||||
|
@ -45,16 +46,6 @@ Item {
|
||||||
passwordValidationError = ""
|
passwordValidationError = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
if (txtTo.text === "") {
|
|
||||||
//% "You need to enter a destination address"
|
|
||||||
toValidationError = qsTrId("you-need-to-enter-a-destination-address")
|
|
||||||
} else if (!Utils.isAddress(txtTo.text)) {
|
|
||||||
//% "This needs to be a valid address (starting with 0x)"
|
|
||||||
toValidationError = qsTrId("this-needs-to-be-a-valid-address-(starting-with-0x)")
|
|
||||||
} else {
|
|
||||||
toValidationError = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (txtAmount.text === "") {
|
if (txtAmount.text === "") {
|
||||||
//% "You need to enter an amount"
|
//% "You need to enter an amount"
|
||||||
amountValidationError = qsTrId("you-need-to-enter-an-amount")
|
amountValidationError = qsTrId("you-need-to-enter-an-amount")
|
||||||
|
@ -114,15 +105,15 @@ Item {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Input {
|
RecipientSelector {
|
||||||
id: txtTo
|
id: selectRecipient
|
||||||
//% "Recipient"
|
accounts: walletModel.accounts
|
||||||
label: qsTrId("recipient")
|
contacts: profileModel.addedContacts
|
||||||
//% "Send to"
|
label: qsTr("Recipient")
|
||||||
placeholderText: qsTrId("send-to")
|
|
||||||
anchors.top: selectFromAccount.bottom
|
anchors.top: selectFromAccount.bottom
|
||||||
anchors.topMargin: Style.current.padding
|
anchors.topMargin: Style.current.padding
|
||||||
validationError: toValidationError
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
}
|
}
|
||||||
|
|
||||||
Input {
|
Input {
|
||||||
|
@ -131,7 +122,7 @@ Item {
|
||||||
label: qsTrId("password")
|
label: qsTrId("password")
|
||||||
//% "Enter Password"
|
//% "Enter Password"
|
||||||
placeholderText: qsTrId("biometric-auth-login-ios-fallback-label")
|
placeholderText: qsTrId("biometric-auth-login-ios-fallback-label")
|
||||||
anchors.top: txtTo.bottom
|
anchors.top: selectRecipient.bottom
|
||||||
anchors.topMargin: Style.current.padding
|
anchors.topMargin: Style.current.padding
|
||||||
textField.echoMode: TextInput.Password
|
textField.echoMode: TextInput.Password
|
||||||
validationError: passwordValidationError
|
validationError: passwordValidationError
|
||||||
|
|
|
@ -5,6 +5,7 @@ Theme {
|
||||||
property color white: "#FFFFFF"
|
property color white: "#FFFFFF"
|
||||||
property color white2: "#FCFCFC"
|
property color white2: "#FCFCFC"
|
||||||
property color black: "#000000"
|
property color black: "#000000"
|
||||||
|
property color almostBlack: "#141414"
|
||||||
property color grey: "#EEF2F5"
|
property color grey: "#EEF2F5"
|
||||||
property color lightBlue: "#ECEFFC"
|
property color lightBlue: "#ECEFFC"
|
||||||
property color cyan: "#00FFFF"
|
property color cyan: "#00FFFF"
|
||||||
|
@ -18,10 +19,15 @@ Theme {
|
||||||
property color lightRed: "#FFEAEE"
|
property color lightRed: "#FFEAEE"
|
||||||
property color green: "#4EBC60"
|
property color green: "#4EBC60"
|
||||||
property color turquoise: "#007b7d"
|
property color turquoise: "#007b7d"
|
||||||
|
property color tenPercentWhite: Qt.rgba(255, 255, 255, 0.1)
|
||||||
|
property color tenPercentBlue: Qt.rgba(67, 96, 223, 0.1)
|
||||||
|
|
||||||
property color background: "#141414"
|
property color background: almostBlack
|
||||||
property color border: "#252528"
|
property color border: "#252528"
|
||||||
|
property color borderSecondary: tenPercentWhite
|
||||||
|
property color borderTertiary: blue
|
||||||
property color textColor: white
|
property color textColor: white
|
||||||
|
property color textColorTertiary: blue
|
||||||
property color currentUserTextColor: white
|
property color currentUserTextColor: white
|
||||||
property color secondaryBackground: "#23252F"
|
property color secondaryBackground: "#23252F"
|
||||||
property color inputBackground: secondaryBackground
|
property color inputBackground: secondaryBackground
|
||||||
|
@ -29,6 +35,9 @@ Theme {
|
||||||
property color modalBackground: background
|
property color modalBackground: background
|
||||||
property color backgroundHover: "#252528"
|
property color backgroundHover: "#252528"
|
||||||
property color secondaryText: darkGrey
|
property color secondaryText: darkGrey
|
||||||
property color secondaryHover: Qt.rgba(255, 255, 255, 0.1)
|
property color secondaryHover: tenPercentWhite
|
||||||
property color danger: red
|
property color danger: red
|
||||||
|
property color primaryMenuItemHover: blue
|
||||||
|
property color primaryMenuItemTextHover: almostBlack
|
||||||
|
property color backgroundTertiary: tenPercentBlue
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,15 @@ Theme {
|
||||||
property color lightRed: "#FFEAEE"
|
property color lightRed: "#FFEAEE"
|
||||||
property color green: "#4EBC60"
|
property color green: "#4EBC60"
|
||||||
property color turquoise: "#007b7d"
|
property color turquoise: "#007b7d"
|
||||||
|
property color tenPercentBlack: Qt.rgba(0, 0, 0, 0.1)
|
||||||
|
property color tenPercentBlue: Qt.rgba(67, 96, 223, 0.1)
|
||||||
|
|
||||||
property color background: white
|
property color background: white
|
||||||
property color border: grey
|
property color border: grey
|
||||||
|
property color borderSecondary: tenPercentBlack
|
||||||
|
property color borderTertiary: blue
|
||||||
property color textColor: black
|
property color textColor: black
|
||||||
|
property color textColorTertiary: blue
|
||||||
property color currentUserTextColor: white
|
property color currentUserTextColor: white
|
||||||
property color secondaryBackground: lightBlue
|
property color secondaryBackground: lightBlue
|
||||||
property color inputBackground: grey
|
property color inputBackground: grey
|
||||||
|
@ -29,6 +34,9 @@ Theme {
|
||||||
property color modalBackground: white2
|
property color modalBackground: white2
|
||||||
property color backgroundHover: grey
|
property color backgroundHover: grey
|
||||||
property color secondaryText: darkGrey
|
property color secondaryText: darkGrey
|
||||||
property color secondaryHover: Qt.rgba(0, 0, 0, 0.1)
|
property color secondaryHover: tenPercentBlack
|
||||||
property color danger: red
|
property color danger: red
|
||||||
|
property color primaryMenuItemHover: blue
|
||||||
|
property color primaryMenuItemTextHover: white
|
||||||
|
property color backgroundTertiary: tenPercentBlue
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ Item {
|
||||||
// NOTE: if this asset is not selected as a wallet token in the UI, then
|
// NOTE: if this asset is not selected as a wallet token in the UI, then
|
||||||
// nothing will be displayed
|
// nothing will be displayed
|
||||||
property string showAssetBalance: ""
|
property string showAssetBalance: ""
|
||||||
|
property int dropdownWidth: width
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
visible: showAssetBalance !== ""
|
visible: showAssetBalance !== ""
|
||||||
|
@ -36,7 +37,7 @@ Item {
|
||||||
id: select
|
id: select
|
||||||
label: root.label
|
label: root.label
|
||||||
model: root.accounts
|
model: root.accounts
|
||||||
|
menuAlignment: Select.MenuAlignment.Left
|
||||||
menu.delegate: menuItem
|
menu.delegate: menuItem
|
||||||
menu.onOpened: {
|
menu.onOpened: {
|
||||||
selectedAccountDetails.visible = false
|
selectedAccountDetails.visible = false
|
||||||
|
@ -44,6 +45,7 @@ Item {
|
||||||
menu.onClosed: {
|
menu.onClosed: {
|
||||||
selectedAccountDetails.visible = true
|
selectedAccountDetails.visible = true
|
||||||
}
|
}
|
||||||
|
menu.width: dropdownWidth
|
||||||
selectedItemView: Item {
|
selectedItemView: Item {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
|
@ -85,18 +87,11 @@ Item {
|
||||||
|
|
||||||
StyledText {
|
StyledText {
|
||||||
id: textSelectedAddress
|
id: textSelectedAddress
|
||||||
text: selectedAccount.address
|
text: selectedAccount.address + " • "
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
elide: Text.ElideMiddle
|
elide: Text.ElideMiddle
|
||||||
height: 16
|
height: 16
|
||||||
width: 85
|
width: 90
|
||||||
color: Style.current.secondaryText
|
|
||||||
}
|
|
||||||
StyledText {
|
|
||||||
id: separator
|
|
||||||
text: "• "
|
|
||||||
font.pixelSize: 12
|
|
||||||
height: 16
|
|
||||||
color: Style.current.secondaryText
|
color: Style.current.secondaryText
|
||||||
}
|
}
|
||||||
StyledText {
|
StyledText {
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Controls 2.13
|
||||||
|
import QtQuick.Layouts 1.13
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
import "../imports"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property string validationError: "Error"
|
||||||
|
property alias label: inpAddress.label
|
||||||
|
property string selectedAddress
|
||||||
|
|
||||||
|
height: inpAddress.height
|
||||||
|
|
||||||
|
function isValidAddress(inputValue) {
|
||||||
|
return /0x[a-fA-F0-9]{40}/.test(inputValue)
|
||||||
|
}
|
||||||
|
function isValidEns(inputValue) {
|
||||||
|
// TODO: Check if the entered value resolves to an address. Long operation.
|
||||||
|
// Issue tracked: https://github.com/status-im/nim-status-client/issues/718
|
||||||
|
const isEmail = /(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])/.test(inputValue)
|
||||||
|
const isDomain = /(?:(?:(?<thld>[\w\-]*)(?:\.))?(?<sld>[\w\-]*))\.(?<tld>[\w\-]*)/.test(inputValue)
|
||||||
|
return isEmail || isDomain
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate(inputValue) {
|
||||||
|
if (!inputValue) inputValue = selectedAddress
|
||||||
|
let isValid =
|
||||||
|
(inputValue && inputValue.startsWith("0x") && isValidAddress(inputValue)) ||
|
||||||
|
isValidEns(inputValue)
|
||||||
|
inpAddress.validationError = isValid ? "" : validationError
|
||||||
|
return isValid
|
||||||
|
}
|
||||||
|
|
||||||
|
Input {
|
||||||
|
id: inpAddress
|
||||||
|
placeholderText: qsTr("eg. 0x1234 or ENS")
|
||||||
|
customHeight: 56
|
||||||
|
validationErrorAlignment: TextEdit.AlignRight
|
||||||
|
validationErrorTopMargin: 8
|
||||||
|
textField.onFocusChanged: {
|
||||||
|
let isValid = true
|
||||||
|
if (text !== "") {
|
||||||
|
isValid = root.validate(metrics.text)
|
||||||
|
}
|
||||||
|
if (!isValid) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (textField.focus) {
|
||||||
|
text = metrics.text
|
||||||
|
} else if (root.isValidAddress(metrics.text)) {
|
||||||
|
text = metrics.elidedText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
textField.rightPadding: 73
|
||||||
|
onTextEdited: {
|
||||||
|
metrics.text = text
|
||||||
|
const isValid = root.validate(inputValue)
|
||||||
|
if (isValid) {
|
||||||
|
root.selectedAddress = inputValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TextMetrics {
|
||||||
|
id: metrics
|
||||||
|
elideWidth: 97
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
}
|
||||||
|
TertiaryButton {
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 14
|
||||||
|
label: qsTr("Paste")
|
||||||
|
onClicked: {
|
||||||
|
if (inpAddress.textField.canPaste) {
|
||||||
|
inpAddress.textField.paste()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*##^##
|
||||||
|
Designer {
|
||||||
|
D{i:0;autoSize:true;height:480;width:640}
|
||||||
|
}
|
||||||
|
##^##*/
|
|
@ -0,0 +1,67 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Controls 2.13
|
||||||
|
import QtQuick.Layouts 1.13
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
import "../imports"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property var sources: []
|
||||||
|
property string selectedSource: sources[0] || "Address"
|
||||||
|
property int dropdownWidth: 220
|
||||||
|
height: select.height
|
||||||
|
|
||||||
|
Select {
|
||||||
|
id: select
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
model: root.sources
|
||||||
|
selectedItemView: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
StyledText {
|
||||||
|
id: selectedTextField
|
||||||
|
text: root.selectedSource
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Style.current.padding
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
font.pixelSize: 15
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
height: 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
menu.width: dropdownWidth
|
||||||
|
menu.topPadding: 8
|
||||||
|
menu.bottomPadding: 8
|
||||||
|
menu.delegate: Component {
|
||||||
|
MenuItem {
|
||||||
|
id: menuItem
|
||||||
|
height: 40
|
||||||
|
width: parent.width
|
||||||
|
onTriggered: function () {
|
||||||
|
root.selectedSource = root.sources[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: itemText
|
||||||
|
text: root.sources[index]
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Style.current.padding
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
font.pixelSize: 15
|
||||||
|
height: 22
|
||||||
|
color: menuItem.highlighted ? Style.current.primaryMenuItemTextHover : Style.current.textColor
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
color: menuItem.highlighted ? Style.current.primaryMenuItemHover : Style.current.transparent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*##^##
|
||||||
|
Designer {
|
||||||
|
D{i:0;autoSize:true;height:480;width:640}
|
||||||
|
}
|
||||||
|
##^##*/
|
|
@ -0,0 +1,186 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Controls 2.13
|
||||||
|
import QtQuick.Layouts 1.13
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
import "../imports"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property var contacts
|
||||||
|
property var selectedContact
|
||||||
|
height: select.height + (validationErrorText.visible ? validationErrorText.height : 0)
|
||||||
|
property int dropdownWidth: width
|
||||||
|
property string validationError: validationErrorText.text
|
||||||
|
property alias validationErrorAlignment: validationErrorText.horizontalAlignment
|
||||||
|
|
||||||
|
onContactsChanged: {
|
||||||
|
root.selectedContact = { name: qsTr("Select a contact") }
|
||||||
|
}
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
const isValid = root.selectedContact && root.selectedContact.address
|
||||||
|
if (!isValid) {
|
||||||
|
select.select.border.color = Style.current.danger
|
||||||
|
select.select.border.width = 1
|
||||||
|
validationErrorText.visible = true
|
||||||
|
} else {
|
||||||
|
select.select.border.color = Style.current.transparent
|
||||||
|
select.select.border.width = 0
|
||||||
|
validationErrorText.visible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Select {
|
||||||
|
id: select
|
||||||
|
label: ""
|
||||||
|
model: root.contacts
|
||||||
|
width: parent.width
|
||||||
|
menuAlignment: Select.MenuAlignment.Left
|
||||||
|
selectedItemView: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
Identicon {
|
||||||
|
id: iconImg
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 14
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
height: 32
|
||||||
|
width: !!selectedContact.identicon ? 32 : 0
|
||||||
|
visible: !!selectedContact.identicon
|
||||||
|
source: selectedContact.identicon ? selectedContact.identicon : ""
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: selectedTextField
|
||||||
|
text: selectedContact.name
|
||||||
|
anchors.left: iconImg.right
|
||||||
|
anchors.leftMargin: 4
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
font.pixelSize: 15
|
||||||
|
height: 22
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
zeroItemsView: Item {
|
||||||
|
height: 186
|
||||||
|
StyledText {
|
||||||
|
anchors.fill: parent
|
||||||
|
text: qsTr("You don't have any contacts yet")
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
font.pixelSize: 13
|
||||||
|
height: 18
|
||||||
|
color: Style.current.secondaryText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.delegate: menuItem
|
||||||
|
menu.width: dropdownWidth
|
||||||
|
}
|
||||||
|
TextEdit {
|
||||||
|
id: validationErrorText
|
||||||
|
visible: false
|
||||||
|
text: qsTr("Please select a contact")
|
||||||
|
anchors.top: select.bottom
|
||||||
|
anchors.topMargin: 8
|
||||||
|
selectByMouse: true
|
||||||
|
readOnly: true
|
||||||
|
font.pixelSize: 12
|
||||||
|
height: 16
|
||||||
|
color: Style.current.danger
|
||||||
|
width: parent.width
|
||||||
|
horizontalAlignment: TextEdit.AlignRight
|
||||||
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: menuItem
|
||||||
|
MenuItem {
|
||||||
|
id: itemContainer
|
||||||
|
property bool isFirstItem: index === 0
|
||||||
|
property bool isLastItem: index === contacts.rowCount() - 1
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: visible ? 72 : 0
|
||||||
|
Identicon {
|
||||||
|
id: iconImg
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: Style.current.padding
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
width: 40
|
||||||
|
height: 40
|
||||||
|
source: identicon
|
||||||
|
}
|
||||||
|
Column {
|
||||||
|
anchors.left: iconImg.right
|
||||||
|
anchors.leftMargin: 12
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: name
|
||||||
|
font.pixelSize: 15
|
||||||
|
font.family: Style.current.fontBold.name
|
||||||
|
font.bold: true
|
||||||
|
color: Style.current.textColor
|
||||||
|
height: 22
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
StyledText {
|
||||||
|
text: alias + " • "
|
||||||
|
visible: ensVerified
|
||||||
|
color: Style.current.secondaryText
|
||||||
|
font.pixelSize: 12
|
||||||
|
height: 16
|
||||||
|
}
|
||||||
|
StyledText {
|
||||||
|
text: address
|
||||||
|
width: 85
|
||||||
|
elide: Text.ElideMiddle
|
||||||
|
color: Style.current.secondaryText
|
||||||
|
font.pixelSize: 12
|
||||||
|
height: 16
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
background: Rectangle {
|
||||||
|
color: itemContainer.highlighted ? Style.current.backgroundHover : Style.current.background
|
||||||
|
radius: Style.current.radius
|
||||||
|
|
||||||
|
// cover bottom left/right corners with square corners
|
||||||
|
Rectangle {
|
||||||
|
visible: !isLastItem
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
height: parent.radius
|
||||||
|
color: parent.color
|
||||||
|
}
|
||||||
|
|
||||||
|
// cover top left/right corners with square corners
|
||||||
|
Rectangle {
|
||||||
|
visible: !isFirstItem
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
height: parent.radius
|
||||||
|
color: parent.color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MouseArea {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
anchors.fill: itemContainer
|
||||||
|
onClicked: {
|
||||||
|
root.selectedContact = { address, name, alias, isContact, identicon, ensVerified }
|
||||||
|
select.menu.close()
|
||||||
|
validate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*##^##
|
||||||
|
Designer {
|
||||||
|
D{i:0;autoSize:true;height:480;width:640}
|
||||||
|
}
|
||||||
|
##^##*/
|
|
@ -9,7 +9,7 @@ Rectangle {
|
||||||
color: Style.current.background
|
color: Style.current.background
|
||||||
radius: 50
|
radius: 50
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: Style.current.border
|
border.color: Style.current.borderSecondary
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
|
@ -5,8 +5,11 @@ import "../imports"
|
||||||
Item {
|
Item {
|
||||||
property alias textField: inputValue
|
property alias textField: inputValue
|
||||||
property string placeholderText: "My placeholder"
|
property string placeholderText: "My placeholder"
|
||||||
|
property string placeholderTextColor: Style.current.secondaryText
|
||||||
property alias text: inputValue.text
|
property alias text: inputValue.text
|
||||||
property string validationError: ""
|
property string validationError: ""
|
||||||
|
property alias validationErrorAlignment: validationErrorText.horizontalAlignment
|
||||||
|
property int validationErrorTopMargin: 1
|
||||||
property string label: ""
|
property string label: ""
|
||||||
readonly property bool hasLabel: label !== ""
|
readonly property bool hasLabel: label !== ""
|
||||||
property color bgColor: Style.current.inputBackground
|
property color bgColor: Style.current.inputBackground
|
||||||
|
@ -22,9 +25,10 @@ Item {
|
||||||
property int customHeight: 44
|
property int customHeight: 44
|
||||||
property int fontPixelSize: 15
|
property int fontPixelSize: 15
|
||||||
signal editingFinished(string inputValue)
|
signal editingFinished(string inputValue)
|
||||||
|
signal textEdited(string inputValue)
|
||||||
|
|
||||||
id: inputBox
|
id: inputBox
|
||||||
height: inputRectangle.height + (hasLabel ? inputLabel.height + labelMargin : 0) + (!!validationError ? validationErrorText.height : 0)
|
height: inputRectangle.height + (hasLabel ? inputLabel.height + labelMargin : 0) + (!!validationError ? (validationErrorText.height + validationErrorTopMargin) : 0)
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
||||||
|
@ -56,6 +60,7 @@ Item {
|
||||||
id: inputValue
|
id: inputValue
|
||||||
visible: !inputBox.isTextArea && !inputBox.isSelect
|
visible: !inputBox.isTextArea && !inputBox.isSelect
|
||||||
placeholderText: inputBox.placeholderText
|
placeholderText: inputBox.placeholderText
|
||||||
|
placeholderTextColor: inputBox.placeholderTextColor
|
||||||
text: inputBox.text
|
text: inputBox.text
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 0
|
anchors.topMargin: 0
|
||||||
|
@ -72,6 +77,7 @@ Item {
|
||||||
color: Style.current.transparent
|
color: Style.current.transparent
|
||||||
}
|
}
|
||||||
onEditingFinished: inputBox.editingFinished(inputBox.text)
|
onEditingFinished: inputBox.editingFinished(inputBox.text)
|
||||||
|
onTextEdited: inputBox.textEdited(inputBox.text)
|
||||||
}
|
}
|
||||||
|
|
||||||
SVGImage {
|
SVGImage {
|
||||||
|
@ -91,12 +97,13 @@ Item {
|
||||||
id: validationErrorText
|
id: validationErrorText
|
||||||
text: validationError
|
text: validationError
|
||||||
anchors.top: inputRectangle.bottom
|
anchors.top: inputRectangle.bottom
|
||||||
anchors.topMargin: 1
|
anchors.topMargin: validationErrorTopMargin
|
||||||
selectByMouse: true
|
selectByMouse: true
|
||||||
readOnly: true
|
readOnly: true
|
||||||
font.pixelSize: 12
|
font.pixelSize: 12
|
||||||
color: Style.current.red
|
height: 16
|
||||||
|
color: Style.current.danger
|
||||||
|
width: parent.width
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -82,8 +82,8 @@ Menu {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
source: parent
|
source: parent
|
||||||
color: popupMenuItem.highlighted ?
|
color: popupMenuItem.highlighted ?
|
||||||
Style.current.white :
|
Style.current.primaryMenuItemTextHover :
|
||||||
(popupMenuItem.action.icon.color != "#00000000" ? popupMenuItem.action.icon.color : Style.current.blue)
|
(popupMenuItem.action.icon.color != "#00000000" ? popupMenuItem.action.icon.color : Style.current.primaryMenuItemHover)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ Menu {
|
||||||
anchors.leftMargin: popupMenu.paddingSize
|
anchors.leftMargin: popupMenu.paddingSize
|
||||||
text: popupMenuItem.text
|
text: popupMenuItem.text
|
||||||
font: popupMenuItem.font
|
font: popupMenuItem.font
|
||||||
color: popupMenuItem.highlighted ? Style.current.white : popupMenuItem.textColor
|
color: popupMenuItem.highlighted ? Style.current.primaryMenuItemTextHover : popupMenuItem.textColor
|
||||||
horizontalAlignment: Text.AlignLeft
|
horizontalAlignment: Text.AlignLeft
|
||||||
verticalAlignment: Text.AlignVCenter
|
verticalAlignment: Text.AlignVCenter
|
||||||
opacity: enabled ? 1.0 : 0.3
|
opacity: enabled ? 1.0 : 0.3
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Controls 2.13
|
||||||
|
import QtQuick.Layouts 1.13
|
||||||
|
import QtGraphicalEffects 1.13
|
||||||
|
import "../imports"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
property var accounts
|
||||||
|
property var contacts
|
||||||
|
property int inputWidth: 272
|
||||||
|
property int sourceSelectWidth: 136
|
||||||
|
property string label: qsTr("Recipient")
|
||||||
|
property string selectedRecipient: ""
|
||||||
|
height: inpAddress.height + txtLabel.height
|
||||||
|
|
||||||
|
function validate() {
|
||||||
|
if (selAddressSource.selectedSource === "Address") {
|
||||||
|
inpAddress.validate()
|
||||||
|
} else if (selAddressSource.selectedSource === "Contact") {
|
||||||
|
selContact.validate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: txtLabel
|
||||||
|
visible: label !== ""
|
||||||
|
text: root.label
|
||||||
|
font.pixelSize: 13
|
||||||
|
font.family: Style.current.fontBold.name
|
||||||
|
color: Style.current.textColor
|
||||||
|
height: 18
|
||||||
|
}
|
||||||
|
|
||||||
|
RowLayout {
|
||||||
|
anchors.top: txtLabel.bottom
|
||||||
|
anchors.topMargin: 7
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
|
AddressInput {
|
||||||
|
id: inpAddress
|
||||||
|
width: root.inputWidth
|
||||||
|
label: ""
|
||||||
|
Layout.preferredWidth: root.inputWidth
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
validationError: qsTr("Invalid ethereum address")
|
||||||
|
onSelectedAddressChanged: {
|
||||||
|
root.selectedRecipient = selectedAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ContactSelector {
|
||||||
|
id: selContact
|
||||||
|
contacts: root.contacts
|
||||||
|
visible: false
|
||||||
|
width: root.inputWidth
|
||||||
|
dropdownWidth: parent.width
|
||||||
|
Layout.preferredWidth: root.inputWidth
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onSelectedContactChanged: {
|
||||||
|
if(selectedContact && selectedContact.address) {
|
||||||
|
root.selectedRecipient = selectedContact.address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AccountSelector {
|
||||||
|
id: selAccount
|
||||||
|
accounts: root.accounts
|
||||||
|
visible: false
|
||||||
|
width: root.inputWidth
|
||||||
|
dropdownWidth: parent.width
|
||||||
|
label: ""
|
||||||
|
Layout.preferredWidth: root.inputWidth
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
Layout.fillWidth: true
|
||||||
|
onSelectedAccountChanged: {
|
||||||
|
root.selectedRecipient = selectedAccount.address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AddressSourceSelector {
|
||||||
|
id: selAddressSource
|
||||||
|
sources: ["Address", "Contact", "My account"]
|
||||||
|
width: sourceSelectWidth
|
||||||
|
Layout.preferredWidth: root.sourceSelectWidth
|
||||||
|
Layout.alignment: Qt.AlignTop
|
||||||
|
|
||||||
|
onSelectedSourceChanged: {
|
||||||
|
switch (selectedSource) {
|
||||||
|
case "Address":
|
||||||
|
inpAddress.visible = true
|
||||||
|
selContact.visible = selAccount.visible = false
|
||||||
|
root.height = Qt.binding(function() { return inpAddress.height + txtLabel.height })
|
||||||
|
break;
|
||||||
|
case "Contact":
|
||||||
|
selContact.visible = true
|
||||||
|
inpAddress.visible = selAccount.visible = false
|
||||||
|
root.height = Qt.binding(function() { return selContact.height + txtLabel.height })
|
||||||
|
break;
|
||||||
|
case "My account":
|
||||||
|
selAccount.visible = true
|
||||||
|
inpAddress.visible = selContact.visible = false
|
||||||
|
root.height = Qt.binding(function() { return selAccount.height + txtLabel.height })
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*##^##
|
||||||
|
Designer {
|
||||||
|
D{i:0;autoSize:true;height:480;width:640}
|
||||||
|
}
|
||||||
|
##^##*/
|
|
@ -5,6 +5,10 @@ import QtGraphicalEffects 1.13
|
||||||
import "../imports"
|
import "../imports"
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
|
enum MenuAlignment {
|
||||||
|
Left,
|
||||||
|
Right
|
||||||
|
}
|
||||||
property string label: ""
|
property string label: ""
|
||||||
readonly property bool hasLabel: label !== ""
|
readonly property bool hasLabel: label !== ""
|
||||||
property color bgColor: Style.current.inputBackground
|
property color bgColor: Style.current.inputBackground
|
||||||
|
@ -15,6 +19,8 @@ Item {
|
||||||
property alias selectedItemView: selectedItemContainer.children
|
property alias selectedItemView: selectedItemContainer.children
|
||||||
property int caretRightMargin: Style.current.padding
|
property int caretRightMargin: Style.current.padding
|
||||||
property alias select: inputRectangle
|
property alias select: inputRectangle
|
||||||
|
property int menuAlignment: Select.MenuAlignment.Right
|
||||||
|
property Item zeroItemsView: Item {}
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
@ -105,7 +111,24 @@ Item {
|
||||||
Repeater {
|
Repeater {
|
||||||
id: menuItems
|
id: menuItems
|
||||||
model: root.model
|
model: root.model
|
||||||
|
property int zeroItemsViewHeight
|
||||||
delegate: selectMenu.delegate
|
delegate: selectMenu.delegate
|
||||||
|
onItemAdded: {
|
||||||
|
root.zeroItemsView.visible = false
|
||||||
|
root.zeroItemsView.height = 0
|
||||||
|
}
|
||||||
|
onItemRemoved: {
|
||||||
|
if (count === 0) {
|
||||||
|
root.zeroItemsView.visible = true
|
||||||
|
root.zeroItemsView.height = zeroItemsViewHeight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
zeroItemsViewHeight = root.zeroItemsView.height
|
||||||
|
root.zeroItemsView.visible = count === 0
|
||||||
|
root.zeroItemsView.height = count !== 0 ? 0 : root.zeroItemsView.height
|
||||||
|
selectMenu.insertItem(0, root.zeroItemsView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
@ -123,7 +146,8 @@ Item {
|
||||||
if (selectMenu.opened) {
|
if (selectMenu.opened) {
|
||||||
selectMenu.close()
|
selectMenu.close()
|
||||||
} else {
|
} else {
|
||||||
const offset = inputRectangle.width - selectMenu.width
|
const rightOffset = inputRectangle.width - selectMenu.width
|
||||||
|
const offset = root.menuAlignment === Select.MenuAlignment.Left ? 0 : rightOffset
|
||||||
selectMenu.popup(inputRectangle.x + offset, inputRectangle.y + inputRectangle.height + 8)
|
selectMenu.popup(inputRectangle.x + offset, inputRectangle.y + inputRectangle.height + 8)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
import QtQuick 2.13
|
||||||
|
import QtQuick.Controls 2.13
|
||||||
|
import "../imports"
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: root
|
||||||
|
property alias label: txtBtnLabel.text
|
||||||
|
|
||||||
|
width: txtBtnLabel.width + 2 * 12
|
||||||
|
height: txtBtnLabel.height + 2 * 6
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: Style.current.backgroundTertiary
|
||||||
|
radius: 6
|
||||||
|
anchors.fill: parent
|
||||||
|
border.color: Style.current.borderTertiary
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
StyledText {
|
||||||
|
id: txtBtnLabel
|
||||||
|
color: Style.current.textColorTertiary
|
||||||
|
font.pixelSize: 12
|
||||||
|
height: 16
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: qsTr("Paste")
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouse
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
anchors.fill: parent
|
||||||
|
onClicked: {
|
||||||
|
parent.clicked()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue