monero-gui/pages/merchant/Merchant.qml

707 lines
25 KiB
QML
Raw Normal View History

2019-04-11 01:17:29 +00:00
import QtQuick 2.9
2018-12-08 15:55:29 +00:00
import QtQuick.Layouts 1.1
import QtQuick.Controls 2.0
import QtGraphicalEffects 1.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.2
import moneroComponents.Clipboard 1.0
import moneroComponents.Wallet 1.0
import moneroComponents.WalletManager 1.0
import moneroComponents.TransactionHistory 1.0
import moneroComponents.TransactionHistoryModel 1.0
import moneroComponents.Subaddress 1.0
import moneroComponents.SubaddressModel 1.0
import "../../js/Windows.js" as Windows
import "../../js/TxUtils.js" as TxUtils
import "../../js/Utils.js" as Utils
import "../../components" as MoneroComponents
import "../../pages"
import "."
Item {
id: root
anchors.margins: 0
2019-04-25 19:09:23 +00:00
property int minWidth: 900
property int qrCodeSize: 220
2018-12-08 15:55:29 +00:00
property bool enableTracking: false
property string trackingError: "" // setting this will show a message @ tracking table
property alias merchantHeight: mainLayout.height
property string addressLabel: ""
property var hiddenAmounts: []
function onPageCompleted() {
appWindow.hideMenu();
// prepare tracking
trackingCheckbox.checked = root.enableTracking
root.update();
timer.running = true;
// set currently selected account indication
var _addressLabel = appWindow.currentWallet.getSubaddressLabel(
appWindow.currentWallet.currentSubaddressAccount,
appWindow.current_subaddress_table_index);
if(_addressLabel === ""){
root.addressLabel = "#" + appWindow.current_subaddress_table_index;
} else {
root.addressLabel = _addressLabel;
}
}
function onPageClosed() {
// reset component objects
timer.running = false
root.enableTracking = false
trackingModel.clear()
appWindow.showMenu();
}
Image {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
2019-04-25 19:09:23 +00:00
height: 300
2019-04-11 01:17:29 +00:00
source: "qrc:///images/merchant/bg.png"
2018-12-08 15:55:29 +00:00
smooth: false
}
ColumnLayout {
id: mainLayout
visible: parent.width >= root.minWidth
spacing: 0
// emulates max-width + center for container
2019-04-25 19:09:23 +00:00
property int maxWidth: 1200
property int defaultMargin: 50
2018-12-08 15:55:29 +00:00
property int horizontalMargin: {
if(appWindow.width >= maxWidth){
return ((appWindow.width - maxWidth) / 2) + defaultMargin;
} else {
return defaultMargin;
}
}
anchors.leftMargin: horizontalMargin
anchors.rightMargin: horizontalMargin
anchors.margins: defaultMargin
anchors.left: parent.left
anchors.top: parent.top
anchors.right: parent.right
Item {
2019-04-25 19:09:23 +00:00
height: 220
2018-12-08 15:55:29 +00:00
anchors.left: parent.left
anchors.right: parent.right
Rectangle {
id: tracker
anchors.left: parent.left
anchors.top: parent.top
2019-04-25 19:09:23 +00:00
height: 220
width: (parent.width - qrImg.width) - 50
2018-12-08 15:55:29 +00:00
radius: 5
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
RowLayout {
spacing: 0
2019-04-25 19:09:23 +00:00
height: 56
2018-12-08 15:55:29 +00:00
RowLayout {
Layout.alignment: Qt.AlignLeft
2019-04-25 19:09:23 +00:00
Layout.preferredWidth: 260
2018-12-08 15:55:29 +00:00
Layout.preferredHeight: parent.height
Layout.fillHeight: true
2019-04-25 19:09:23 +00:00
spacing: 8
2018-12-08 15:55:29 +00:00
Item {
2019-04-25 19:09:23 +00:00
Layout.preferredWidth: 10
2018-12-08 15:55:29 +00:00
}
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2019-04-25 19:09:23 +00:00
font.pixelSize: 16
2018-12-08 15:55:29 +00:00
font.bold: true
color: "#767676"
text: qsTr("Sales") + translationManager.emptyString
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
}
Item {
Layout.fillWidth: true
}
}
Item {
Layout.fillWidth: true
}
}
Rectangle {
Layout.fillWidth: true
2019-04-25 19:09:23 +00:00
Layout.preferredHeight: 1
2018-12-08 15:55:29 +00:00
color: "#d9d9d9"
}
MerchantTrackingList {
Layout.fillWidth: true
2019-04-25 19:09:23 +00:00
Layout.preferredHeight: 400
2018-12-08 15:55:29 +00:00
model: trackingModel
message: {
if(!root.enableTracking){
return qsTr(
"<style>p{font-size:14px;}</style>" +
"<p>This page will automatically scan the blockchain and the tx pool " +
"for incoming transactions using the QR code.</p>" +
"<p>It's up to you whether to accept unconfirmed transactions or not. It is likely they'll be " +
"confirmed in short order, but there is still a possibility they might not, so for larger " +
"values you may want to wait for one or more confirmation(s).</p>"
);
} else if(root.trackingError !== ""){
return root.trackingError;
} else if(trackingModel.count < 1){
return qsTr("Currently monitoring incoming transactions, none found yet.");
} else {
return ""
}
}
onHideAmountToggled: {
if(root.hiddenAmounts.indexOf(txid) < 0){
root.hiddenAmounts.push(txid);
} else {
root.hiddenAmounts = root.hiddenAmounts.filter(function(_txid) { return _txid !== txid });
}
}
}
}
}
DropShadow {
anchors.fill: source
cached: true
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 16
color: "#20000000"
smooth: true
source: tracker
}
Rectangle {
id: qrImg
color: "white"
anchors.right: parent.right
anchors.top: parent.top
height: root.qrCodeSize
width: root.qrCodeSize
Layout.maximumWidth: root.qrCodeSize
Layout.preferredHeight: width
radius: 5
Image {
id: qrCode
anchors.fill: parent
2019-04-25 19:09:23 +00:00
anchors.margins: 1
2018-12-08 15:55:29 +00:00
smooth: false
fillMode: Image.PreserveAspectFit
source: "image://qrcode/" + TxUtils.makeQRCodeString(appWindow.current_address, amountToReceive.text)
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.RightButton
onClicked: {
if (mouse.button == Qt.RightButton){
qrMenu.x = this.mouseX;
qrMenu.y = this.mouseY;
qrMenu.open()
}
}
onPressAndHold: qrFileDialog.open()
}
}
Menu {
id: qrMenu
title: "QrCode"
MenuItem {
text: qsTr("Save As") + translationManager.emptyString;
onTriggered: qrFileDialog.open()
}
}
}
DropShadow {
anchors.fill: source
cached: true
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 16
color: "#30000000"
smooth: true
source: qrImg
}
}
Item {
2019-04-25 19:09:23 +00:00
Layout.preferredHeight: 40
2018-12-08 15:55:29 +00:00
anchors.left: parent.left
anchors.right: parent.right
Item {
2019-04-25 19:09:23 +00:00
width: (parent.width - qrImg.width) - (50)
height: 32
2018-12-08 15:55:29 +00:00
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2018-12-08 15:55:29 +00:00
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
2019-04-25 19:09:23 +00:00
font.pixelSize: 12
2018-12-08 15:55:29 +00:00
font.bold: false
color: "white"
text: "<style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 12px;}</style>Currently selected address: " + addressLabel + " <a href='#'>(Change)</a>"
textFormat: Text.RichText
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: appWindow.showPageRequest("Receive")
}
}
}
Item {
anchors.right: parent.right
anchors.top: parent.top
2019-04-25 19:09:23 +00:00
width: 220
height: 32
2018-12-08 15:55:29 +00:00
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2018-12-08 15:55:29 +00:00
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
2019-04-25 19:09:23 +00:00
font.pixelSize: 12
2018-12-08 15:55:29 +00:00
font.bold: false
color: "white"
text: qsTr("(right-click, save as)") + translationManager.emptyString
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
}
}
}
Item {
2019-04-25 19:09:23 +00:00
Layout.preferredHeight: 120
Layout.topMargin: 20
2018-12-08 15:55:29 +00:00
Layout.fillWidth: true
Rectangle {
id: payment_url_container
anchors.left: parent.left
anchors.top: parent.top
2019-04-25 19:09:23 +00:00
implicitHeight: 120
width: (parent.width - qrImg.width) - (50)
2018-12-08 15:55:29 +00:00
radius: 5
ColumnLayout {
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
spacing: 0
RowLayout {
spacing: 0
2019-04-25 19:09:23 +00:00
height: 56
2018-12-08 15:55:29 +00:00
RowLayout {
Layout.alignment: Qt.AlignLeft
2019-04-25 19:09:23 +00:00
Layout.preferredWidth: 260
2018-12-08 15:55:29 +00:00
Layout.preferredHeight: parent.height
Layout.fillHeight: true
spacing: 8
Item {
2019-04-25 19:09:23 +00:00
Layout.preferredWidth: 10
2018-12-08 15:55:29 +00:00
}
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2019-04-25 19:09:23 +00:00
font.pixelSize: 14
2018-12-08 15:55:29 +00:00
font.bold: true
color: "#767676"
text: qsTr("Payment URL") + translationManager.emptyString
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
}
Item {
Layout.fillWidth: true
}
}
// @TODO: PaymentURL explanation
// Rectangle {
// // help box
// Layout.alignment: Qt.AlignLeft
2019-04-25 19:09:23 +00:00
// Layout.preferredWidth: 40
2018-12-08 15:55:29 +00:00
// Layout.fillHeight: true
// color: "transparent"
2019-04-11 01:17:29 +00:00
// MoneroComponents.TextPlain {
2018-12-08 15:55:29 +00:00
// anchors.verticalCenter: parent.verticalCenter
// anchors.right: parent.right
2019-04-25 19:09:23 +00:00
// anchors.rightMargin: 20
// font.pixelSize: 16
2018-12-08 15:55:29 +00:00
// font.bold: true
// color: "#767676"
// text:"?"
// }
// MouseArea {
// anchors.fill: parent
// cursorShape: Qt.PointingHandCursor
// onClicked: {
// merchantPageDialog.title = qsTr("Payment URL") + translationManager.emptyString;
// merchantPageDialog.text = qsTr("payment url explanation")
// merchantPageDialog.icon = StandardIcon.Information
// merchantPageDialog.open()
// }
// }
// }
Item {
Layout.fillWidth: true
}
}
Rectangle {
Layout.fillWidth: true
Layout.preferredHeight: 1
color: "#d9d9d9"
}
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2018-12-08 15:55:29 +00:00
property string _color: "#767676"
Layout.fillWidth: true
2019-04-25 19:09:23 +00:00
Layout.margins: 20
Layout.topMargin: 10
2018-12-08 15:55:29 +00:00
wrapMode: Text.WrapAnywhere
elide: Text.ElideRight
2019-04-25 19:09:23 +00:00
font.pixelSize: 12
2018-12-08 15:55:29 +00:00
font.bold: true
color: _color
text: TxUtils.makeQRCodeString(appWindow.current_address, amountToReceive.text)
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onEntered: {
parent.color = MoneroComponents.Style.orange
}
onExited: {
parent.color = parent._color
}
onClicked: {
console.log("Copied to clipboard");
clipboard.setText(parent.text);
appWindow.showStatusMessage(qsTr("Copied to clipboard"), 3);
}
}
}
}
}
DropShadow {
anchors.fill: source
cached: true
horizontalOffset: 3
verticalOffset: 3
radius: 8.0
samples: 16
color: "#20000000"
smooth: true
source: payment_url_container
}
Item {
anchors.right: parent.right
anchors.top: parent.top
2019-04-25 19:09:23 +00:00
width: 220
height: 32
2018-12-08 15:55:29 +00:00
ColumnLayout {
anchors.left: parent.left
anchors.right: parent.right
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2019-04-25 19:09:23 +00:00
font.pixelSize: 14
2018-12-08 15:55:29 +00:00
font.bold: false
color: "white"
text: qsTr("Amount to receive") + " (XMR)"
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
}
Image {
2019-04-25 19:09:23 +00:00
height: 28
width: 220
2019-04-11 01:17:29 +00:00
source: "qrc:///images/merchant/input_box.png"
2018-12-08 15:55:29 +00:00
TextField {
id: amountToReceive
topPadding: 0
2019-04-25 19:09:23 +00:00
leftPadding: 10
2018-12-08 15:55:29 +00:00
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
2019-04-25 19:09:23 +00:00
anchors.topMargin: 3
font.pixelSize: 16
2018-12-08 15:55:29 +00:00
font.bold: true
horizontalAlignment: TextInput.AlignLeft
verticalAlignment: TextInput.AlignVCenter
selectByMouse: true
color: "#424242"
selectionColor: "#3f3fe3"
selectedTextColor: "white"
placeholderText: "0.00"
2018-12-08 15:55:29 +00:00
background: Rectangle {
color: "transparent"
}
onTextChanged: {
if (amountToReceive.text.indexOf('.') === 0) {
amountToReceive.text = '0' + amountToReceive.text;
}
}
validator: RegExpValidator {
regExp: /^(\d{1,8})?([\.]\d{1,12})?$/
}
}
}
Item {
2019-04-25 19:09:23 +00:00
height: 2
width: 220
2018-12-08 15:55:29 +00:00
}
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2018-12-08 15:55:29 +00:00
// @TODO: When we have XMR/USD rate avi. in the future.
visible: false
2019-04-25 19:09:23 +00:00
font.pixelSize: 14
2018-12-08 15:55:29 +00:00
font.bold: false
color: "white"
text: qsTr("Amount to receive") + " (USD)"
opacity: 0.2
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
}
Image {
visible: false
2019-04-25 19:09:23 +00:00
height: 28
width: 220
2019-04-11 01:17:29 +00:00
source: "qrc:///images/merchant/input_box.png"
2018-12-08 15:55:29 +00:00
opacity: 0.2
}
}
}
}
Item {
2019-04-25 19:09:23 +00:00
Layout.topMargin: 32
Layout.preferredHeight: 40
2018-12-08 15:55:29 +00:00
anchors.left: parent.left
anchors.right: parent.right
ColumnLayout {
2019-04-25 19:09:23 +00:00
spacing: 16
2018-12-08 15:55:29 +00:00
MerchantCheckbox {
id: trackingCheckbox
checked: root.enableTracking
text: qsTr("Enable sales tracker") + translationManager.emptyString
2018-12-08 15:55:29 +00:00
onChanged: {
root.enableTracking = this.checked;
}
}
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2018-12-08 15:55:29 +00:00
id: content
2019-04-25 19:09:23 +00:00
font.pixelSize: 14
2018-12-08 15:55:29 +00:00
font.bold: false
color: "white"
text: qsTr("Leave this page") + translationManager.emptyString
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
MouseArea {
anchors.fill: parent
hoverEnabled: true
cursorShape: Qt.PointingHandCursor
onClicked: appWindow.showPageRequest("Receive")
}
}
}
}
}
Rectangle {
// Shows when the window is too small
visible: parent.width < root.minWidth
anchors.top: parent.top
2019-04-25 19:09:23 +00:00
anchors.topMargin: 100;
2018-12-08 15:55:29 +00:00
anchors.horizontalCenter: parent.horizontalCenter
2019-04-25 19:09:23 +00:00
height: 120
width: 400
2018-12-08 15:55:29 +00:00
radius: 5
2019-04-11 01:17:29 +00:00
MoneroComponents.TextPlain {
2018-12-08 15:55:29 +00:00
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
2019-04-25 19:09:23 +00:00
font.pixelSize: 14
2018-12-08 15:55:29 +00:00
font.bold: true
color: MoneroComponents.Style.moneroGrey
text: qsTr("The merchant page requires a larger window") + translationManager.emptyString
2019-04-11 01:17:29 +00:00
themeTransition: false
2018-12-08 15:55:29 +00:00
}
}
function update() {
const max_tracking = 3;
if (!appWindow.currentWallet || !root.enableTracking) {
root.trackingError = "";
trackingModel.clear();
return
}
if (appWindow.currentWallet.connected() == Wallet.ConnectionStatus_Disconnected) {
root.trackingError = qsTr("WARNING: no connection to daemon");
trackingModel.clear();
return
}
var model = appWindow.currentWallet.historyModel
var count = model.rowCount()
var totalAmount = 0
var nTransactions = 0
var blockchainHeight = null
var txs = []
// Currently selected subaddress as per Receive page
var current_subaddress_table_index = appWindow.current_subaddress_table_index;
for (var i = 0; i < count && txs.length < max_tracking; ++i) {
var idx = model.index(i, 0)
var isout = model.data(idx, TransactionHistoryModel.TransactionIsOutRole);
var timeDate = model.data(idx, TransactionHistoryModel.TransactionDateRole);
var timeHour = model.data(idx, TransactionHistoryModel.TransactionTimeRole);
2019-01-10 09:39:22 +00:00
var timeEpoch = new Date(timeDate + "T" + timeHour) .getTime() / 1000;
2018-12-08 15:55:29 +00:00
var subaddrAccount = model.data(idx, TransactionHistoryModel.TransactionSubaddrAccountRole);
var subaddrIndex = model.data(idx, TransactionHistoryModel.TransactionSubaddrIndexRole);
if (!isout && subaddrAccount == appWindow.currentWallet.currentSubaddressAccount && subaddrIndex == current_subaddress_table_index) {
var amount = model.data(idx, TransactionHistoryModel.TransactionAtomicAmountRole);
totalAmount = walletManager.addi(totalAmount, amount)
nTransactions += 1
var txid = model.data(idx, TransactionHistoryModel.TransactionHashRole);
var blockHeight = model.data(idx, TransactionHistoryModel.TransactionBlockHeightRole);
var in_txpool = false;
var confirmations = 0;
var displayAmount = 0;
if (blockHeight == 0) {
in_txpool = true;
} else {
if (blockchainHeight == null)
blockchainHeight = appWindow.currentWallet.blockChainHeight()
confirmations = blockchainHeight - blockHeight - 1
displayAmount = model.data(idx, TransactionHistoryModel.TransactionDisplayAmountRole);
}
txs.push({
"amount": displayAmount,
"confirmations": confirmations,
"blockheight": blockHeight,
"in_txpool": in_txpool,
"txid": txid,
"time_epoch": timeEpoch,
"time_date": timeDate + " " + timeHour,
"hide_amount": root.hiddenAmounts.indexOf(txid) >= 0
})
}
}
// Update tracking status label
if (nTransactions == 0) {
root.trackingError = qsTr("Currently monitoring incoming transactions, none found yet.") + translationManager.emptyString
return
}
trackingModel.clear();
txs.forEach(function(tx){
trackingModel.append({
"amount": tx.amount,
"blockheight": tx.blockheight,
"confirmations": tx.confirmations,
"blockheight": tx.blockHeight,
"in_txpool": tx.in_txpool,
"txid": tx.txid,
"time_epoch": tx.time_epoch,
"time_date": tx.time_date,
"hide_amount": tx.hide_amount
});
});
}
ListModel {
id: trackingModel
}
Timer {
id: timer
interval: 3000; running: false; repeat: true
onTriggered: update()
}
MessageDialog {
id: merchantPageDialog
standardButtons: StandardButton.Ok
}
FileDialog {
id: qrFileDialog
title: "Please choose a name"
folder: shortcuts.pictures
selectExisting: false
nameFilters: ["Image (*.png)"]
onAccepted: {
if(!walletManager.saveQrCode(TxUtils.makeQRCodeString(appWindow.current_address, amountToReceive.text), walletManager.urlToLocalPath(fileUrl))) {
console.log("Failed to save QrCode to file " + walletManager.urlToLocalPath(fileUrl) )
receivePageDialog.title = qsTr("Save QrCode") + translationManager.emptyString;
receivePageDialog.text = qsTr("Failed to save QrCode to ") + walletManager.urlToLocalPath(fileUrl) + translationManager.emptyString;
receivePageDialog.icon = StandardIcon.Error
receivePageDialog.open()
}
}
}
Clipboard { id: clipboard }
}