358 lines
11 KiB
QML
358 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.FullKeyboard)
|
|
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 + Pay.screenInsets.height
|
|
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
|
|
anchors.bottomMargin: Pay.screenInsets.height
|
|
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 {} }
|
|
}
|
|
}
|