Files
pay/guis/mobile/RepeatPaymentDetails.qml
T
tomFlowee 2a387a72e7 Fix changing payment on finish-edit
When the user finishes with the comment edit field
we no longer change which amount is pinned
2025-09-05 17:31:07 +02:00

489 lines
22 KiB
QML

/*
* This file is part of the Flowee project
* Copyright (C) 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.Layouts
import "../Flowee" as Flowee
import "../Utils.js" as Utils
import Flowee.org.pay;
Page {
id: root
headerText: qsTr("Scheduled Payment")
// This is an instance of the C++ SavedPayment class (SavedPaymentsHandler.h)
required property QtObject savedPayment;
property Payment payment: savedPayment.payment;
property bool withScheduleButton: false
property var acceptHandler: function handler() { thePile.pop(); }
Flickable {
id: scrollPane
anchors.fill: parent
contentWidth: width
contentHeight: column.height
Column {
id: column
width: parent.width
PageTitledBox {
title: qsTr("Comment")
width: parent.width
EditableLabel {
text: root.payment.userComment
width: parent.width
onEdited: root.payment.userComment = text
}
}
PageTitledBox {
title: qsTr("Payment Amount")
id: amountsBox
width: parent.width
property var detail: {
let details = root.payment.details
for (let index in details) {
let detail = details[index];
if (detail.isOutput)
return detail;
}
return null;
}
Row {
spacing: 10
Rectangle {
property bool main: amountsBox.detail.fiatFollows
width: Math.max(priceBch.implicitWidth, 140)
height: 80
color: "#00000000"
radius: 6
border.width: 1.3
border.color: main ? palette.highlight : color
opacity: main ? 1 : 0.7
Image {
id: pinButton
y: -5
width: 28
height: 40
anchors.horizontalCenter: parent.horizontalCenter
visible: parent.main
source: "qrc:/pin-green.svg";
}
MouseArea {
anchors.fill: parent
onClicked: priceBch.forceActiveFocus()
}
Flowee.BitcoinValueField {
id: priceBch
value: amountsBox.detail.paymentAmount
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: pinButton.bottom
anchors.topMargin: 5
focus: false
onValueEdited: amountsBox.detail.paymentAmount = value
onActiveFocusChanged: {
if (activeFocus) {
amountsBox.detail.fiatFollows = true
numKeyboardWidget.start(money)
}
}
activeFocusOnTab: false
}
}
Rectangle {
property bool main: !amountsBox.detail.fiatFollows
width: Math.max(priceFiat.implicitWidth, 140)
height: 80
color: "#00000000"
radius: 6
border.width: 1.3
opacity: main ? 1 : 0.7
border.color: main ? palette.highlight : color
Image {
id: pinButton2
y: -5
width: 28
height: 40
anchors.horizontalCenter: parent.horizontalCenter
visible: parent.main
source: "qrc:/pin-green.svg";
transform: Scale { xScale: -1; yScale: 1; origin.x: 15 }
}
MouseArea {
anchors.fill: parent
onClicked: priceFiat.forceActiveFocus()
}
Flowee.FiatValueField {
id: priceFiat
value: amountsBox.detail.paymentAmountFiat
anchors.horizontalCenter: parent.horizontalCenter
anchors.top: pinButton2.bottom
anchors.topMargin: 5
focus: false
activeFocusOnTab: false
onValueEdited: amountsBox.detail.paymentAmountFiat = value
visible: Pay.isMainChain
onActiveFocusChanged: {
if (activeFocus) {
amountsBox.detail.fiatFollows = false
numKeyboardWidget.start(money)
}
}
}
}
}
NumericKeyboardWidget {
id: numKeyboardWidget
width: parent.width
dataInput: ourData
opacity: 0
visible: opacity > 0
function start(money) {
ourData.editor = money;
if (!numKeyboardWidget.visible) {
var pos = valueComment.mapToItem(scrollPane.contentItem, 0, implicitHeight) // bottom of keyboard
numKeyboardWidget.opacity = 1 // will make the item visible
scrollPane.contentY = 10 + pos.y - scrollPane.height
}
}
Item {
id: ourData
property QtObject editor: null// Item {}
function shake() {}
}
Behavior on opacity { NumberAnimation { } }
}
Flowee.Label {
id: valueComment
width: parent.width
text: qsTr("Pinned amount will be protected from exchange rate fluctuations.")
font.italic: true
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
focus: true
}
}
PageTitledBox {
title: qsTr("Planning")
width: parent.width
CalendarTextButton {
width: parent.width
text: qsTr("Next Payment") + ":"
date: root.savedPayment.next
onSelectedDateChanged: root.savedPayment.updateStartDate(selectedDate)
}
Repeater {
model: root.savedPayment.configs
Item {
width: column.width
height: planningPane.height
Column {
id: planningPane
width: parent.width
Flowee.RadioButton {
id: aRadioButton
focus: true
text: qsTr("By weekday")
checked: modelData.dayOfWeek !== -1
visible: index === 0 // don't repeat this for each config
width: parent.width
onClicked: root.savedPayment.switchToWeek();
}
Flowee.RadioButton {
text: qsTr("By day of month")
focus: true
checked: modelData.dayOfMonth !== -1
visible: index === 0// don't repeat this for each config
width: parent.width
onClicked: root.savedPayment.switchToMonth();
}
Flowee.Label {
id: startTimeLabel
x: aRadioButton.textAlignOffset
height: Math.max(50, contentHeight + 10)
verticalAlignment: Text.AlignVCenter
text: {
let loc = Qt.locale();
var day = modelData.dayOfWeek;
if (day >= 0) {
return loc.standaloneDayName(day, Locale.LongFormat);
}
day = modelData.dayOfMonth;
if (day > 0)
return loc.toString(modelData.next, loc.dateFormat(Locale.ShortFormat));
return "";
}
MouseArea {
width: root.width / 2
x: -5
y: -5
height: parent.height + 10
onClicked: picker.startDayPicker();
}
}
Flow {
id: repeatIntervalFlow
spacing: 6
width: parent.width - x
Flowee.Label {
id: repeatLabel
text: qsTr("Repeat Interval") + ":"
font.bold: true
height: Math.max(50, contentHeight + 10)
verticalAlignment: Text.AlignVCenter
MouseArea {
width: root.width - 10
height: parent.height
x: -5
onClicked: picker.startRepeatPicker();
}
}
Flowee.Label {
height: repeatLabel.height
verticalAlignment: Text.AlignVCenter
text: {
var week = modelData.dayOfWeek;
if (week >= 0) {
let interval = modelData.weekInterval
if (interval <= 1)
return qsTr("Every week", "repetition");
return qsTr("Once every %1 weeks", "repetition", interval).arg(interval);
}
var month = modelData.dayOfMonth
if (month >= 0) {
let interval = modelData.monthInterval
if (interval <= 0)
return qsTr("Every month", "repetition");
return qsTr("Once every %1 months", "repetition", interval).arg(interval);
}
return "";
}
}
}
CalendarTextButton {
width: parent.width
visible: index === 0 // don't repeat this for each config
text: qsTr("End Date") + ":"
date: root.payment.repeat.sunset
onSelectedDateChanged: root.payment.repeat.sunset = selectedDate
}
ListView {
id: calendarsList
width: parent.width
height: 90
orientation: ListView.Horizontal
model: 12
property QtObject config: modelData
spacing: 10
delegate: Item {
width: miniCalendar.width
height: miniCalendar.height
MiniCalendarWidget {
id: miniCalendar
month: {
var date = new Date(); // now
date.setDate(1);
date.setMonth(date.getMonth() + index);
return date;
}
highlights: {
var hls = [];
var source = calendarsList.config.predictedPayments
var thisMonth = month;
for (let date of source) {
if (date.getFullYear() === thisMonth.getFullYear()
&& date.getMonth() === thisMonth.getMonth())
hls.push(date.getDate());
}
return hls;
}
}
}
}
}
Flowee.CloseIcon {
visible: index > 0
scale: 0.6
anchors.right: parent.right
anchors.rightMargin: 20
anchors.top: parent.top
anchors.bottomMargin: startTimeLabel.height + 8
onClicked: root.savedPayment.remove(modelData)
}
Rectangle {
color: "#00000000"
radius: 15
width: 30
height: 30
anchors.right: parent.right
anchors.rightMargin: 20
anchors.top: parent.top
anchors.bottomMargin: startTimeLabel.height + 8
visible: index === 0
border.width: 1
border.color: palette.text
Rectangle {
color: palette.text
width: 1
height: parent.height * 0.5
anchors.centerIn: parent
}
Rectangle {
color: palette.text
height: 1
width: parent.width * 0.5
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: root.savedPayment.add()
}
}
Flowee.Dialog {
id: picker
property bool isDayPicker: true
function startDayPicker() {
isDayPicker = true;
open();
}
function startRepeatPicker() {
isDayPicker = false;
open();
}
property QtObject config: modelData
standardButtons: QQC2.DialogButtonBox.Cancel
contentComponent: Flickable {
width: picker.width - 40
height: Math.min(400, chooserColumn.height)
contentWidth: width
contentHeight: chooserColumn.height
clip: true
Column {
id: chooserColumn
width: parent.width
Repeater {
model: picker.isDayPicker ? (modelData.dayOfWeek >= 0 ? 7 : 31) : 12
Item {
width: picker.width - 40
height: chooserLabel.height + 12
Flowee.Label {
id: chooserLabel
anchors.centerIn: parent
text: {
if (!picker.isDayPicker || picker.config.dayOfMonth >= 0)
return "" + (modelData + 1);
return Qt.locale().standaloneDayName(modelData, Locale.LongFormat);
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (picker.isDayPicker) {
if (picker.config.dayOfMonth >= 0)
picker.config.dayOfMonth = modelData + 1;
else
picker.config.dayOfWeek = modelData;
}
else { // repeat interval picker
if (picker.config.dayOfMonth >= 0)
picker.config.monthInterval = modelData + 1;
else
picker.config.weekInterval = modelData + 1;
}
picker.close();
}
}
}
}
}
}
}
}
}
}
PageTitledBox {
title: qsTr("Wallet for Payment")
id: walletSelector
width: parent.width
visible: !portfolio.singleAccountSetup
AccountSelectorWidget {
onSelectedAccountChanged: payment.account = selectedAccount
startingAccount: root.payment.account
}
}
/*
TODO time of day for the payment
PageTitledBox {
title: qsTr("Advanced")
Flowee.Label {
text: "Fee per byte: " + root.payment.feePerByte
}
}
*/
Item { width: 1; height: 20; visible: scheduleButton.visible } // spacer
Flowee.BigButton {
id: scheduleButton
width: parent.width * 0.75
anchors.horizontalCenter: parent.horizontalCenter
isMainButton: true
text: qsTr("Schedule")
visible: root.withScheduleButton
onClicked: {
Pay.addRepeatPayment(root.payment);
root.acceptHandler();
}
}
Item { width: 1; height: 20 } // spacer
}
}
Keys.onPressed: (event)=> {
if ((event.key === Qt.Key_Escape || event.key === Qt.Key_Back)
&& numKeyboardWidget.visible) {
valueComment.forceActiveFocus(); // move focus away from the input widgets to avoid blinking cursor
numKeyboardWidget.opacity = 0;
event.accepted = true;
}
}
}