/* * This file is part of the Flowee project * Copyright (C) 2022-2024 Tom Zander * * 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 . */ import QtQuick import QtQuick.Controls as QQC2 import "../Flowee" as Flowee import Flowee.org.pay FocusScope { id: root property string icon: "qrc:/receive.svg" property string title: qsTr("Receive") property bool showInstructions: true property QtObject account: portfolio.current property bool qrViewActive: true; property bool editViewActive: false focus: true PaymentRequest { id: request amount: priceInput.paymentBackend.paymentAmount onAmountSeenChanged: feedback.hide = false; } onAccountChanged: { var state = request.state; if (request.state === PaymentRequest.Unpaid) { // I can only change the wallet without cost // if no payment has been seen. request.account = account; } } onActiveFocusChanged: { // starting reserves an adderess, don't do that until // this tab is actually viewed. if (activeFocus) request.start(); } Column { id: verticalTabs y: 50 x: -10 enabled: feedback.opacity < 0.1 Repeater { model: ["qr-code", "edit-money", "edit-pen"] MouseArea { property bool active: index === swipeView.currentIndex width: 50 height: 50 onClicked: swipeView.moveTo(index); Rectangle { x: 46 y: 5 width: 4 height: 40 color: palette.highlight visible: parent.active } Rectangle { anchors.fill: parent color: palette.highlight visible: parent.active opacity: 0.15 } Image { anchors.fill: parent anchors.margins: 10 source: "qrc:/" + modelData + (Pay.useDarkSkin ? "-light" : "") + ".svg" smooth: true opacity: enabled ? 1 : 0.4 } } } } Flowee.Label { id: instructions anchors.horizontalCenter: parent.horizontalCenter text: qsTr("Share this QR to receive") opacity: editViewActive ? 0 : 0.5 Behavior on opacity { OpacityAnimator { } } visible: root.showInstructions } Flowee.QRWidget { id: qr width: parent.width y: root.showInstructions ? instructions.height : 0 qrSize: { var avail = root.height - y - swipeView.height; return Math.min(256, avail); } textVisible: false qrText: request.qr Flowee.Label { visible: request.failReason !== PaymentRequest.NoFailure text: { var f = request.failReason; if (f === PaymentRequest.AccountEncrypted) return qsTr("Encrypted Wallet"); if (f === PaymentRequest.AccountImporting) return qsTr("Import Running..."); if (f === PaymentRequest.NoAccountSet) return "No Account Set"; // not translated b/c cause is bug in QML return ""; } anchors.centerIn: parent width: parent.width - 40 wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pointSize: 18 } } QQC2.SwipeView { id: swipeView width: parent.width + 20 // to ensure swiped pages are wide enough x: -10 height: { var have = parent.height - (showInstructions ? instructions.height : 0) - 256; // qr is ideally 256 if (currentIndex === 1) return Math.max(have, priceInput.implicitHeight + 215); return have - 6; // spacing of 6 } anchors.bottom: parent.bottom onCurrentIndexChanged: currentItem.doFocus(); function moveTo(index) { forceActiveFocus(); currentIndex = index; currentItem.doFocus(); } Item { function doFocus() { } Column { width: parent.width - 20 x: 10 Flowee.BitcoinAmountLabel { visible: value > 0 colorize: false value: request.amount font.pixelSize: instructions.font.pixelSize * 1.4 anchors.horizontalCenter: parent.horizontalCenter } PageTitledBox { width: parent.width title: qsTr("Address", "Bitcoin Cash address") Flowee.LabelWithClipboard { width: parent.width text: request.addressShort fontSizeMode: Text.HorizontalFit } } Flowee.Button { anchors.right: parent.right text: qsTr("Clear") enabled: description.totalText !== "" || priceInput.paymentBackend.paymentAmount > 0; onClicked: { request.clear(); request.account = portfolio.current; description.text = ""; priceInput.paymentBackend.paymentAmount = 0; request.start(); } } } } Item { function doFocus() { priceInput.takeFocus(); } PriceInputWidget { id: priceInput fiatFollowsSats: false width: parent.width Connections { target: Fiat /* * The price may change simply because the market moved and we re-cheked the server, * but more important is the case where the user changed which currency they use. */ function onPriceChanged() { // set the value again to trigger an updated exchange-rate calculation to happen. var price = priceInput.editor.value; if (priceInput.fiatFollowsSats) priceInput.paymentBackend.paymentAmount = price; else priceInput.paymentBackend.paymentAmountFiat = price; } } } Rectangle { width: parent.width anchors.bottom: parent.bottom color: palette.base height: 215 } NumericKeyboardWidget { width: parent.width height: 200 anchors.bottom: parent.bottom anchors.bottomMargin: 10 onFinished: passwordData.finished(); dataInput: priceInput buttonBackground: Item { property int index: 0 property bool pressed: false } } } Item { function doFocus() { description.forceActiveFocus(); } Column { width: parent.width - 20 x: 10 spacing: 10 Flowee.BitcoinAmountLabel { anchors.horizontalCenter: parent.horizontalCenter visible: value > 0 colorize: false value: request.amount font.pixelSize: instructions.font.pixelSize * 1.4 } PageTitledBox { width: parent.width title: qsTr("Description") Flowee.TextField { id: description width: parent.width onTotalTextChanged: request.message = totalText } } } } } // the "payment received" screen. Rectangle { id: feedback property bool hide: false // hide a partially paid popup anchors.fill: swipeView gradient: Gradient { GradientStop { position: 0.6 color: { var state = request.state; if (state === PaymentRequest.PaymentSeen || state === PaymentRequest.Unpaid) return palette.base if (state === PaymentRequest.DoubleSpentSeen) return mainWindow.errorRedBg return "#60b671" // in all other cases: green } Behavior on color { ColorAnimation {} } } GradientStop { position: 0.01 color: palette.base } } opacity: hide || request.state === PaymentRequest.Unpaid ? 0: 1 enabled: opacity > 0 visible: opacity > 0 // animating timer to indicate our checking the security of the transaction. // (i.e. waiting for the double spent proof) Flowee.Label { color: "green" text: "✔" // y: 60 y: -30 // basically avoid the xheight x: 30 opacity: { if (request.state === PaymentRequest.PaymentSeenOk || request.state === PaymentRequest.Confirmed) return 1; return 0; } font.pixelSize: 130 } // textual feedback Flowee.Label { text: qsTr("Payment Seen") + " (" + ((request.amountSeen / request.amount) * 100).toFixed(2) + " %)" y: 70 x: 20 opacity: request.state === PaymentRequest.PartiallyPaid ? 1 : 0 } Flowee.Label { id: feedbackLabel text: { var s = request.state; if (s === PaymentRequest.DoubleSpentSeen) // double-spent-proof received return qsTr("High risk transaction"); if (s === PaymentRequest.PartiallyPaid) return qsTr("Partially Paid"); if (s === PaymentRequest.PaymentSeen) return qsTr("Payment Seen"); if (s === PaymentRequest.PaymentSeenOk) return qsTr("Payment Accepted"); if (s === PaymentRequest.Confirmed) return qsTr("Payment Settled"); return "INTERNAL ERROR"; } width: parent.width - 40 anchors.horizontalCenter: parent.horizontalCenter y: 100 wrapMode: Text.WrapAtWordBoundaryOrAnywhere font.pointSize: 20 } Behavior on opacity { OpacityAnimator {} } Flowee.BigCloseButton { anchors.bottom: parent.bottom anchors.bottomMargin: 20 onClicked: { request.clear(); description.text = ""; priceInput.paymentBackend.paymentAmount = 0; request.account = portfolio.current; request.start(); feedback.hide = false; switchToTab(0); // go to the 'main' tab. } visible: request.state !== PaymentRequest.PartiallyPaid } Flowee.Button { anchors.right: parent.right anchors.rightMargin: 10 y: 160 text: qsTr("Continue") visible: request.state === PaymentRequest.PartiallyPaid onClicked: feedback.hide = true; } } }