2014-01-05 20:58:25 +00:00
|
|
|
/*
|
2014-02-16 20:41:36 +00:00
|
|
|
* Copyright (c) 2014, Stefan Brand <seiichiro@seiichiro0185.org>
|
2014-01-05 20:58:25 +00:00
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* Redistribution and use in source and binary forms, with or without modification,
|
|
|
|
* are permitted provided that the following conditions are met:
|
|
|
|
*
|
2014-02-01 14:38:09 +00:00
|
|
|
* 1. Redistributions of source code must retain the above copyright notice, this
|
2014-01-05 20:58:25 +00:00
|
|
|
* list of conditions and the following disclaimer.
|
|
|
|
*
|
|
|
|
* 2. Redistributions in binary form must reproduce the above copyright notice, this
|
2014-02-01 14:38:09 +00:00
|
|
|
* list of conditions and the following disclaimer in the documentation and/or other
|
2014-01-05 20:58:25 +00:00
|
|
|
* materials provided with the distribution.
|
2014-02-01 14:38:09 +00:00
|
|
|
*
|
|
|
|
* 3. The names of the contributors may not be used to endorse or promote products
|
2014-01-05 20:58:25 +00:00
|
|
|
* derived from this software without specific prior written permission.
|
|
|
|
*
|
2014-02-01 14:38:09 +00:00
|
|
|
* 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,
|
2014-01-05 20:58:25 +00:00
|
|
|
* 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 {
|
2014-01-06 19:08:16 +00:00
|
|
|
id: mainPage
|
|
|
|
|
2015-09-26 08:51:17 +00:00
|
|
|
allowedOrientations: Orientation.All
|
|
|
|
|
2014-02-01 14:38:09 +00:00
|
|
|
// This holds the time of the last update of the page as Unix Timestamp (in Milliseconds)
|
2014-01-10 18:20:52 +00:00
|
|
|
property double lastUpdated: 0
|
2014-01-06 19:08:16 +00:00
|
|
|
|
2014-01-10 18:20:52 +00:00
|
|
|
// Hand favorite over to the cover
|
2014-01-12 17:03:23 +00:00
|
|
|
function setCoverOTP(title, secret, type) {
|
2014-01-10 18:20:52 +00:00
|
|
|
appWin.coverTitle = title
|
|
|
|
appWin.coverSecret = secret
|
2014-01-12 17:03:23 +00:00
|
|
|
appWin.coverType = type
|
2014-05-25 09:03:19 +00:00
|
|
|
if (secret == "") {
|
2014-01-12 17:03:23 +00:00
|
|
|
appWin.coverOTP = "";
|
|
|
|
} else if (type == "HOTP") {
|
|
|
|
appWin.coverOTP = "------";
|
|
|
|
}
|
2014-01-10 18:20:52 +00:00
|
|
|
}
|
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
// Reload the List of OTPs from storage
|
|
|
|
function refreshOTPList() {
|
2014-01-09 21:12:55 +00:00
|
|
|
otpList.visible = false;
|
2014-07-13 10:03:02 +00:00
|
|
|
otpList.model = null; // Hack to prevent unaccessible pulley after List refresh
|
2014-02-09 09:50:58 +00:00
|
|
|
appWin.listModel.clear();
|
2014-01-06 19:08:16 +00:00
|
|
|
DB.getOTP();
|
|
|
|
refreshOTPValues();
|
2014-07-13 10:03:02 +00:00
|
|
|
otpList.model = appWin.listModel; // Hack to prevent unaccessible pulley after List refresh
|
2014-01-09 21:12:55 +00:00
|
|
|
otpList.visible = true;
|
2014-01-06 19:08:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Calculate new OTPs for every entry
|
|
|
|
function refreshOTPValues() {
|
2014-02-01 14:38:09 +00:00
|
|
|
// get seconds from current Date
|
|
|
|
var curDate = new Date();
|
2014-01-06 19:08:16 +00:00
|
|
|
var seconds = curDate.getSeconds();
|
|
|
|
|
2014-02-01 14:38:09 +00:00
|
|
|
// Iterate over all List entries
|
2014-02-09 09:50:58 +00:00
|
|
|
for (var i=0; i<appWin.listModel.count; i++) {
|
2015-12-06 17:47:13 +00:00
|
|
|
if (appWin.listModel.get(i).type == "TOTP" || appWin.listModel.get(i).type == "TOTP_STEAM" ) {
|
2014-01-12 17:03:23 +00:00
|
|
|
// Only update on full 30 / 60 Seconds or if last run of the Functions is more than 2s in the past (e.g. app was in background)
|
2014-02-09 09:50:58 +00:00
|
|
|
if (appWin.listModel.get(i).otp == "------" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
|
2015-12-06 17:47:13 +00:00
|
|
|
var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, appWin.listModel.get(i).type)
|
2014-02-09 09:50:58 +00:00
|
|
|
appWin.listModel.setProperty(i, "otp", curOTP);
|
2014-01-12 17:03:23 +00:00
|
|
|
}
|
2014-02-09 09:50:58 +00:00
|
|
|
} else if (appWin.coverType == "HOTP" && (curDate.getTime() - lastUpdated > 2000) && appWin.listModel.get(i).fav == 1) {
|
2014-01-12 17:03:23 +00:00
|
|
|
// If we are coming back from the CoverPage update OTP value if current favourite is HOTP
|
2014-02-09 09:50:58 +00:00
|
|
|
appWin.listModel.setProperty(i, "otp", appWin.coverOTP);
|
2014-01-06 19:08:16 +00:00
|
|
|
}
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|
|
|
|
|
2014-02-01 14:38:09 +00:00
|
|
|
// Update the Progressbar
|
2014-01-06 19:08:16 +00:00
|
|
|
updateProgress.value = 29 - (seconds % 30)
|
2014-02-01 14:38:09 +00:00
|
|
|
// Set lastUpdate property
|
2014-01-06 19:08:16 +00:00
|
|
|
lastUpdated = curDate.getTime();
|
|
|
|
}
|
|
|
|
|
|
|
|
Timer {
|
2014-01-12 17:03:23 +00:00
|
|
|
interval: 500
|
2014-01-09 21:12:55 +00:00
|
|
|
// Timer only runs when app is acitive and we have entries
|
2014-02-09 09:50:58 +00:00
|
|
|
running: Qt.application.active && appWin.listModel.count
|
2014-01-06 19:08:16 +00:00
|
|
|
repeat: true
|
|
|
|
onTriggered: refreshOTPValues();
|
|
|
|
}
|
|
|
|
|
|
|
|
SilicaFlickable {
|
|
|
|
anchors.fill: parent
|
|
|
|
|
|
|
|
PullDownMenu {
|
|
|
|
MenuItem {
|
2014-02-08 16:08:10 +00:00
|
|
|
text: qsTr("About")
|
2014-01-06 19:08:16 +00:00
|
|
|
onClicked: pageStack.push(Qt.resolvedUrl("About.qml"))
|
|
|
|
}
|
2014-02-01 11:59:39 +00:00
|
|
|
MenuItem {
|
2014-02-16 20:41:36 +00:00
|
|
|
text: qsTr("Export / Import")
|
2014-02-01 16:06:34 +00:00
|
|
|
onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage, mode: "export"})
|
2014-02-01 11:59:39 +00:00
|
|
|
}
|
2014-02-01 16:06:34 +00:00
|
|
|
MenuItem {
|
2014-02-08 16:08:10 +00:00
|
|
|
text: qsTr("Add Token")
|
2014-02-16 15:27:28 +00:00
|
|
|
onClicked: pageStack.push(Qt.resolvedUrl("ScanOTP.qml"), {parentPage: mainPage})
|
|
|
|
}
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
ProgressBar {
|
|
|
|
id: updateProgress
|
|
|
|
width: parent.width
|
|
|
|
maximumValue: 29
|
|
|
|
anchors.top: parent.top
|
2015-09-26 08:51:17 +00:00
|
|
|
anchors.topMargin: 36 * Theme.pixelRatio
|
2014-01-09 21:12:55 +00:00
|
|
|
// Only show when there are enries
|
2014-02-09 09:50:58 +00:00
|
|
|
visible: appWin.listModel.count
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|
|
|
|
|
2015-09-26 08:51:17 +00:00
|
|
|
|
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
SilicaListView {
|
|
|
|
id: otpList
|
2015-09-26 08:51:17 +00:00
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
header: PageHeader {
|
|
|
|
title: "SailOTP"
|
|
|
|
}
|
2015-09-26 08:51:17 +00:00
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
anchors.fill: parent
|
2014-02-09 09:50:58 +00:00
|
|
|
model: appWin.listModel
|
2014-01-06 19:08:16 +00:00
|
|
|
width: parent.width
|
2014-01-05 20:58:25 +00:00
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
ViewPlaceholder {
|
|
|
|
enabled: otpList.count == 0
|
2014-02-08 16:08:10 +00:00
|
|
|
text: qsTr("Nothing here")
|
|
|
|
hintText: qsTr("Pull down to add a OTP")
|
2014-01-06 19:08:16 +00:00
|
|
|
}
|
2014-01-05 20:58:25 +00:00
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
delegate: ListItem {
|
|
|
|
id: otpListItem
|
|
|
|
menu: otpContextMenu
|
|
|
|
contentHeight: Theme.itemSizeMedium
|
2014-01-09 21:12:55 +00:00
|
|
|
width: parent.width
|
2014-01-05 20:58:25 +00:00
|
|
|
|
2014-01-10 18:26:46 +00:00
|
|
|
function remove() {
|
|
|
|
// Show 5s countdown, then delete from DB and List
|
2014-02-09 09:50:58 +00:00
|
|
|
remorseAction(qsTr("Deleting"), function() { DB.removeOTP(title, secret); appWin.listModel.remove(index) })
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|
|
|
|
|
2014-05-24 13:42:52 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-10 18:26:46 +00:00
|
|
|
onClicked: {
|
|
|
|
Clipboard.text = otp
|
2014-02-08 16:08:10 +00:00
|
|
|
notify.show(qsTr("Token for ") + title + qsTr(" copied to clipboard"), 3000);
|
2014-01-10 18:26:46 +00:00
|
|
|
}
|
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
ListView.onRemove: animateRemoval()
|
|
|
|
Rectangle {
|
2014-01-09 21:12:55 +00:00
|
|
|
id: listRow
|
|
|
|
width: parent.width
|
2014-01-06 19:08:16 +00:00
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
|
2014-01-10 05:38:38 +00:00
|
|
|
IconButton {
|
|
|
|
icon.source: fav == 1 ? "image://theme/icon-m-favorite-selected" : "image://theme/icon-m-favorite"
|
2014-01-09 21:12:55 +00:00
|
|
|
anchors.left: parent.left
|
|
|
|
onClicked: {
|
2014-01-10 18:20:52 +00:00
|
|
|
if (fav == 0) {
|
|
|
|
DB.setFav(title, secret)
|
2014-02-09 09:50:58 +00:00
|
|
|
appWin.setCover(index)
|
2014-01-12 17:03:23 +00:00
|
|
|
if (type == "HOTP") appWin.coverOTP = otp
|
2014-02-09 09:50:58 +00:00
|
|
|
for (var i=0; i<appWin.listModel.count; i++) {
|
2014-01-10 18:20:52 +00:00
|
|
|
if (i != index) {
|
2014-02-09 09:50:58 +00:00
|
|
|
appWin.listModel.setProperty(i, "fav", 0);
|
2014-01-10 18:20:52 +00:00
|
|
|
} else {
|
2014-02-09 09:50:58 +00:00
|
|
|
appWin.listModel.setProperty(i, "fav", 1);
|
2014-01-10 18:20:52 +00:00
|
|
|
}
|
2014-01-09 21:12:55 +00:00
|
|
|
}
|
2014-01-10 18:20:52 +00:00
|
|
|
} else {
|
2014-07-13 10:03:02 +00:00
|
|
|
DB.resetFav(title, secret);
|
|
|
|
appWin.setCover(-1);
|
2014-02-09 09:50:58 +00:00
|
|
|
appWin.listModel.setProperty(index, "fav", 0);
|
2014-01-10 05:38:38 +00:00
|
|
|
}
|
2014-01-09 21:12:55 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Column {
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
|
|
|
|
Label {
|
2014-01-06 19:08:16 +00:00
|
|
|
id: otpLabel
|
|
|
|
text: model.title
|
|
|
|
color: Theme.secondaryColor
|
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
2014-01-09 21:12:55 +00:00
|
|
|
}
|
2014-01-06 19:08:16 +00:00
|
|
|
|
2014-01-09 21:12:55 +00:00
|
|
|
Label {
|
2014-01-06 19:08:16 +00:00
|
|
|
id: otpValue
|
|
|
|
text: model.otp
|
|
|
|
color: Theme.highlightColor
|
|
|
|
font.pixelSize: Theme.fontSizeLarge
|
2014-01-09 21:12:55 +00:00
|
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
|
|
}
|
2014-01-06 19:08:16 +00:00
|
|
|
}
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|
|
|
|
|
2014-01-12 17:03:23 +00:00
|
|
|
// Show an update button on HTOP-Type Tokens
|
|
|
|
IconButton {
|
|
|
|
icon.source: "image://theme/icon-m-refresh"
|
|
|
|
anchors.right: parent.right
|
|
|
|
visible: type == "HOTP" ? true : false
|
|
|
|
onClicked: {
|
2014-02-09 09:50:58 +00:00
|
|
|
appWin.listModel.setProperty(index, "counter", DB.getCounter(title, secret, true));
|
|
|
|
appWin.listModel.setProperty(index, "otp", OTP.calcOTP(secret, "HOTP", counter));
|
2014-01-12 17:03:23 +00:00
|
|
|
if (fav == 1) appWin.coverOTP = otp;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-06 19:08:16 +00:00
|
|
|
Component {
|
|
|
|
id: otpContextMenu
|
|
|
|
ContextMenu {
|
2014-05-24 13:42:52 +00:00
|
|
|
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);
|
|
|
|
}
|
2014-01-08 18:13:53 +00:00
|
|
|
MenuItem {
|
2014-02-08 16:08:10 +00:00
|
|
|
text: qsTr("Edit")
|
2014-01-08 18:13:53 +00:00
|
|
|
onClicked: {
|
2014-01-12 17:03:23 +00:00
|
|
|
pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramCounter: DB.getCounter(title, secret, false)})
|
2014-01-08 18:13:53 +00:00
|
|
|
}
|
|
|
|
}
|
2014-01-06 19:08:16 +00:00
|
|
|
MenuItem {
|
2014-02-08 16:08:10 +00:00
|
|
|
text: qsTr("Delete")
|
2014-01-06 19:08:16 +00:00
|
|
|
onClicked: remove()
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|
2014-01-06 19:08:16 +00:00
|
|
|
}
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|
2014-01-06 19:08:16 +00:00
|
|
|
}
|
|
|
|
VerticalScrollDecorator{}
|
|
|
|
|
|
|
|
Component.onCompleted: {
|
2014-02-01 14:38:09 +00:00
|
|
|
// Load list of OTP-Entries
|
2014-01-06 19:08:16 +00:00
|
|
|
refreshOTPList();
|
|
|
|
}
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|
2014-01-06 19:08:16 +00:00
|
|
|
}
|
2014-01-05 20:58:25 +00:00
|
|
|
}
|