// Copyright (c) 2014-2015, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.2
import moneroComponents.PendingTransaction 1.0
import "../components"
import moneroComponents.Wallet 1.0
Rectangle {
id: root
signal paymentClicked(string address, string paymentId, string amount, int mixinCount,
int priority, string description)
signal sweepUnmixableClicked()
color: "#F0EEEE"
property string startLinkText: " (Start daemon)"
function scaleValueToMixinCount(scaleValue) {
var scaleToMixinCount = [4,5,6,7,8,9,10,11,12,13,14,15,20,25];
if (scaleValue < scaleToMixinCount.length) {
return scaleToMixinCount[scaleValue];
} else {
return 0;
}
}
function isValidOpenAliasAddress(address) {
address = address.trim()
var dot = address.indexOf('.')
if (dot < 0)
return false
// we can get an awful lot of valid domains, including non ASCII chars... accept anything
return true
}
function oa_message(text) {
oaPopup.title = qsTr("OpenAlias error") + translationManager.emptyString
oaPopup.text = text
oaPopup.icon = StandardIcon.Information
oaPopup.onCloseCallback = null
oaPopup.open()
}
// Information dialog
StandardDialog {
// dynamically change onclose handler
property var onCloseCallback
id: oaPopup
cancelVisible: false
onAccepted: {
if (onCloseCallback) {
onCloseCallback()
}
}
}
Item {
id: pageRoot
anchors.fill: parent
Label {
id: amountLabel
anchors.left: parent.left
anchors.top: parent.top
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 17
text: qsTr("Amount") + translationManager.emptyString
fontSize: 14
}
Label {
id: transactionPriority
anchors.top: parent.top
anchors.topMargin: 17
fontSize: 14
x: (parent.width - 17) / 2 + 17
text: qsTr("Transaction priority") + translationManager.emptyString
}
Row {
id: amountRow
anchors.top: amountLabel.bottom
anchors.topMargin: 5
anchors.left: parent.left
anchors.leftMargin: 7
width: (parent.width - 17) / 2 + 10
Item {
width: 37
height: 37
Image {
anchors.centerIn: parent
source: "../images/moneroIcon.png"
}
}
// Amount input
LineEdit {
id: amountLine
placeholderText: qsTr("") + translationManager.emptyString
width: parent.width - 37 - 17 - 60
validator: DoubleValidator {
bottom: 0.0
top: 18446744.073709551615
decimals: 12
notation: DoubleValidator.StandardNotation
locale: "C"
}
}
StandardButton {
id: amountAllButton
//anchors.left: amountLine.right
//anchors.top: amountLine.top
//anchors.bottom: amountLine.bottom
width: 60
text: qsTr("or ALL") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
enabled : true
onClicked: amountLine.text = "(all)"
}
}
ListModel {
id: priorityModel
// ListElement: cannot use script for property value, so
// code like this wont work:
// ListElement { column1: qsTr("LOW") + translationManager.emptyString ; column2: ""; priority: PendingTransaction.Priority_Low }
ListElement { column1: qsTr("LOW (x1 fee)") ; column2: ""; priority: PendingTransaction.Priority_Low }
ListElement { column1: qsTr("MEDIUM (x20 fee)") ; column2: ""; priority: PendingTransaction.Priority_Medium }
ListElement { column1: qsTr("HIGH (x166 fee)") ; column2: ""; priority: PendingTransaction.Priority_High }
}
StandardDropdown {
id: priorityDropdown
anchors.top: transactionPriority.bottom
anchors.right: parent.right
anchors.rightMargin: 17
anchors.topMargin: 5
anchors.left: transactionPriority.left
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
dataModel: priorityModel
z: 1
}
Label {
id: privacyLabel
anchors.left: parent.left
anchors.right: parent.right
anchors.top: amountRow.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 30
fontSize: 14
text: qsTr("Privacy level") + translationManager.emptyString
}
PrivacyLevel {
id: privacyLevelItem
anchors.left: parent.left
anchors.right: parent.right
anchors.top: privacyLabel.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 5
onFillLevelChanged: {
print ("PrivacyLevel changed:" + fillLevel)
print ("mixin count:" + scaleValueToMixinCount(fillLevel))
}
}
Label {
id: costLabel
anchors.right: parent.right
anchors.top: amountRow.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 30
fontSize: 14
text: qsTr("Transaction cost")
}
Label {
id: addressLabel
anchors.left: parent.left
anchors.right: parent.right
anchors.top: privacyLevelItem.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 30
fontSize: 14
textFormat: Text.RichText
text: qsTr("\
Address ( Paste in or select from Address book )")
+ translationManager.emptyString
onLinkActivated: appWindow.showPageRequest("AddressBook")
}
// recipient address input
RowLayout {
id: addressLineRow
anchors.left: parent.left
anchors.right: parent.right
anchors.top: addressLabel.bottom
LineEdit {
id: addressLine
anchors.left: parent.left
anchors.right: resolveButton.left
anchors.leftMargin: 17
anchors.topMargin: 5
placeholderText: "4..."
// validator: RegExpValidator { regExp: /[0-9A-Fa-f]{95}/g }
}
StandardButton {
id: resolveButton
anchors.right: parent.right
anchors.leftMargin: 17
anchors.topMargin: 17
anchors.rightMargin: 17
width: 60
text: qsTr("RESOLVE") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
enabled : isValidOpenAliasAddress(addressLine.text)
onClicked: {
var result = walletManager.resolveOpenAlias(addressLine.text)
if (result) {
var parts = result.split("|")
if (parts.length == 2) {
var address_ok = walletManager.addressValid(parts[1], appWindow.persistentSettings.testnet)
if (parts[0] === "true") {
if (address_ok) {
addressLine.text = parts[1]
addressLine.cursorPosition = 0
}
else
oa_message(qsTr("No valid address found at this OpenAlias address"))
} else if (parts[0] === "false") {
if (address_ok) {
addressLine.text = parts[1]
addressLine.cursorPosition = 0
oa_message(qsTr("Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed"))
} else {
oa_message(qsTr("No valid address found at this OpenAlias address, but the DNSSEC signatures could not be verified, so this may be spoofed"))
}
} else {
oa_message(qsTr("Internal error"))
}
} else {
oa_message(qsTr("Internal error"))
}
} else {
oa_message(qsTr("No address found"))
}
}
}
}
Label {
id: paymentIdLabel
anchors.left: parent.left
anchors.right: parent.right
anchors.top: addressLineRow.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 17
fontSize: 14
text: qsTr("Payment ID ( Optional )") + translationManager.emptyString
}
// payment id input
LineEdit {
id: paymentIdLine
anchors.left: parent.left
anchors.right: parent.right
anchors.top: paymentIdLabel.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 5
placeholderText: qsTr("16 or 64 hexadecimal characters") + translationManager.emptyString
// validator: DoubleValidator { top: 0.0 }
}
Label {
id: descriptionLabel
anchors.left: parent.left
anchors.right: parent.right
anchors.top: paymentIdLine.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 17
fontSize: 14
text: qsTr("Description ( Optional - saved to local wallet history )")
+ translationManager.emptyString
}
LineEdit {
id: descriptionLine
anchors.left: parent.left
anchors.right: parent.right
anchors.top: descriptionLabel.bottom
anchors.leftMargin: 17
anchors.rightMargin: 17
anchors.topMargin: 5
}
function checkInformation(amount, address, payment_id, testnet) {
address = address.trim()
payment_id = payment_id.trim()
var amount_ok = amount.length > 0
var address_ok = walletManager.addressValid(address, testnet)
var payment_id_ok = payment_id.length == 0 || walletManager.paymentIdValid(payment_id)
var ipid = walletManager.paymentIdFromAddress(address, testnet)
if (ipid.length > 0 && payment_id.length > 0)
payment_id_ok = false
addressLine.error = !address_ok
amountLine.error = !amount_ok
paymentIdLine.error = !payment_id_ok
return amount_ok && address_ok && payment_id_ok
}
StandardButton {
id: sendButton
anchors.left: parent.left
anchors.top: descriptionLine.bottom
anchors.leftMargin: 17
anchors.topMargin: 17
width: 60
text: qsTr("SEND") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
enabled : pageRoot.checkInformation(amountLine.text, addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet)
onClicked: {
console.log("Transfer: paymentClicked")
var priority = priorityModel.get(priorityDropdown.currentIndex).priority
console.log("priority: " + priority)
console.log("amount: " + amountLine.text)
addressLine.text = addressLine.text.trim()
paymentIdLine.text = paymentIdLine.text.trim()
root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, scaleValueToMixinCount(privacyLevelItem.fillLevel),
priority, descriptionLine.text)
}
}
StandardButton {
id: sweepUnmixableButton
anchors.right: parent.right
anchors.top: descriptionLine.bottom
anchors.rightMargin: 17
anchors.topMargin: 17
width: 60*2
text: qsTr("SWEEP UNMIXABLE") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
enabled : true
onClicked: {
console.log("Transfer: sweepUnmixableClicked")
root.sweepUnmixableClicked()
}
}
Rectangle {
id:desaturate
color:"black"
anchors.fill: parent
opacity: 0.1
visible: (pageRoot.enabled)? 0 : 1;
}
} // Rectangle
Rectangle {
x: root.width/2 - width/2
y: root.height/2 - height/2
height:statusText.paintedHeight + 50
width:statusText.paintedWidth + 40
visible: statusText.text != ""
opacity: 0.9
Text {
id: statusText
anchors.fill:parent
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
textFormat: Text.RichText
onLinkActivated: { appWindow.startDaemon(); }
}
}
Component.onCompleted: {
//Disable password page until enabled by updateStatus
pageRoot.enabled = false
}
// fires on every page load
function onPageCompleted() {
console.log("transfer page loaded")
updateStatus();
}
//TODO: Add daemon sync status
//TODO: enable send page when we're connected and daemon is synced
function updateStatus() {
console.log("updated transfer page status")
if(typeof currentWallet === "undefined") {
statusText.text = qsTr("Wallet is not connected to daemon.") + "
" + root.startLinkText
return;
}
switch (currentWallet.connected) {
case Wallet.ConnectionStatus_Disconnected:
statusText.text = qsTr("Wallet is not connected to daemon.") + "
" + root.startLinkText
break
case Wallet.ConnectionStatus_WrongVersion:
statusText.text = qsTr("Connected daemon is not compatible with GUI. \n" +
"Please upgrade or connect to another daemon")
break
default:
if(!appWindow.daemonSynced){
statusText.text = qsTr("Waiting on daemon synchronization to finish")
} else {
// everything OK, enable transfer page
pageRoot.enabled = true;
statusText.text = "";
}
}
}
// Popuplate fields from addressbook.
function sendTo(address, paymentId, description){
addressLine.text = address
paymentIdLine.text = paymentId
descriptionLine.text = description
}
}