/* * Copyright (c) 2014, Stefan Brand * 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 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(); } Column { 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 model: appWin.listModel height: parent.height - updateProgress.height - searchField.height width: parent.width ViewPlaceholder { enabled: otpList.count == 0 text: qsTr("Nothing here") hintText: qsTr("Pull down to add a OTP") } header: PageHeader { title: "SailOTP" } 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 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{} } 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 } } } ProgressBar { id: updateProgress height: Theme.itemSizeSmall width: parent.width maximumValue: 29 value: 29 - seconds_global // Only show when there are enries visible: appWin.listModel.count } } Component.onCompleted: { // Load list of OTP-Entries refreshOTPList(); } }