b82bf5c753
This moves the creation of the portfolio to happen the moment we finished loading. (wallets were loaded either way) The networking is the part that now waits for the user to unlock before it does anything.
357 lines
11 KiB
QML
357 lines
11 KiB
QML
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (C) 2023-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 "../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 {} }
|
|
}
|
|
}
|