mirror of
https://github.com/seiichiro0185/sailotp.git
synced 2024-11-22 07:39:42 +00:00
Version 0.6
Added internationalization support Added german translation Tokens can be switched on the Cover Some minor UI-tweaks
This commit is contained in:
parent
a19c48e5f3
commit
1e73388791
13 changed files with 720 additions and 104 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -2,3 +2,4 @@
|
||||||
*.pro.user
|
*.pro.user
|
||||||
*.pro.user.*
|
*.pro.user.*
|
||||||
rpm/harbour-sailotp.spec
|
rpm/harbour-sailotp.spec
|
||||||
|
i18n/*.qm
|
||||||
|
|
|
@ -35,3 +35,18 @@ OTHER_FILES += qml/harbour-sailotp.qml \
|
||||||
HEADERS += \
|
HEADERS += \
|
||||||
src/fileio.h
|
src/fileio.h
|
||||||
|
|
||||||
|
i18n.files = i18n/*.qm
|
||||||
|
i18n.path = /usr/share/$${TARGET}/i18n
|
||||||
|
|
||||||
|
INSTALLS += i18n
|
||||||
|
|
||||||
|
lupdate_only {
|
||||||
|
SOURCES = qml/*.qml \
|
||||||
|
qml/pages/*.qml \
|
||||||
|
qml/covers/*.qml \
|
||||||
|
qml/components/*.qml
|
||||||
|
|
||||||
|
TRANSLATIONS = i18n/de.ts \
|
||||||
|
i18n/en.ts
|
||||||
|
}
|
||||||
|
|
||||||
|
|
263
i18n/de.ts
Normal file
263
i18n/de.ts
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="de_DE">
|
||||||
|
<context>
|
||||||
|
<name>About</name>
|
||||||
|
<message>
|
||||||
|
<source>A Simple Sailfish OTP Generator<br />(RFC 6238/4226 compatible)</source>
|
||||||
|
<translation type="vanished">Ein einfacher Sailfish OTP-Generator<br/>(RFC 6238/4226-kompatibel)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<source>Copyright: Stefan Brand<br />License: BSD (3-clause)</source>
|
||||||
|
<translation type="vanished">Copyright: Stefan Brand<br/>Lizenz: BSD (3-Klausel)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/About.qml" line="57"/>
|
||||||
|
<source>A Simple Sailfish OTP Generator
|
||||||
|
(RFC 6238/4226 compatible)</source>
|
||||||
|
<translation>Ein einfacher Sailfish OTP-Generator
|
||||||
|
(RFC 6238/4226-kompatibel)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/About.qml" line="67"/>
|
||||||
|
<source>Copyright: Stefan Brand
|
||||||
|
License: BSD (3-clause)</source>
|
||||||
|
<translation>Copyright: Stefan Brand
|
||||||
|
Lizenz: BSD (3-Klausel)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/About.qml" line="88"/>
|
||||||
|
<source>SailOTP uses the following third party libs:</source>
|
||||||
|
<translation>SailOTP verwendet folgende externe Bibliotheken:</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>AddOTP</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="57"/>
|
||||||
|
<source>Save</source>
|
||||||
|
<translation>Speichern</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="57"/>
|
||||||
|
<source>Add</source>
|
||||||
|
<translation>Hinzufügen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="62"/>
|
||||||
|
<source>Type</source>
|
||||||
|
<translation>Typ</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="64"/>
|
||||||
|
<source>Time-based (TOTP)</source>
|
||||||
|
<translation>Zeitbasiert (TOTP)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="65"/>
|
||||||
|
<source>Counter-based (HOTP)</source>
|
||||||
|
<translation>Zählerbasiert (HOTP)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="71"/>
|
||||||
|
<source>Title</source>
|
||||||
|
<translation>Titel</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="72"/>
|
||||||
|
<source>Title for the OTP</source>
|
||||||
|
<translation>Titel für das Token</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="80"/>
|
||||||
|
<source>Secret (at least 16 characters)</source>
|
||||||
|
<translation>Schlüssel (mindestens 16 Zeichen)</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="82"/>
|
||||||
|
<source>Secret OTP Key</source>
|
||||||
|
<translation>Geheimer Schlüssel</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="90"/>
|
||||||
|
<source>Next Counter Value</source>
|
||||||
|
<translation>Nächster Zählerwert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="92"/>
|
||||||
|
<source>Next Value of the Counter</source>
|
||||||
|
<translation>Nächster Wert für den Zähler</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>ExportPage</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="61"/>
|
||||||
|
<source>File already exists, choose "Overwrite existing" to overwrite it.</source>
|
||||||
|
<translation>Datei existiert, aktiviere "Existierende überschreiben" um sie zu ersetzen.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="70"/>
|
||||||
|
<source>Given file does not exist!</source>
|
||||||
|
<translation>Gewählte Datei existiert nicht!</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="92"/>
|
||||||
|
<source>Export</source>
|
||||||
|
<translation>Export</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="92"/>
|
||||||
|
<source>Import</source>
|
||||||
|
<translation>Import</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="99"/>
|
||||||
|
<source>Filename</source>
|
||||||
|
<translation>Dateiname</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="100"/>
|
||||||
|
<source>File to import</source>
|
||||||
|
<translation>Aus Datei importieren</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="100"/>
|
||||||
|
<source>File to export</source>
|
||||||
|
<translation>In Datei exportieren</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="109"/>
|
||||||
|
<source>Overwrite existing</source>
|
||||||
|
<translation>Existierende überschreiben</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="115"/>
|
||||||
|
<source>Password</source>
|
||||||
|
<translation>Passwort</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="116"/>
|
||||||
|
<source>Password for the file</source>
|
||||||
|
<translation>Passwort für die Datei</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="125"/>
|
||||||
|
<source>Passwords don't match!</source>
|
||||||
|
<translation>Passwörter nicht identisch!</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="125"/>
|
||||||
|
<source>Passwords match!</source>
|
||||||
|
<translation>Passwörter identisch!</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="126"/>
|
||||||
|
<source>Repeated Password for the file</source>
|
||||||
|
<translation>Passwort wiederholen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="146"/>
|
||||||
|
<source>Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import.</source>
|
||||||
|
<translation>Hier können Tokens aus einer Datei importiert werden. Gib die Datei und das beim Export gewählte Passwort ein. Nach links ziehen um zu starten.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="162"/>
|
||||||
|
<source>Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export.</source>
|
||||||
|
<translation>Hier können Tokens in eine Datei exportiert werden. Die Datei wird mit AES-256-CBC verschlüsselt und Base64-kodiert. Wähle ein starkes Passwort, die Datei enthält die geheimen Schlüssel zur Erzeugung der Tokens für deine Accounts. Nach links ziehen um zu starten.</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="184"/>
|
||||||
|
<source>Error writing to file </source>
|
||||||
|
<translation>Fehler beim Schreiben der Datei</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="186"/>
|
||||||
|
<source>Token Database exported to </source>
|
||||||
|
<translation>Datenbank exportiert nach </translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="189"/>
|
||||||
|
<source>Could not encrypt tokens. Error: </source>
|
||||||
|
<translation>Fehler beim Verschlüsseln. Fehler: </translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="192"/>
|
||||||
|
<source>Could not read tokens from Database</source>
|
||||||
|
<translation>Datenbank konnte nicht gelesen werden</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="203"/>
|
||||||
|
<source>Tokens imported from </source>
|
||||||
|
<translation>Tokens importiert aus </translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="208"/>
|
||||||
|
<source>Unable to decrypt file, did you use the right password?</source>
|
||||||
|
<translation>Fehler beim entschlüsseln, falsches Passwort?</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="211"/>
|
||||||
|
<source>Could not read from file </source>
|
||||||
|
<translation>Datei konnte nicht gelesen werden</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>MainView</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="111"/>
|
||||||
|
<source>About</source>
|
||||||
|
<translation>Über</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="115"/>
|
||||||
|
<source>Export Token-DB</source>
|
||||||
|
<translation>Datenbank exportieren</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="119"/>
|
||||||
|
<source>Import Token-DB</source>
|
||||||
|
<translation>Datenbank importieren</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="123"/>
|
||||||
|
<source>Add Token</source>
|
||||||
|
<translation>Token hinzufügen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="149"/>
|
||||||
|
<source>Nothing here</source>
|
||||||
|
<translation>Hier ist nichts</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="150"/>
|
||||||
|
<source>Pull down to add a OTP</source>
|
||||||
|
<translation>Nach unten ziehen zum hinzufügen</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="161"/>
|
||||||
|
<source>Deleting</source>
|
||||||
|
<translation>Lösche</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="166"/>
|
||||||
|
<source>Token for </source>
|
||||||
|
<translation>Token für </translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="166"/>
|
||||||
|
<source> copied to clipboard</source>
|
||||||
|
<translation> kopiert</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="234"/>
|
||||||
|
<source>Edit</source>
|
||||||
|
<translation>Bearbeiten</translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="240"/>
|
||||||
|
<source>Delete</source>
|
||||||
|
<translation>Löschen</translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
253
i18n/en.ts
Normal file
253
i18n/en.ts
Normal file
|
@ -0,0 +1,253 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!DOCTYPE TS>
|
||||||
|
<TS version="2.1" language="en_GB">
|
||||||
|
<context>
|
||||||
|
<name>About</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/About.qml" line="57"/>
|
||||||
|
<source>A Simple Sailfish OTP Generator
|
||||||
|
(RFC 6238/4226 compatible)</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/About.qml" line="67"/>
|
||||||
|
<source>Copyright: Stefan Brand
|
||||||
|
License: BSD (3-clause)</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/About.qml" line="88"/>
|
||||||
|
<source>SailOTP uses the following third party libs:</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>AddOTP</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="57"/>
|
||||||
|
<source>Save</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="57"/>
|
||||||
|
<source>Add</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="62"/>
|
||||||
|
<source>Type</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="64"/>
|
||||||
|
<source>Time-based (TOTP)</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="65"/>
|
||||||
|
<source>Counter-based (HOTP)</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="71"/>
|
||||||
|
<source>Title</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="72"/>
|
||||||
|
<source>Title for the OTP</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="80"/>
|
||||||
|
<source>Secret (at least 16 characters)</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="82"/>
|
||||||
|
<source>Secret OTP Key</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="90"/>
|
||||||
|
<source>Next Counter Value</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/AddOTP.qml" line="92"/>
|
||||||
|
<source>Next Value of the Counter</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>ExportPage</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="61"/>
|
||||||
|
<source>File already exists, choose "Overwrite existing" to overwrite it.</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="70"/>
|
||||||
|
<source>Given file does not exist!</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="92"/>
|
||||||
|
<source>Export</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="92"/>
|
||||||
|
<source>Import</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="99"/>
|
||||||
|
<source>Filename</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="100"/>
|
||||||
|
<source>File to import</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="100"/>
|
||||||
|
<source>File to export</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="109"/>
|
||||||
|
<source>Overwrite existing</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="115"/>
|
||||||
|
<source>Password</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="116"/>
|
||||||
|
<source>Password for the file</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="125"/>
|
||||||
|
<source>Passwords don't match!</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="125"/>
|
||||||
|
<source>Passwords match!</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="126"/>
|
||||||
|
<source>Repeated Password for the file</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="146"/>
|
||||||
|
<source>Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import.</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="162"/>
|
||||||
|
<source>Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export.</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="184"/>
|
||||||
|
<source>Error writing to file </source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="186"/>
|
||||||
|
<source>Token Database exported to </source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="189"/>
|
||||||
|
<source>Could not encrypt tokens. Error: </source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="192"/>
|
||||||
|
<source>Could not read tokens from Database</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="203"/>
|
||||||
|
<source>Tokens imported from </source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="208"/>
|
||||||
|
<source>Unable to decrypt file, did you use the right password?</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/ExportPage.qml" line="211"/>
|
||||||
|
<source>Could not read from file </source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
<context>
|
||||||
|
<name>MainView</name>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="111"/>
|
||||||
|
<source>About</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="115"/>
|
||||||
|
<source>Export Token-DB</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="119"/>
|
||||||
|
<source>Import Token-DB</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="123"/>
|
||||||
|
<source>Add Token</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="149"/>
|
||||||
|
<source>Nothing here</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="150"/>
|
||||||
|
<source>Pull down to add a OTP</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="161"/>
|
||||||
|
<source>Deleting</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="166"/>
|
||||||
|
<source>Token for </source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="166"/>
|
||||||
|
<source> copied to clipboard</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="234"/>
|
||||||
|
<source>Edit</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
<message>
|
||||||
|
<location filename="../qml/pages/MainView.qml" line="240"/>
|
||||||
|
<source>Delete</source>
|
||||||
|
<translation></translation>
|
||||||
|
</message>
|
||||||
|
</context>
|
||||||
|
</TS>
|
|
@ -38,12 +38,7 @@ CoverBackground {
|
||||||
|
|
||||||
property double lastUpdated: 0
|
property double lastUpdated: 0
|
||||||
|
|
||||||
Timer {
|
function updateOTP() {
|
||||||
interval: 1000
|
|
||||||
// Timer runs only when cover is visible and favourite is set
|
|
||||||
running: !Qt.application.active && appWin.coverSecret != "" && appWin.coverType == "TOTP"
|
|
||||||
repeat: true
|
|
||||||
onTriggered: {
|
|
||||||
// get seconds from current Date
|
// get seconds from current Date
|
||||||
var curDate = new Date();
|
var curDate = new Date();
|
||||||
|
|
||||||
|
@ -60,6 +55,13 @@ CoverBackground {
|
||||||
|
|
||||||
lastUpdated = curDate.getTime();
|
lastUpdated = curDate.getTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
interval: 1000
|
||||||
|
// Timer runs only when cover is visible and favourite is set
|
||||||
|
running: !Qt.application.active && appWin.coverSecret != "" && appWin.coverType == "TOTP"
|
||||||
|
repeat: true
|
||||||
|
onTriggered: updateOTP();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show the SailOTP Logo
|
// Show the SailOTP Logo
|
||||||
|
@ -79,7 +81,6 @@ CoverBackground {
|
||||||
Label {
|
Label {
|
||||||
text: appWin.coverTitle
|
text: appWin.coverTitle
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: Theme.secondaryColor
|
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
id: lOTP
|
id: lOTP
|
||||||
|
@ -91,11 +92,28 @@ CoverBackground {
|
||||||
}
|
}
|
||||||
// CoverAction to update a HOTP-Token, only visible for HOTP-Type Tokens
|
// CoverAction to update a HOTP-Token, only visible for HOTP-Type Tokens
|
||||||
CoverActionList {
|
CoverActionList {
|
||||||
enabled: appWin.coverType == "HOTP" ? true : false
|
|
||||||
CoverAction {
|
CoverAction {
|
||||||
iconSource: "image://theme/icon-m-refresh"
|
iconSource: appWin.coverType == "HOTP" ? "image://theme/icon-cover-refresh" : "image://theme/icon-cover-previous"
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
|
if (appWin.coverType == "HOTP") {
|
||||||
appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "HOTP", DB.getCounter(appWin.coverTitle, appWin.coverSecret, true));
|
appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "HOTP", DB.getCounter(appWin.coverTitle, appWin.coverSecret, true));
|
||||||
|
} else {
|
||||||
|
var index = appWin.coverIndex - 1
|
||||||
|
if (index < 0) index = appWin.listModel.count - 1
|
||||||
|
appWin.setCover(index);
|
||||||
|
DB.setFav(appWin.coverTitle, appWin.coverSecret)
|
||||||
|
if (appWin.coverType == "TOTP") updateOTP();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CoverAction {
|
||||||
|
iconSource: "image://theme/icon-cover-next"
|
||||||
|
onTriggered: {
|
||||||
|
var index = appWin.coverIndex + 1
|
||||||
|
if (index >= appWin.listModel.count) index = 0
|
||||||
|
appWin.setCover(index);
|
||||||
|
DB.setFav(appWin.coverTitle, appWin.coverSecret)
|
||||||
|
if (appWin.coverType == "TOTP") updateOTP();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,15 +37,52 @@ ApplicationWindow
|
||||||
id: appWin
|
id: appWin
|
||||||
|
|
||||||
// Properties to pass values between MainPage and Cover
|
// Properties to pass values between MainPage and Cover
|
||||||
|
property alias listModel: otpListModel
|
||||||
property string coverTitle: "SailOTP"
|
property string coverTitle: "SailOTP"
|
||||||
property string coverSecret: ""
|
property string coverSecret: ""
|
||||||
property string coverType: ""
|
property string coverType: ""
|
||||||
property string coverOTP: "------"
|
property string coverOTP: "------"
|
||||||
|
property int coverIndex: 0
|
||||||
|
|
||||||
|
// Global Listmodel for Tokens
|
||||||
|
ListModel { id: otpListModel }
|
||||||
|
|
||||||
|
// Global Component for showing notification banners
|
||||||
|
NotifyBanner { id: notify }
|
||||||
|
|
||||||
|
// Add an entry to the list
|
||||||
|
function appendOTP(title, secret, type, counter, fav) {
|
||||||
|
listModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "otp": "------"});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the OTP shown on the Cover
|
||||||
|
function setCover(index) {
|
||||||
|
if (index >= 0 && index < listModel.count) {
|
||||||
|
coverTitle = listModel.get(index).title;
|
||||||
|
coverSecret = listModel.get(index).secret;
|
||||||
|
coverType = listModel.get(index).type;
|
||||||
|
coverIndex = index;
|
||||||
|
if (coverType == "TOTP") { coverOTP = "------"; } else { coverOTP = listModel.get(index).otp; }
|
||||||
|
for (var i=0; i<listModel.count; i++) {
|
||||||
|
if (i != index) {
|
||||||
|
listModel.setProperty(i, "fav", 0);
|
||||||
|
} else {
|
||||||
|
listModel.setProperty(i, "fav", 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
coverTitle = "SailOTP";
|
||||||
|
coverSecret = "";
|
||||||
|
coverType = "";
|
||||||
|
coverIndex = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
NotifyBanner { id: notify }
|
NotifyBanner { id: notify }
|
||||||
|
|
||||||
initialPage: Component { MainView { } }
|
initialPage: Component { MainView { } }
|
||||||
cover: Qt.resolvedUrl("cover/CoverPage.qml")
|
cover: Qt.resolvedUrl("cover/CoverPage.qml")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -65,8 +65,8 @@ function getOTP() {
|
||||||
function(tx) {
|
function(tx) {
|
||||||
var res = tx.executeSql("select * from OTPStorage;");
|
var res = tx.executeSql("select * from OTPStorage;");
|
||||||
for (var i=0; i < res.rows.length; i++) {
|
for (var i=0; i < res.rows.length; i++) {
|
||||||
mainPage.appendOTP(res.rows.item(i).title, res.rows.item(i).secret, res.rows.item(i).type, res.rows.item(i).counter, res.rows.item(i).fav);
|
appWin.appendOTP(res.rows.item(i).title, res.rows.item(i).secret, res.rows.item(i).type, res.rows.item(i).counter, res.rows.item(i).fav);
|
||||||
if (res.rows.item(i).fav) mainPage.setCoverOTP(res.rows.item(i).title, res.rows.item(i).secret, res.rows.item(i).type);
|
if (res.rows.item(i).fav) appWin.setCover(i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,36 +37,40 @@ Page {
|
||||||
id: logo
|
id: logo
|
||||||
source: "../sailotp.png"
|
source: "../sailotp.png"
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
y: 200
|
y: 150
|
||||||
}
|
}
|
||||||
Label {
|
Label {
|
||||||
id: name
|
id: name
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
y: 320
|
y: 270
|
||||||
font.bold: true
|
font.bold: true
|
||||||
text: "SailOTP " + Qt.application.version
|
text: "SailOTP " + Qt.application.version
|
||||||
}
|
}
|
||||||
Text {
|
TextArea {
|
||||||
id: desc
|
id: desc
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: name.bottom
|
anchors.top: name.bottom
|
||||||
anchors.topMargin: 20
|
anchors.topMargin: 20
|
||||||
text: "A Simple Sailfish OTP Generator<br />(RFC 6238/4226 compatible)"
|
width: parent.width
|
||||||
|
horizontalAlignment: TextEdit.Center
|
||||||
|
readOnly: true
|
||||||
|
text: qsTr("A Simple Sailfish OTP Generator\n(RFC 6238/4226 compatible)")
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
Text {
|
TextArea {
|
||||||
id: copyright
|
id: copyright
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: desc.bottom
|
anchors.top: desc.bottom
|
||||||
anchors.topMargin: 20
|
width: parent.width
|
||||||
text: "Copyright: Stefan Brand<br />License: BSD (3-clause)"
|
horizontalAlignment: TextEdit.Center
|
||||||
|
readOnly: true
|
||||||
|
text: qsTr("Copyright: Stefan Brand\nLicense: BSD (3-clause)")
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
id: homepage
|
id: homepage
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.top: copyright.bottom
|
anchors.top: copyright.bottom
|
||||||
anchors.topMargin: 20
|
|
||||||
text: "<a href=\"https://www.seiichiro0185.org/gitlab/seiichiro0185/harbour-sailotp.git\">Source Code</a>"
|
text: "<a href=\"https://www.seiichiro0185.org/gitlab/seiichiro0185/harbour-sailotp.git\">Source Code</a>"
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Qt.openUrlExternally("https://www.seiichiro0185.org/gitlab/seiichiro0185/harbour-sailotp.git")
|
Qt.openUrlExternally("https://www.seiichiro0185.org/gitlab/seiichiro0185/harbour-sailotp.git")
|
||||||
|
@ -81,7 +85,7 @@ Page {
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
horizontalAlignment: TextEdit.Center
|
horizontalAlignment: TextEdit.Center
|
||||||
readOnly: true
|
readOnly: true
|
||||||
text: "SailOTP uses the following third party libs:\n\nhttp://caligatio.github.io/jsSHA/\nhttps://github.com/mdp/gibberish-aes"
|
text: qsTr("SailOTP uses the following third party libs:")+"\n\nhttp://caligatio.github.io/jsSHA/\nhttps://github.com/mdp/gibberish-aes"
|
||||||
color: "white"
|
color: "white"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,45 +54,56 @@ Dialog {
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
DialogHeader {
|
DialogHeader {
|
||||||
acceptText: paramLabel != "" ? "Save" : "Add"
|
acceptText: paramLabel != "" ? qsTr("Save") : qsTr("Add")
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboBox {
|
ComboBox {
|
||||||
id: typeSel
|
id: typeSel
|
||||||
label: "Type"
|
label: qsTr("Type")
|
||||||
menu: ContextMenu {
|
menu: ContextMenu {
|
||||||
MenuItem { text: "Time-based (TOTP)"; onClicked: { paramType = "TOTP" } }
|
MenuItem { text: qsTr("Time-based (TOTP)"); onClicked: { paramType = "TOTP" } }
|
||||||
MenuItem { text: "Counter-based (HOTP)"; onClicked: { paramType = "HOTP" } }
|
MenuItem { text: qsTr("Counter-based (HOTP)"); onClicked: { paramType = "HOTP" } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TextField {
|
TextField {
|
||||||
id: otpLabel
|
id: otpLabel
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Title"
|
label: qsTr("Title")
|
||||||
placeholderText: "Title for the OTP"
|
placeholderText: qsTr("Title for the OTP")
|
||||||
text: paramLabel != "" ? paramLabel : ""
|
text: paramLabel != "" ? paramLabel : ""
|
||||||
focus: true
|
focus: true
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
|
||||||
|
EnterKey.enabled: text.length > 0
|
||||||
|
EnterKey.iconSource: "image://theme/icon-m-enter-next"
|
||||||
|
EnterKey.onClicked: otpSecret.focus = true
|
||||||
}
|
}
|
||||||
TextField {
|
TextField {
|
||||||
id: otpSecret
|
id: otpSecret
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Secret (at least 16 characters)"
|
label: qsTr("Secret (at least 16 characters)")
|
||||||
text: paramKey != "" ? paramKey : ""
|
text: paramKey != "" ? paramKey : ""
|
||||||
placeholderText: "Secret OTP Key"
|
placeholderText: qsTr("Secret OTP Key")
|
||||||
focus: true
|
focus: true
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
|
||||||
|
EnterKey.enabled: text.length > 15
|
||||||
|
EnterKey.iconSource: paramType == "TOTP" ? "image://theme/icon-m-enter-accept" : "image://theme/icon-m-enter-next"
|
||||||
|
EnterKey.onClicked: paramType == "TOTP" ? addOTP.accept() : otpCounter.focus = true
|
||||||
}
|
}
|
||||||
TextField {
|
TextField {
|
||||||
id: otpCounter
|
id: otpCounter
|
||||||
width: parent.width
|
width: parent.width
|
||||||
visible: paramType == "HOTP" ? true : false
|
visible: paramType == "HOTP" ? true : false
|
||||||
label: "Next Counter Value"
|
label: qsTr("Next Counter Value")
|
||||||
text: paramCounter
|
text: paramCounter
|
||||||
placeholderText: "Next Value of the Counter"
|
placeholderText: qsTr("Next Value of the Counter")
|
||||||
focus: true
|
focus: true
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
validator: IntValidator { bottom: 0 }
|
validator: IntValidator { bottom: 0 }
|
||||||
|
|
||||||
|
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
|
||||||
|
EnterKey.onClicked: addOTP.accept()
|
||||||
}
|
}
|
||||||
Component.onCompleted: { typeSel.currentIndex = paramType == "HOTP" ? 1 : 0 }
|
Component.onCompleted: { typeSel.currentIndex = paramType == "HOTP" ? 1 : 0 }
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,7 @@ Dialog {
|
||||||
function checkFileName(file) {
|
function checkFileName(file) {
|
||||||
if (mode == "export") {
|
if (mode == "export") {
|
||||||
if (exportFile.exists(file) && !fileOverwrite.checked) {
|
if (exportFile.exists(file) && !fileOverwrite.checked) {
|
||||||
notify.show("File already exists, choose \"Overwrite existing\" to overwrite it.", 4000);
|
notify.show(qsTr("File already exists, choose \"Overwrite existing\" to overwrite it."), 4000);
|
||||||
return(false)
|
return(false)
|
||||||
} else {
|
} else {
|
||||||
return(true)
|
return(true)
|
||||||
|
@ -67,7 +67,7 @@ Dialog {
|
||||||
if (exportFile.exists(file)) {
|
if (exportFile.exists(file)) {
|
||||||
return(true)
|
return(true)
|
||||||
} else {
|
} else {
|
||||||
notify.show("Given file does not exist!", 4000);
|
notify.show(qsTr("Given file does not exist!"), 4000);
|
||||||
return(false)
|
return(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,45 +89,57 @@ Dialog {
|
||||||
Column {
|
Column {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
DialogHeader {
|
DialogHeader {
|
||||||
acceptText: mode == "export" ? "Export" : "Import"
|
acceptText: mode == "export" ? qsTr("Export") : qsTr("Import")
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: fileName
|
id: fileName
|
||||||
width: parent.width
|
width: parent.width
|
||||||
text: mode == "export" ? creFileName() : XDG_HOME_DIR + "/";
|
text: mode == "export" ? creFileName() : XDG_HOME_DIR + "/";
|
||||||
label: "Filename"
|
label: qsTr("Filename")
|
||||||
placeholderText: mode == "import" ? "File to import" : "File to export"
|
placeholderText: mode == "import" ? qsTr("File to import") : qsTr("File to export")
|
||||||
focus: true
|
focus: true
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
|
||||||
|
EnterKey.enabled: text.length > 0
|
||||||
|
EnterKey.iconSource: "image://theme/icon-m-enter-next"
|
||||||
|
EnterKey.onClicked: filePassword.focus = true
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSwitch {
|
TextSwitch {
|
||||||
id: fileOverwrite
|
id: fileOverwrite
|
||||||
checked: false
|
checked: false
|
||||||
visible: mode == "export"
|
visible: mode == "export"
|
||||||
text: "Overwrite existing"
|
text: qsTr("Overwrite existing")
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: filePassword
|
id: filePassword
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Password"
|
label: qsTr("Password")
|
||||||
placeholderText: "Password for the file"
|
placeholderText: qsTr("Password for the file")
|
||||||
echoMode: TextInput.Password
|
echoMode: TextInput.Password
|
||||||
focus: true
|
focus: true
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
|
||||||
|
EnterKey.enabled: text.length > 0
|
||||||
|
EnterKey.iconSource: mode == "export" ? "image://theme/icon-m-enter-next" : "image://theme/icon-m-enter-accept"
|
||||||
|
EnterKey.onClicked: mode == "export" ? filePasswordCheck.focus = true : exportPage.accept()
|
||||||
}
|
}
|
||||||
|
|
||||||
TextField {
|
TextField {
|
||||||
id: filePasswordCheck
|
id: filePasswordCheck
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: (filePassword.text != filePasswordCheck.text && filePassword.text.length > 0) ? "Passwords don't match!" : "Passwords match!"
|
label: (filePassword.text != filePasswordCheck.text && filePassword.text.length > 0) ? qsTr("Passwords don't match!") : qsTr("Passwords match!")
|
||||||
placeholderText: "Repeated Password for the file"
|
placeholderText: qsTr("Repeated Password for the file")
|
||||||
visible: mode == "export"
|
visible: mode == "export"
|
||||||
echoMode: TextInput.Password
|
echoMode: TextInput.Password
|
||||||
focus: true
|
focus: true
|
||||||
horizontalAlignment: TextInput.AlignLeft
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
|
||||||
|
EnterKey.enabled: filePassword.text == filePasswordCheck.text && filePassword.text.length > 0
|
||||||
|
EnterKey.iconSource: "image://theme/icon-m-enter-accept"
|
||||||
|
EnterKey.onClicked: exportPage.accept()
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
@ -143,7 +155,7 @@ Dialog {
|
||||||
color: Theme.secondaryColor
|
color: Theme.secondaryColor
|
||||||
|
|
||||||
visible: mode == "import"
|
visible: mode == "import"
|
||||||
text: "Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import."
|
text: qsTr("Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import.")
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
|
@ -159,7 +171,7 @@ Dialog {
|
||||||
color: Theme.secondaryColor
|
color: Theme.secondaryColor
|
||||||
|
|
||||||
visible: mode == "export"
|
visible: mode == "export"
|
||||||
text: "Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export."
|
text: qsTr("Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -181,15 +193,15 @@ Dialog {
|
||||||
try {
|
try {
|
||||||
chipherText = Gibberish.AES.enc(plainText, filePassword.text);
|
chipherText = Gibberish.AES.enc(plainText, filePassword.text);
|
||||||
if (!exportFile.write(chipherText)) {
|
if (!exportFile.write(chipherText)) {
|
||||||
notify.show("Error writing to file "+ fileName.text, 4000);
|
notify.show(qsTr("Error writing to file ")+ fileName.text, 4000);
|
||||||
} else {
|
} else {
|
||||||
notify.show("Token Database exported to "+ fileName.text, 4000);
|
notify.show(qsTr("Token Database exported to ")+ fileName.text, 4000);
|
||||||
}
|
}
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
notify.show("Could not encrypt tokens. Error: ", 4000);
|
notify.show(qsTr("Could not encrypt tokens. Error: "), 4000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notify.show("Could not read tokens from Database", 4000);
|
notify.show(qsTr("Could not read tokens from Database"), 4000);
|
||||||
}
|
}
|
||||||
} else if(mode == "import") {
|
} else if(mode == "import") {
|
||||||
// Import Tokens from File
|
// Import Tokens from File
|
||||||
|
@ -200,15 +212,15 @@ Dialog {
|
||||||
var errormsg = ""
|
var errormsg = ""
|
||||||
plainText = Gibberish.AES.dec(chipherText, filePassword.text);
|
plainText = Gibberish.AES.dec(chipherText, filePassword.text);
|
||||||
if (DB.json2db(plainText, errormsg)) {
|
if (DB.json2db(plainText, errormsg)) {
|
||||||
notify.show("Tokens imported from "+ fileName.text, 4000);
|
notify.show(qsTr("Tokens imported from ")+ fileName.text, 4000);
|
||||||
} else {
|
} else {
|
||||||
notify.show(errormsg, 4000);
|
notify.show(errormsg, 4000);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
notify.show("Unable to decrypt file, did you use the right password?", 4000);
|
notify.show(qsTr("Unable to decrypt file, did you use the right password?"), 4000);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
notify.show("Could not read from file " + fileName.text, 4000);
|
notify.show(qsTr("Could not read from file ") + fileName.text, 4000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,18 +36,9 @@ import "../lib/crypto.js" as OTP
|
||||||
Page {
|
Page {
|
||||||
id: mainPage
|
id: mainPage
|
||||||
|
|
||||||
ListModel {
|
|
||||||
id: otpListModel
|
|
||||||
}
|
|
||||||
|
|
||||||
// This holds the time of the last update of the page as Unix Timestamp (in Milliseconds)
|
// This holds the time of the last update of the page as Unix Timestamp (in Milliseconds)
|
||||||
property double lastUpdated: 0
|
property double lastUpdated: 0
|
||||||
|
|
||||||
// Add an entry to the list
|
|
||||||
function appendOTP(title, secret, type, counter, fav) {
|
|
||||||
otpListModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "otp": "------"});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hand favorite over to the cover
|
// Hand favorite over to the cover
|
||||||
function setCoverOTP(title, secret, type) {
|
function setCoverOTP(title, secret, type) {
|
||||||
appWin.coverTitle = title
|
appWin.coverTitle = title
|
||||||
|
@ -63,7 +54,7 @@ Page {
|
||||||
// Reload the List of OTPs from storage
|
// Reload the List of OTPs from storage
|
||||||
function refreshOTPList() {
|
function refreshOTPList() {
|
||||||
otpList.visible = false;
|
otpList.visible = false;
|
||||||
otpListModel.clear();
|
appWin.listModel.clear();
|
||||||
DB.getOTP();
|
DB.getOTP();
|
||||||
refreshOTPValues();
|
refreshOTPValues();
|
||||||
otpList.visible = true;
|
otpList.visible = true;
|
||||||
|
@ -76,16 +67,16 @@ Page {
|
||||||
var seconds = curDate.getSeconds();
|
var seconds = curDate.getSeconds();
|
||||||
|
|
||||||
// Iterate over all List entries
|
// Iterate over all List entries
|
||||||
for (var i=0; i<otpListModel.count; i++) {
|
for (var i=0; i<appWin.listModel.count; i++) {
|
||||||
if (otpListModel.get(i).type == "TOTP") {
|
if (appWin.listModel.get(i).type == "TOTP") {
|
||||||
// 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)
|
// 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)
|
||||||
if (otpListModel.get(i).otp == "------" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
|
if (appWin.listModel.get(i).otp == "------" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) {
|
||||||
var curOTP = OTP.calcOTP(otpListModel.get(i).secret, "TOTP")
|
var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, "TOTP")
|
||||||
otpListModel.setProperty(i, "otp", curOTP);
|
appWin.listModel.setProperty(i, "otp", curOTP);
|
||||||
}
|
}
|
||||||
} else if (appWin.coverType == "HOTP" && (curDate.getTime() - lastUpdated > 2000) && otpListModel.get(i).fav == 1) {
|
} 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
|
// If we are coming back from the CoverPage update OTP value if current favourite is HOTP
|
||||||
otpListModel.setProperty(i, "otp", appWin.coverOTP);
|
appWin.listModel.setProperty(i, "otp", appWin.coverOTP);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,7 +89,7 @@ Page {
|
||||||
Timer {
|
Timer {
|
||||||
interval: 500
|
interval: 500
|
||||||
// Timer only runs when app is acitive and we have entries
|
// Timer only runs when app is acitive and we have entries
|
||||||
running: Qt.application.active && otpListModel.count
|
running: Qt.application.active && appWin.listModel.count
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: refreshOTPValues();
|
onTriggered: refreshOTPValues();
|
||||||
}
|
}
|
||||||
|
@ -108,19 +99,19 @@ Page {
|
||||||
|
|
||||||
PullDownMenu {
|
PullDownMenu {
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "About"
|
text: qsTr("About")
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("About.qml"))
|
onClicked: pageStack.push(Qt.resolvedUrl("About.qml"))
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Export Token-DB"
|
text: qsTr("Export Token-DB")
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage, mode: "export"})
|
onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage, mode: "export"})
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Import Token-DB"
|
text: qsTr("Import Token-DB")
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage, mode: "import"})
|
onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage, mode: "import"})
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Add Token"
|
text: qsTr("Add Token")
|
||||||
onClicked: pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage})
|
onClicked: pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +123,7 @@ Page {
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.topMargin: 48
|
anchors.topMargin: 48
|
||||||
// Only show when there are enries
|
// Only show when there are enries
|
||||||
visible: otpListModel.count
|
visible: appWin.listModel.count
|
||||||
}
|
}
|
||||||
|
|
||||||
SilicaListView {
|
SilicaListView {
|
||||||
|
@ -141,13 +132,13 @@ Page {
|
||||||
title: "SailOTP"
|
title: "SailOTP"
|
||||||
}
|
}
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
model: otpListModel
|
model: appWin.listModel
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
|
||||||
ViewPlaceholder {
|
ViewPlaceholder {
|
||||||
enabled: otpList.count == 0
|
enabled: otpList.count == 0
|
||||||
text: "Nothing here"
|
text: qsTr("Nothing here")
|
||||||
hintText: "Pull down to add a OTP"
|
hintText: qsTr("Pull down to add a OTP")
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: ListItem {
|
delegate: ListItem {
|
||||||
|
@ -158,12 +149,12 @@ Page {
|
||||||
|
|
||||||
function remove() {
|
function remove() {
|
||||||
// Show 5s countdown, then delete from DB and List
|
// Show 5s countdown, then delete from DB and List
|
||||||
remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) })
|
remorseAction(qsTr("Deleting"), function() { DB.removeOTP(title, secret); appWin.listModel.remove(index) })
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
Clipboard.text = otp
|
Clipboard.text = otp
|
||||||
notify.show("Token for " + title + " copied to clipboard", 3000);
|
notify.show(qsTr("Token for ") + title + qsTr(" copied to clipboard"), 3000);
|
||||||
}
|
}
|
||||||
|
|
||||||
ListView.onRemove: animateRemoval()
|
ListView.onRemove: animateRemoval()
|
||||||
|
@ -178,19 +169,19 @@ Page {
|
||||||
onClicked: {
|
onClicked: {
|
||||||
if (fav == 0) {
|
if (fav == 0) {
|
||||||
DB.setFav(title, secret)
|
DB.setFav(title, secret)
|
||||||
setCoverOTP(title, secret, type)
|
appWin.setCover(index)
|
||||||
if (type == "HOTP") appWin.coverOTP = otp
|
if (type == "HOTP") appWin.coverOTP = otp
|
||||||
for (var i=0; i<otpListModel.count; i++) {
|
for (var i=0; i<appWin.listModel.count; i++) {
|
||||||
if (i != index) {
|
if (i != index) {
|
||||||
otpListModel.setProperty(i, "fav", 0);
|
appWin.listModel.setProperty(i, "fav", 0);
|
||||||
} else {
|
} else {
|
||||||
otpListModel.setProperty(i, "fav", 1);
|
appWin.listModel.setProperty(i, "fav", 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
DB.resetFav(title, secret)
|
DB.resetFav(title, secret)
|
||||||
setCoverOTP("SailOTP", "", "")
|
appWin.setCover(-1)
|
||||||
otpListModel.setProperty(index, "fav", 0);
|
appWin.listModel.setProperty(index, "fav", 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -221,8 +212,8 @@ Page {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
visible: type == "HOTP" ? true : false
|
visible: type == "HOTP" ? true : false
|
||||||
onClicked: {
|
onClicked: {
|
||||||
otpListModel.setProperty(index, "counter", DB.getCounter(title, secret, true));
|
appWin.listModel.setProperty(index, "counter", DB.getCounter(title, secret, true));
|
||||||
otpListModel.setProperty(index, "otp", OTP.calcOTP(secret, "HOTP", counter));
|
appWin.listModel.setProperty(index, "otp", OTP.calcOTP(secret, "HOTP", counter));
|
||||||
if (fav == 1) appWin.coverOTP = otp;
|
if (fav == 1) appWin.coverOTP = otp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,13 +222,13 @@ Page {
|
||||||
id: otpContextMenu
|
id: otpContextMenu
|
||||||
ContextMenu {
|
ContextMenu {
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Edit"
|
text: qsTr("Edit")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramCounter: DB.getCounter(title, secret, false)})
|
pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramCounter: DB.getCounter(title, secret, false)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MenuItem {
|
MenuItem {
|
||||||
text: "Delete"
|
text: qsTr("Delete")
|
||||||
onClicked: remove()
|
onClicked: remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -248,10 +239,7 @@ Page {
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Load list of OTP-Entries
|
// Load list of OTP-Entries
|
||||||
refreshOTPList();
|
refreshOTPList();
|
||||||
console.log("SailOTP Version " + Qt.application.version + " started");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Name: harbour-sailotp
|
Name: harbour-sailotp
|
||||||
Summary: SailOTP
|
Summary: SailOTP
|
||||||
Version: 0.5
|
Version: 0.6
|
||||||
Release: 1
|
Release: 1
|
||||||
Group: Security
|
Group: Security
|
||||||
URL: https://github.com/seiichiro0185/sailotp/
|
URL: https://github.com/seiichiro0185/sailotp/
|
||||||
|
@ -19,6 +19,9 @@ PkgConfigBR:
|
||||||
- Qt5Qml
|
- Qt5Qml
|
||||||
- Qt5Core
|
- Qt5Core
|
||||||
- sailfishapp >= 0.0.10
|
- sailfishapp >= 0.0.10
|
||||||
|
- Qt5Core
|
||||||
|
- Qt5Qml
|
||||||
|
- Qt5Quick
|
||||||
Requires:
|
Requires:
|
||||||
- sailfishsilica-qt5 >= 0.10.9
|
- sailfishsilica-qt5 >= 0.10.9
|
||||||
Files:
|
Files:
|
||||||
|
@ -30,4 +33,5 @@ Files:
|
||||||
- /usr/share/harbour-sailotp
|
- /usr/share/harbour-sailotp
|
||||||
- /usr/share/applications
|
- /usr/share/applications
|
||||||
- /usr/share/icons/hicolor/86x86/apps
|
- /usr/share/icons/hicolor/86x86/apps
|
||||||
|
- /usr/share/harbour-sailotp/i18n
|
||||||
PkgBR: []
|
PkgBR: []
|
||||||
|
|
|
@ -34,21 +34,31 @@
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
// Get App and QML-View objects
|
||||||
QScopedPointer<QGuiApplication> app(SailfishApp::application(argc, argv));
|
QScopedPointer<QGuiApplication> app(SailfishApp::application(argc, argv));
|
||||||
QScopedPointer<QQuickView> view(SailfishApp::createView());
|
QScopedPointer<QQuickView> view(SailfishApp::createView());
|
||||||
|
|
||||||
|
// Internationalization, Load the Language
|
||||||
|
QString locale = QLocale::system().name();
|
||||||
|
QTranslator translator;
|
||||||
|
translator.load(locale,SailfishApp::pathTo(QString("i18n")).toLocalFile());
|
||||||
|
app->installTranslator(&translator);
|
||||||
|
|
||||||
|
// Set some global values
|
||||||
app->setOrganizationName("harbour-sailotp");
|
app->setOrganizationName("harbour-sailotp");
|
||||||
app->setOrganizationDomain("harbour-sailotp");
|
app->setOrganizationDomain("harbour-sailotp");
|
||||||
app->setApplicationName("harbour-sailotp");
|
app->setApplicationName("harbour-sailotp");
|
||||||
app->setApplicationVersion(APP_VERSION);
|
app->setApplicationVersion(APP_VERSION);
|
||||||
|
|
||||||
|
// Register FileIO Class
|
||||||
qmlRegisterType<FileIO, 1>("harbour.sailotp.FileIO", 1, 0, "FileIO");
|
qmlRegisterType<FileIO, 1>("harbour.sailotp.FileIO", 1, 0, "FileIO");
|
||||||
|
|
||||||
|
// Prepare the QML and set Homedir
|
||||||
view->setSource(SailfishApp::pathTo("qml/harbour-sailotp.qml"));
|
view->setSource(SailfishApp::pathTo("qml/harbour-sailotp.qml"));
|
||||||
view->rootContext()->setContextProperty("XDG_HOME_DIR", QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
|
view->rootContext()->setContextProperty("XDG_HOME_DIR", QStandardPaths::writableLocation(QStandardPaths::HomeLocation));
|
||||||
view->show();
|
view->show();
|
||||||
|
|
||||||
|
// Run the app
|
||||||
return app->exec();
|
return app->exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue