/* * This file is part of the Flowee project * Copyright (C) 2023-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 "../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 /// 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(); } /// clears the password typed, if needed. function acceptedPassword() { pwdField.text = ""; } function takeFocus() { moveFocusTimer.start(); } // 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 (!switchButton.numericInput) { pwdField.selectAll(); pwdField.forceActiveFocus(); } } } Image { id: lockIcon source: "qrc:/lock" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); width: 60 height: 60 anchors.horizontalCenter: parent.horizontalCenter smooth: true y: parent.height > 700 ? 40 : 10 } Image { id: switchButton property bool numericInput: true width: numericInput ? 40 : 30 height: width anchors.right: parent.right source: "qrc:/" + (numericInput ? "keyboard" : "num-keyboard") + (Pay.useDarkSkin ? "-light.svg" : ".svg"); MouseArea { anchors.fill: parent onClicked: { keyboard.dataInput.editor.reset(); var newValue = !switchButton.numericInput; switchButton.numericInput = newValue; if (newValue === false) pwdField.forceActiveFocus(); } } } Flowee.Label { id: introText text: qsTr("Enter your wallet passcode") anchors.top: lockIcon.bottom anchors.topMargin: 20 horizontalAlignment: Text.AlignHCenter wrapMode: Text.WrapAtWordBoundaryOrAnywhere width: parent.width 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: 10 anchors.horizontalCenter: parent.horizontalCenter visible: switchButton.numericInput Repeater { id: repeater // take the number typed and turn it into an array of characters. model: { var inputString = keyboard.dataInput.editor.enteredString var answer = []; for (let i = 0; i < inputString.length; ++i) { answer.push(inputString.substr(i, 1)); } return answer; } Flowee.Label { text: { if (index !== keyboard.dataInput.editor.insertedIndex) return "∙" return modelData; } font.pixelSize: introText.font.pixelSize * 2 Timer { interval: 1000 running: index === keyboard.dataInput.editor.insertedIndex onTriggered: text = "∙" } } } } NumericKeyboardWidget { id: keyboard x: switchButton.numericInput ? 0 : 0 - parent.width hasSeparator: false width: parent.width anchors.top: pinPreview.top anchors.topMargin: introText.height * 2 // work around the fact that it takes less space when empty anchors.bottom: openButton.top anchors.bottomMargin: 10 onFinished: { var pwd = keyboard.dataInput.editor.enteredString; if (pwd !== "") { root.password = pwd; keyboard.dataInput.editor.reset(); root.passwordEntered(); } } dataInput: Item { 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 shake() { shaker.start(); } } Behavior on x { NumberAnimation { } } } Flowee.TextField { id: pwdField x: switchButton.numericInput ? parent.width + 30 : 0 visible: !switchButton.numericInput anchors.top: introText.bottom anchors.topMargin: 20 width: parent.width focus: switchButton.numericInput === false 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 x { NumberAnimation { } } } Flowee.Button { id: openButton visible: !switchButton.numericInput anchors.right: parent.right y: { if (switchButton.numericInput) return parent.height - height return pwdField.y + pwdField.height + 20; } text: qsTr("Open", "open wallet with PIN") onClicked: { var pwd = pwdField.text; if (pwd !== "") { root.password = pwd; keyboard.dataInput.editor.reset(); root.passwordEntered(); } } } }