Files
pay/guis/mobile/PayWithQR.qml
T

466 lines
16 KiB
QML
Raw Permalink Normal View History

2022-12-05 18:34:53 +01:00
/*
* This file is part of the Flowee project
2023-02-06 21:55:54 +01:00
* Copyright (C) 2022-2023 Tom Zander <tom@flowee.org>
2022-12-05 18:34:53 +01:00
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import QtQuick
import QtQuick.Layouts
2022-12-20 15:18:05 +01:00
import QtQuick.Controls as QQC2
2022-12-05 18:34:53 +01:00
import "../Flowee" as Flowee
import Flowee.org.pay;
Page {
id: root
headerText: qsTr("Approve Payment")
2022-12-05 18:34:53 +01:00
2023-02-08 14:30:14 +01:00
property QtObject sendAllAction: QQC2.Action {
checkable: true
checked: payment.details[0].maxSelected
text: qsTr("Send All", "all money in wallet")
onTriggered: {
payment.details[0].maxSelected = checked
if (payment.isValid)
payment.prepare(); // auto-prepare doesn't act on changes done on the details.
}
}
2023-05-31 15:20:11 +02:00
property QtObject showTargetAddress: QQC2.Action {
checkable: true
2023-12-22 18:52:05 +01:00
checked: true
2023-05-31 15:20:11 +02:00
text: qsTr("Show Address", "to show a bitcoincash address")
}
property QtObject editPrice: QQC2.Action {
text: qsTr("Edit Amount", "Edit amount of money to send")
onTriggered: {
root.closeHeaderMenu();
// this action can only ever be used to start editing.
root.allowEditAmount = true;
priceInput.fiatFollowsSats = false
priceInput.takeFocus()
2023-05-31 15:20:11 +02:00
}
2023-08-30 12:50:40 +02:00
checked: root.allowEditAmount
2023-05-31 15:20:11 +02:00
}
menuItems: {
// only have menu items as long as we are effectively
// showing this page and not some overlay.
2023-05-31 15:20:11 +02:00
if (payment.broadcastStatus === Payment.NotStarted && !scanner.isScanning) {
// a QR _with_ a bch-amount will turn off editing of amount-to-send
if (allowEditAmount)
2023-08-30 12:50:40 +02:00
return [ showTargetAddress, sendAllAction ];
2023-05-31 15:20:11 +02:00
else
return [ showTargetAddress, editPrice ];
}
return [];
}
2023-05-31 15:20:11 +02:00
// if true, show widgets to edit the amount-to-send
property bool allowEditAmount: true
2023-02-06 21:55:54 +01:00
Item { // data
2022-12-05 18:34:53 +01:00
QRScanner {
id: scanner
scanType: QRScanner.PaymentDetails
2023-08-30 22:42:08 +02:00
autostart: Pay.paymentProtocolRequest === ""
2022-12-07 14:22:11 +01:00
onFinished: {
2022-12-07 14:36:51 +01:00
var rc = scanResult
2023-06-05 18:12:29 +02:00
if (rc === "") { // scanning interrupted
2022-12-07 14:36:51 +01:00
thePile.pop();
2023-06-05 18:12:29 +02:00
return;
2022-12-07 14:36:51 +01:00
}
// if the scanner got bypassed with a 'paste' then
// we don't allow instapay
2023-06-11 18:10:06 +02:00
if (resultSource === QRScanner.Clipboard)
payment.instaPay = false;
2023-12-22 18:52:05 +01:00
// check if payment has been done from 'simple' payment-protocol
showTargetAddress.checked = payment.simpleAddressTarget;
2023-06-05 18:12:29 +02:00
// Take the entire QR-url and let the Payment object parse it.
// this updates things like amount, comment and indeed address.
2023-09-05 19:09:46 +02:00
let success = payment.pasteTargetAddress(rc);
if (!success)
2023-06-05 18:12:29 +02:00
scannedUrlFaultyDialog.open();
// should the price be included in the QR code, don't show editing widgets.
root.allowEditAmount = payment.paymentAmount <= 0;
if (root.allowEditAmount)
priceInput.takeFocus();
2023-11-06 17:17:18 +01:00
else
root.takeFocus();
2022-12-07 14:22:11 +01:00
}
2022-12-05 18:34:53 +01:00
}
Payment {
id: payment
2022-12-07 14:22:11 +01:00
account: portfolio.current
2022-12-07 18:30:02 +01:00
fiatPrice: Fiat.price
2023-02-08 14:30:14 +01:00
autoPrepare: true
2023-05-16 15:32:33 +02:00
instaPay: true
2022-12-07 14:22:11 +01:00
2023-08-30 22:42:08 +02:00
Component.onCompleted: {
/*
The application can be started with a click on a payment link,
in that case the link gets made available in the following property
and we start a payment protocol with the value.
Afterwards we reset the property to avoid the next opening of this
screen repeating the payment.
*/
var paymentProtcolUrl = Pay.paymentProtocolRequest;
if (paymentProtcolUrl !== "") {
scanner.autostart = false;
payment.targetAddress = paymentProtcolUrl;
Pay.paymentProtocolRequest = "";
root.allowEditAmount = payment.paymentAmount <= 0;
}
}
2022-12-07 14:22:11 +01:00
// easier testing values (for when you don't have a camera)
2023-09-02 20:23:29 +02:00
// ps. in case of testing, you want above instaPay: property => false!!
2023-02-08 14:30:14 +01:00
// paymentAmount: 100000000
// targetAddress: "qrejlchcwl232t304v8ve8lky65y3s945u7j2msl45"
// userComment: "bla bla bla"
2023-02-06 21:55:54 +01:00
}
2023-06-05 18:12:29 +02:00
Flowee.Dialog {
id: scannedUrlFaultyDialog
title: qsTr("Invalid QR code")
standardButtons: QQC2.DialogButtonBox.Close
onRejected: thePile.pop(); // remove this entire page
contentComponent: dialogForFaultyUrl
}
Component {
id: dialogForFaultyUrl
Column {
width: parent.width
spacing: 10
Flowee.Label {
id: mainText
width: parent.width
text: qsTr("I don't understand the scanned code. I'm sorry, I can't start a payment.")
wrapMode: Text.Wrap
}
Flowee.Label {
text: qsTr("details")
font.italic: true
color: palette.link
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: detailsLabel.visible = true;
}
}
Flowee.Label {
id: detailsLabel
text: qsTr("Scanned text: <pre>%1</pre>").arg(scanner.scanResult);
visible: false
font.pixelSize: mainText.pixelSize * 0.8
wrapMode: Text.Wrap
width: parent.width
}
}
}
2022-12-05 18:34:53 +01:00
}
2023-03-11 22:44:27 +01:00
PriceInputWidget {
2023-05-31 15:20:11 +02:00
id: priceInput // when the price / amount is editable
paymentBackend: payment
2023-03-11 22:44:27 +01:00
width: parent.width
2023-05-31 15:20:11 +02:00
x: allowEditAmount ? 0 : 0 - width
Behavior on x { NumberAnimation { } }
}
Item {
id: priceFeedback // when the price / amount is given by the scanned QR
width: parent.width
x: allowEditAmount ? 0 - width : 0
y: 10
height: col.height
Column {
id: col
width: parent.width
Flowee.Label {
id: fiatPrice
anchors.horizontalCenter: parent.horizontalCenter
text: Fiat.formattedPrice(payment.paymentAmountFiat)
font.pixelSize: 38
}
Flowee.BitcoinAmountLabel {
value: payment.paymentAmount
colorize: false
showFiat: false
font.pixelSize: mainWindow.font.pixelSize * 0.8
anchors.horizontalCenter: parent.horizontalCenter
}
}
Behavior on x { NumberAnimation { } }
2022-12-20 15:18:05 +01:00
}
2022-12-05 18:34:53 +01:00
Flowee.Label {
id: commentLabel
2023-08-30 12:50:40 +02:00
text: qsTr("Payment description")
visible: userComment.visible
y: {
if (!userComment.visible)
return 0;
if (root.allowEditAmount) { // the numpad is on
/*
position based on available space and how many items
need to be visible.
If invisible, place at -100
*/
let space = walletNameBackground.y - (priceInput.y + priceInput.height)
if (destinationAddress.visible)
space -= destinationAddress.height + 10
space -= userComment.height + 10;
if (space < height + 10)
return -100;
return priceInput.y + priceInput.height + 10;
}
return walletNameBackground.y + walletNameBackground.height + 10;
}
2022-12-05 18:34:53 +01:00
}
Flowee.Label {
id: userComment
2022-12-05 18:34:53 +01:00
text: payment.userComment
2023-08-30 12:50:40 +02:00
visible: text !== "" && !errorLabel.visible
2022-12-07 14:22:11 +01:00
color: palette.highlight
font.italic: true
2023-08-30 12:50:40 +02:00
y: {
if (!visible)
return 0;
if (commentLabel.y < 0)
return priceInput.y + priceInput.height + 10;
return commentLabel.y + commentLabel.height + 10;
}
}
Flowee.Label {
id: destinationAddressHeader
text: qsTr("Destination Address")
visible: destinationAddress.visible
y: {
if (!visible)
return 0;
if (root.allowEditAmount) { // the numpad is on
let space = walletNameBackground.y - (priceInput.y + priceInput.height)
if (userComment.visible)
space -= userComment.height + 10
if (commentLabel.visible)
space -= commentLabel.height + 10
space -= destinationAddress.height + 10;
if (space < height + 10)
return -100;
if (userComment.visible)
return userComment.y + userComment.height + 10;
return priceInput.y + priceInput.height + 10;
}
if (userComment.visible)
return userComment.y + userComment.height + 10;
return walletNameBackground.y + walletNameBackground.height + 10;
}
}
Flowee.LabelWithClipboard {
id: destinationAddress
text: payment.niceAddress
width: parent.width
visible: showTargetAddress.checked && !errorLabel.visible
font.pixelSize: mainWindow.font.pixelSize * 0.9
y: {
if (!visible)
return 0;
if (destinationAddressHeader.y < 0){
if (userComment.visible)
return userComment.y + userComment.height + 10;
return priceInput.y + priceInput.height + 10;
}
return destinationAddressHeader.y + destinationAddressHeader.height + 10;
}
2022-12-05 18:34:53 +01:00
}
2023-02-06 21:55:54 +01:00
Rectangle {
visible: errorLabel.text !== ""
color: mainWindow.errorRedBg
radius: 10
width: parent.width
2023-02-06 21:55:54 +01:00
anchors.bottom: walletNameBackground.top
2023-03-11 22:44:27 +01:00
anchors.bottomMargin: 6
height: errorLabel.height + 20
Flowee.Label {
id: errorLabel
text: payment.error
wrapMode: Text.Wrap
x: 10
y: 10
width: parent.width - 20
horizontalAlignment: Qt.AlignHCenter
}
2023-05-18 21:52:51 +02:00
MouseArea {
anchors.fill: parent
// just here to catch mouse clicks.
}
2022-12-07 14:22:11 +01:00
}
2023-03-13 10:24:15 +01:00
AccountSelectorWidget {
2023-02-06 21:55:54 +01:00
id: walletNameBackground
anchors.bottom: numericKeyboard.top
2023-02-06 21:55:54 +01:00
anchors.bottomMargin: 10
2023-03-13 16:58:05 +01:00
2023-08-30 12:50:40 +02:00
balanceActions: {
if (editPrice.checked)
return [ sendAllAction ];
return [];
}
}
2023-03-11 22:44:27 +01:00
NumericKeyboardWidget {
id: numericKeyboard
anchors.bottom: slideToApprove.top
2022-12-07 14:22:11 +01:00
anchors.bottomMargin: 15
width: parent.width
2023-02-06 21:55:54 +01:00
enabled: !payment.details[0].maxSelected
2023-05-31 15:20:11 +02:00
x: allowEditAmount ? 0 : 0 - width
dataInput: priceInput
2023-05-31 15:20:11 +02:00
Behavior on x { NumberAnimation { } }
}
2022-12-07 14:22:11 +01:00
SlideToApprove {
id: slideToApprove
anchors.bottom: parent.bottom
anchors.bottomMargin: 10
2022-12-07 14:22:11 +01:00
width: parent.width
enabled: payment.isValid && payment.txPrepared
2023-09-02 20:23:29 +02:00
onActivated: payment.markUserApproved()
2023-06-28 22:46:22 +02:00
visible: payment.account.isDecrypted || !payment.account.needsPinToPay
}
2023-09-02 20:23:29 +02:00
Flickable {
anchors.fill: parent
contentWidth: width
contentHeight: warningsColumn.implicitHeight
2023-09-06 13:17:35 +02:00
enabled: warningsColumn.implicitHeight > 0
2023-09-02 20:23:29 +02:00
Column {
id: warningsColumn
width: parent.width
Repeater {
model: payment.warnings
Rectangle {
y: 8
width: root.width - 16
height: Math.max(75, Math.max(warningIcon.height, warningText.height) + 20)
radius: 20
color: palette.alternateBase
border.width: 1
border.color: palette.midlight
Rectangle { // placeholder icon
id: warningIcon
x: 20
width: 40
height: 40
radius: 20
color: mainWindow.errorRedBg
anchors.verticalCenter: parent.verticalCenter
}
Flowee.Label {
id: warningText
text: modelData
wrapMode: Text.Wrap
anchors.left: warningIcon.right
anchors.leftMargin: 10
2023-09-05 19:09:46 +02:00
anchors.right: closeIcon.left
2023-09-02 20:23:29 +02:00
anchors.rightMargin: 10
anchors.verticalCenter: parent.verticalCenter
}
Flowee.CloseIcon {
2023-09-05 19:09:46 +02:00
id: closeIcon
2023-09-02 20:23:29 +02:00
anchors.right: parent.right
anchors.top: parent.top
anchors.margins: 10
onClicked: payment.clearWarnings();
}
}
}
}
}
2023-06-28 22:46:22 +02:00
Item {
id: decryptButton
visible: !slideToApprove.visible
anchors.fill: slideToApprove
Image {
id: lockIcon
source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg");
x: 10
y: 5
width: 50
height: 50
smooth: true
}
Flowee.Button {
height: parent.height - 10
width: parent.width - 80
x: 70
y: 5
text: qsTr("Unlock Wallet")
onClicked: thePile.push(unlockInPage)
}
Component {
id: unlockInPage
Page {
headerText: payment.account.name
UnlockWalletPanel {
anchors.fill: parent
anchors.margins: 10
2023-06-28 22:46:22 +02:00
account: payment.account
Connections {
target: payment.account
function onIsDecryptedChanged() {
if (payment.account.isDecrypted)
thePile.pop()
}
}
}
}
}
}
2022-12-07 18:30:02 +01:00
Flowee.BroadcastFeedback {
2022-12-07 14:22:11 +01:00
id: broadcastPage
2022-12-07 18:30:02 +01:00
anchors.leftMargin: -10 // go against the margins Page gave us to show more fullscreen.
anchors.rightMargin: -10
onStatusChanged: {
if (status !== "")
root.headerText = status;
}
2022-12-07 14:22:11 +01:00
2023-03-13 13:33:45 +01:00
onCloseButtonPressed: {
var mainView = thePile.get(0);
mainView.currentIndex = 0; // go to the 'main' tab.
thePile.pop();
}
2022-12-07 14:22:11 +01:00
}
2022-12-05 18:34:53 +01:00
}