dfabcde813
The non-themed import basically is just a proxy using some auto-detection to find out which theme to use. As the app only uses the basic theme, this is what we'll import.
403 lines
13 KiB
QML
403 lines
13 KiB
QML
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (C) 2022-2025 Tom Zander <tom@flowee.org>
|
|
*
|
|
* 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.Controls.Basic as QQC2
|
|
import QtQuick.Particles
|
|
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
|
|
property alias verticalTab: swipeView.currentIndex
|
|
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: parent.active ? palette.highlight : palette.light
|
|
opacity: parent.active ? 0.15 : 1
|
|
}
|
|
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")
|
|
y: 10
|
|
opacity: editViewActive ? 0 : 0.5
|
|
Behavior on opacity { OpacityAnimator { } }
|
|
visible: root.showInstructions
|
|
}
|
|
Flowee.QRWidget {
|
|
id: qr
|
|
width: parent.width
|
|
y: root.showInstructions ? instructions.y + instructions.height + 15 : 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() { }
|
|
clip: true
|
|
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
|
|
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 {
|
|
clip: true
|
|
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
|
|
|
|
onVisibleChanged: {
|
|
if (visible) {
|
|
// move it to the screen that doesn't have a textfield
|
|
// to ensure that the virtual keyboard gets closed.
|
|
swipeView.currentIndex = 0;
|
|
emitter.pulse(1500)
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
|
|
ParticleSystem {
|
|
anchors.fill: parent
|
|
ImageParticle {
|
|
source: "qrc:/star.png"
|
|
alpha: 0.3
|
|
colorVariation: 1
|
|
}
|
|
Gravity {
|
|
magnitude: 32
|
|
angle: 90
|
|
}
|
|
Emitter {
|
|
id: emitter
|
|
x: parent.width/2
|
|
y: feedback.y + 20
|
|
emitRate: 600
|
|
lifeSpan: 20000
|
|
lifeSpanVariation: 3000
|
|
enabled: false
|
|
velocity: AngleDirection {
|
|
angle: 270
|
|
angleVariation: 90
|
|
magnitude: 64
|
|
magnitudeVariation: 40
|
|
}
|
|
size: 10
|
|
sizeVariation: 6
|
|
}
|
|
}
|
|
}
|