/* * This file is part of the Flowee project * Copyright (C) 2023-2025 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 "../Flowee" as Flowee import Flowee.org.pay; Item { id: root implicitWidth: 300 implicitHeight: 600 /// This will hold the password when the user is done typing it. property string password : ""; property alias buttonText: openButton.text property bool assumeDarkBackground: false /// Emitted when the user submits the password signal passwordEntered; /// in an onPasswordEntered callback, call this method /// to signify the password is incorrect. /// Otherwise just close this widget, or call acceptedPassword() function passwordIncorrect() { shaker.start(); moveFocusTimer.start(); keyboard.flashErrorFeedback(); smallKeyboard.flashErrorFeedback(); } /// clears the password typed, if needed. function acceptedPassword() { pwdField.text = ""; lockIcon.open = true; } function takeFocus() { moveFocusTimer.start(); lockIcon.open = false; } Item { id: passwordData property QtObject editor: Item { property string enteredString; property int insertedIndex: -1 function insertNumber(character) { if (enteredString.length >= 10) return false; insertedIndex = enteredString.length; enteredString = enteredString + character; return true; } function addSeparator() { return false; } function backspacePressed() { if (enteredString == "") return false; insertedIndex = -1; enteredString = enteredString.substring(0, enteredString.length - 1); return true; } function reset() { insertedIndex = -1; enteredString = ""; } } function finished() { var pwd = editor.enteredString; if (pwd !== "") { root.password = pwd; editor.reset(); root.passwordEntered(); } } function shake() { } } // we can't move focus from things like the onClicked handler of a button // therefore a little timer that does it very briefly afterwards is used. Timer { id: moveFocusTimer interval: 50 onTriggered: { if (Pay.unlockingKeyboard === FloweePay.FullKeyboard) { pwdField.selectAll(); pwdField.forceActiveFocus(); } } } Item { id: lockIcon property bool open: false anchors.horizontalCenter: parent.horizontalCenter y: parent.height > 700 ? 40 : 10 width: 60 height: 60 Item { clip: true width: 60 height: 21 rotation: lockIcon.open ? 20 : 0 transformOrigin: Item.Bottom Image { id: lockIconOne source: "qrc:/lock" + (Pay.useDarkSkin || assumeDarkBackground ? "-light.svg" : ".svg"); width: 60 height: 60 } } Item { clip: true y: 21 width: 60 height: 40 rotation: lockIcon.open ? -10 : 0 transformOrigin: Item.TopRight Image { source: lockIconOne.source y: -22 width: 60 height: 60 } } } Image { width: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 30 : 40 height: width anchors.right: parent.right source: { var s = "qrc:/"; if (Pay.unlockingKeyboard === FloweePay.BigNumbersKeyboard) s += "keyboard" // next one else s += "num-keyboard" return s + (Pay.useDarkSkin || assumeDarkBackground ? "-light.svg" : ".svg"); } MouseArea { anchors.fill: parent onClicked: { passwordData.editor.reset(); var cur = Pay.unlockingKeyboard; if (cur === FloweePay.BigNumbersKeyboard) var newValue = FloweePay.FullKeyboard; else newValue = cur + 1; Pay.unlockingKeyboard = newValue; if (newValue === FloweePay.FullKeyoard) pwdField.forceActiveFocus(); } } } Flowee.Label { id: introText text: qsTr("Enter your wallet passcode") anchors.top: lockIcon.bottom anchors.topMargin: 20 horizontalAlignment: Text.AlignHCenter width: parent.width wrapMode: Text.WrapAtWordBoundaryOrAnywhere color: assumeDarkBackground ? "#fcfcfc" : palette.text Flowee.ObjectShaker { id: shaker } // 'shake' to give feedback on mistakes } // Show the typed pin code, but as bullets as you type. Row { id: pinPreview anchors.top: introText.bottom anchors.topMargin: root.height > 700 ? 20 : 6 spacing: 6 anchors.horizontalCenter: parent.horizontalCenter visible: Pay.unlockingKeyboard !== FloweePay.FullKeyboard Flowee.Label { // for height text: " "; font.pixelSize: introText.font.pixelSize * 2 visible: repeater.model == 0 // 2 equals only!! } Repeater { id: repeater // take the number typed and turn it into an array of characters. model: { var inputString = passwordData.editor.enteredString var answer = []; for (let i = 0; i < inputString.length; ++i) { answer.push(inputString.substr(i, 1)); } return answer; } Rectangle { width: 40 height: parent.height color: !Pay.useDarkSkin && assumeDarkBackground ? palette.base : "#00000000" Flowee.Label { id: dot anchors.centerIn: parent text: { if (index !== passwordData.editor.insertedIndex) return "∙" return modelData; } font.pixelSize: introText.font.pixelSize * 2 Timer { interval: 1000 running: index === passwordData.editor.insertedIndex onTriggered: dot.text = "∙" } } } } } Rectangle { anchors.bottom: pinPreview.top width: parent.width / 10 * 8 color: mainWindow.floweeGreen height: 2 opacity: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 0 : 1 x: parent.width / 10 Behavior on opacity { NumberAnimation {} } } Rectangle { anchors.top: pinPreview.bottom width: parent.width / 10 * 8 color: mainWindow.floweeGreen x: parent.width / 10 height: 2 opacity: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 0 : 1 Behavior on opacity { NumberAnimation {} } } NumericKeyboardWidget { id: keyboard opacity: Pay.unlockingKeyboard === FloweePay.BigNumbersKeyboard ? 1 : 0 enabled: opacity > 0.1 hasSeparator: false width: parent.width anchors.top: pinPreview.top anchors.topMargin: introText.height * 2 anchors.bottom: parent.bottom anchors.bottomMargin: 40 dataInput: passwordData onFinished: passwordData.finished(); Behavior on opacity { NumberAnimation {} } } Flowee.TextField { id: pwdField opacity: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 1 : 0 enabled: opacity > 0.1 anchors.top: introText.bottom anchors.topMargin: 20 width: parent.width focus: visible inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhSensitiveDataTyped | (hideText ? Qt.ImhHiddenTextCharacters : Qt.ImhNone) echoMode: hideText ? TextInput.Password : TextInput.Normal property bool hideText: true onEditingFinished: openButton.clicked() Image { width: 14 height: 14 anchors.right: parent.right anchors.rightMargin: 5 anchors.verticalCenter: parent.verticalCenter source: { var state = (pwdField.hideText) ? "open" : "closed"; var skin = Pay.useDarkSkin ? "-light" : "" return "qrc:/eye-" + state + skin + ".png"; } MouseArea { width: parent.width + 50 // extend to right physical edge x: -15 y: 0 - parent.y - 5 height: pwdField.height + 10 onClicked: pwdField.hideText = !pwdField.hideText } } Behavior on opacity { NumberAnimation {} } } Flowee.Button { id: openButton opacity: Pay.unlockingKeyboard === FloweePay.FullKeyboard ? 1 : 0 enabled: opacity > 0.1 anchors.right: parent.right y: pwdField.y + pwdField.height + 20 text: qsTr("Open", "open wallet with PIN") onClicked: { var pwd = pwdField.text; if (pwd !== "") { root.password = pwd; passwordData.editor.reset(); root.passwordEntered(); } } Behavior on opacity { NumberAnimation {} } } Rectangle { width: parent.width + 20 x: -10 opacity: Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard ? 1 : 0 anchors.bottom: parent.bottom anchors.bottomMargin: -10 color: palette.base height: 225 Behavior on opacity { NumberAnimation {} } } NumericKeyboardWidget { id: smallKeyboard opacity: Pay.unlockingKeyboard === FloweePay.SmallNumbersKeyboard ? 1 : 0 enabled: opacity > 0.1 hasSeparator: false width: parent.width height: 200 anchors.bottom: parent.bottom dataInput: passwordData onFinished: passwordData.finished(); buttonBackground: Item { property int index: 0 property bool pressed: false Rectangle { anchors.fill: parent visible: parent.index === 11 color: palette.midlight opacity: 0.6 } } Behavior on opacity { NumberAnimation {} } } }