1
0
Fork 0
mirror of https://github.com/seiichiro0185/sailotp.git synced 2024-11-15 05:16:42 +00:00
harbour-sailotp/qml/pages/MainView.qml
Romain Tartière 8af1daaa39
Move Component.onCompleted at the end
This is used to trigger an action when the page has finished being built
and is now ready for use.  Instead of having this code at the end of the
definition of the SilicaListView at the end of the SilicaFlickable, put
it directly at the end of the Page itself.

In next commits, we are going to move code around, and this will make
maintainers life easier when reviewing changes by minimizing the size of
the diffs.
2024-09-29 18:57:50 -10:00

332 lines
12 KiB
QML

/*
* Copyright (c) 2014, Stefan Brand <seiichiro@seiichiro0185.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, this
* list of conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. The names of the contributors may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import QtQuick 2.0
import Sailfish.Silica 1.0
import "../lib/storage.js" as DB
import "../lib/crypto.js" as OTP
Page {
id: mainPage
allowedOrientations: Orientation.All
// This holds the time of the last update of the page as Unix Timestamp (in Milliseconds)
property double lastUpdated: 0
property double seconds_global: 0
property string searchTerm: ""
// Reload the List of OTPs from storage
function refreshOTPList() {
otpList.visible = false;
otpList.model = null; // Hack to prevent unaccessible pulley after List refresh
appWin.listModel.clear();
DB.getOTP();
refreshOTPValues();
otpList.model = appWin.listModel; // Hack to prevent unaccessible pulley after List refresh
otpList.visible = true;
}
// Calculate new OTPs for every entry
function refreshOTPValues() {
// get seconds from current Date
var curDate = new Date();
seconds_global = curDate.getSeconds() % 30
// Iterate over all List entries
for (var i=0; i<appWin.listModel.count; i++) {
if (appWin.listModel.get(i).type === "TOTP" || appWin.listModel.get(i).type === "TOTP_STEAM" ) {
// Take derivation into account if set
var seconds = (curDate.getSeconds() + appWin.listModel.get(i).diff) % appWin.listModel.get(i).period;
// Only update on full period Seconds or if last run of the Functions is more than 2s in the past (e.g. app was in background)
if (appWin.listModel.get(i).otp === "------" || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, appWin.listModel.get(i).type, appWin.listModel.get(i).len, appWin.listModel.get(i).diff, 0, appWin.listModel.get(i).period);
appWin.listModel.setProperty(i, "otp", curOTP);
} else if (appWin.coverType === "HOTP" && (curDate.getTime() - lastUpdated > 2000) && appWin.listModel.get(i).fav === 1) {
// If we are coming back from the CoverPage update OTP value if current favourite is HOTP
appWin.listModel.setProperty(i, "otp", appWin.coverOTP);
}
}
}
// Set lastUpdate property
lastUpdated = curDate.getTime();
}
// Reload OTP List on Return to the Page (to e.g. accomodate changed settings)
onStatusChanged: {
if (status === PageStatus.Activating) {
refreshOTPList();
if (searchTerm != "") {
for (var i = 0; i < appWin.listModel.count; i++) {
appWin.listModel.get(i).itemVisible = appWin.listModel.get(i).title.toString().toLowerCase().indexOf(searchTerm.toLowerCase()) > -1
}
}
}
}
Timer {
interval: 500
// Timer only runs when app is acitive and we have entries
running: Qt.application.active && appWin.listModel.count
repeat: true
onTriggered: refreshOTPValues();
}
SilicaFlickable {
anchors.fill: parent
PullDownMenu {
MenuItem {
text: qsTr("About")
onClicked: pageStack.push(Qt.resolvedUrl("About.qml"))
}
MenuItem {
text: qsTr("Settings")
visible: true
onClicked: pageStack.push(Qt.resolvedUrl("Settings.qml"))
}
MenuItem {
text: qsTr("Export / Import")
onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage, mode: "export"})
}
MenuItem {
text: qsTr("Add Token")
onClicked: pageStack.push(Qt.resolvedUrl("ScanOTP.qml"), {parentPage: mainPage})
}
}
SilicaListView {
id: otpList
anchors.fill: parent
model: appWin.listModel
width: parent.width
ViewPlaceholder {
enabled: otpList.count == 0
text: qsTr("Nothing here")
hintText: qsTr("Pull down to add a OTP")
}
header: Column {
width: parent.width
height: headerRow.height + searchRow.height
Row {
id: headerRow
height: Theme.itemSizeSmall
width: parent.width
ProgressBar {
id: updateProgress
anchors.topMargin: Theme.paddingLarge * 1.15
anchors.top: parent.top
anchors.bottom: parent.bottom
height: parent.height
width: parent.width * 0.65
maximumValue: 29
value: 29 - seconds_global
// Only show when there are enries
visible: appWin.listModel.count
}
PageHeader {
id: header
anchors.top: parent.top
height: Theme.itemSizeSmall
width: parent.width * 0.35
title: "SailOTP"
}
}
Row {
id: searchRow
width: parent.width
SearchField {
id: searchField
font.pixelSize: Theme.fontSizeMedium
width: parent.width
// This would be useful, but seems to break the button altogether. Perhaps it'll work later?
// canHide: true
EnterKey.enabled: false
inputMethodHints: Qt.ImhNoPredictiveText // Qt.ImhPreferUppercase | Qt.ImhNoAutoUppercase
placeholderText: qsTr("Search")
onTextChanged: {
searchTerm = searchField.text
for (var i = 0; i < appWin.listModel.count; i++) {
appWin.listModel.get(i).itemVisible = appWin.listModel.get(i).title.toString().toLowerCase().indexOf(searchField.text.toLowerCase()) > -1
}
}
}
}
}
delegate: ListItem {
id: otpListItem
menu: otpContextMenu
contentHeight: visible ? Theme.itemSizeMedium : 0
width: parent.width
visible: itemVisible
function remove() {
// Show 5s countdown, then delete from DB and List
remorseAction(qsTr("Deleting"), function() { DB.removeOTP(title, secret); appWin.listModel.remove(index) })
}
function moveEntry(direction, index) {
if (direction) {
appWin.listModel.move(index, index-1, 1);
} else {
appWin.listModel.move(index, index+1, 1);
}
for (var i=0; i<appWin.listModel.count; i++) {
DB.changeOTPSort(appWin.listModel.get(i).title, appWin.listModel.get(i).secret, i);
}
}
onClicked: {
if (settings.hideTokens) {
otpValue.visible = !otpValue.visible
} else if (settings.showQrDefaultAction) {
pageStack.push(Qt.resolvedUrl("QRPage.qml"), {paramQrsource: otp, paramLabel: title, paramQRId: index});
} else {
Clipboard.text = otp
notify.show(qsTr("Token for ") + title + qsTr(" copied to clipboard"), 3000);
}
}
ListView.onRemove: animateRemoval()
Rectangle {
id: listRow
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
IconButton {
icon.source: fav == 1 ? "image://theme/icon-m-favorite-selected" : "image://theme/icon-m-favorite"
anchors.left: parent.left
onClicked: {
if (fav == 0) {
DB.setFav(title, secret)
appWin.setCover(index)
if (type == "HOTP") appWin.coverOTP = otp
for (var i=0; i<appWin.listModel.count; i++) {
if (i != index) {
appWin.listModel.setProperty(i, "fav", 0);
} else {
appWin.listModel.setProperty(i, "fav", 1);
}
}
} else {
DB.resetFav(title, secret);
appWin.setCover(-1);
appWin.listModel.setProperty(index, "fav", 0);
}
}
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
Label {
id: otpLabel
text: model.title
color: Theme.secondaryColor
anchors.horizontalCenter: parent.horizontalCenter
}
Label {
id: otpValue
text: model.otp
color: Theme.highlightColor
font.pixelSize: Theme.fontSizeLarge
visible: !settings.hideTokens
anchors.horizontalCenter: parent.horizontalCenter
}
}
}
// Show an update button on HOTP type tokens
IconButton {
icon.source: "image://theme/icon-m-refresh"
anchors.right: parent.right
visible: type == "HOTP" ? true : false
onClicked: {
appWin.listModel.setProperty(index, "counter", DB.getCounter(title, secret, true));
appWin.listModel.setProperty(index, "otp", OTP.calcOTP(secret, "HOTP", len, 0, counter));
if (fav == 1) appWin.coverOTP = otp;
}
}
Component {
id: otpContextMenu
ContextMenu {
MenuItem {
text: qsTr("Copy to Clipboard")
visible: settings.hideTokens || settings.showQrDefaultAction
onClicked: {
Clipboard.text = otp
notify.show(qsTr("Token for ") + title + qsTr(" copied to clipboard"), 3000);
}
}
MenuItem {
text: qsTr("Show Token as QR-Code")
visible: !settings.showQrDefaultAction
onClicked: pageStack.push(Qt.resolvedUrl("QRPage.qml"), {paramQrsource: otp, paramLabel: title, paramQRId: index});
}
MenuItem {
text: qsTr("Move up")
visible: index > 0 ? true : false;
onClicked: moveEntry(1, index);
}
MenuItem {
text: qsTr("Move down")
visible: index < appWin.listModel.count - 1 ? true : false;
onClicked: moveEntry(0, index);
}
MenuItem {
text: qsTr("Edit")
onClicked: {
pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramLen: len, paramDiff: diff, paramCounter: DB.getCounter(title, secret, false), paramPeriod: period})
}
}
MenuItem {
text: qsTr("Delete")
onClicked: remove()
}
}
}
}
VerticalScrollDecorator{}
}
}
Component.onCompleted: {
// Load list of OTP-Entries
refreshOTPList();
}
}