Some work on login workflow and configuration handling
This commit is contained in:
parent
6d0a6d67d9
commit
af5acbc4a1
14 changed files with 319 additions and 350 deletions
|
@ -17,6 +17,7 @@ CONFIG += sailfishapp
|
|||
DEFINES += APP_VERSION=\\\"$$VERSION\\\"
|
||||
|
||||
HEADERS += src/note.h \
|
||||
src/accounthash.h \
|
||||
src/notesapi.h \
|
||||
src/notesmodel.h
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ CoverBackground {
|
|||
|
||||
CoverActionList {
|
||||
id: coverAction
|
||||
enabled: appSettings.currentAccount.length > 0
|
||||
enabled: account != null
|
||||
|
||||
CoverAction {
|
||||
iconSource: "image://theme/icon-cover-new"
|
||||
|
|
|
@ -8,43 +8,14 @@ ApplicationWindow
|
|||
{
|
||||
id: appWindow
|
||||
|
||||
// All configured accounts
|
||||
ConfigurationValue {
|
||||
id: accounts
|
||||
key: appSettings.path + "/accountIDs"
|
||||
defaultValue: [ ]
|
||||
}
|
||||
|
||||
// Current account in use
|
||||
ConfigurationGroup {
|
||||
id: account
|
||||
path: "/apps/harbour-nextcloudnotes/accounts/" + appSettings.currentAccount
|
||||
|
||||
property string name: value("name", "", String)
|
||||
property url server: value("server", "", String)
|
||||
property string version: value("version", "v0.2", String)
|
||||
property string username: value("username", "", String)
|
||||
property string password: account.value("password", "", String)
|
||||
property bool doNotVerifySsl: account.value("doNotVerifySsl", false, Boolean)
|
||||
property bool allowUnecrypted: account.value("allowUnecrypted", false, Boolean)
|
||||
property date update: value("update", "", Date)
|
||||
onServerChanged: notesApi.server = server
|
||||
onUsernameChanged: {
|
||||
console.log("Username: " + username)
|
||||
notesApi.username = username
|
||||
}
|
||||
onPasswordChanged: notesApi.password = password
|
||||
onDoNotVerifySslChanged: notesApi.verifySsl = !doNotVerifySsl
|
||||
onNameChanged: console.log("Using account: " + name)
|
||||
}
|
||||
|
||||
// General settings of the app
|
||||
ConfigurationGroup {
|
||||
id: appSettings
|
||||
path: "/apps/harbour-nextcloudnotes/settings"
|
||||
path: "/apps/harbour-nextcloudnotes"
|
||||
|
||||
property bool initialized: false
|
||||
property string currentAccount: value("currentAccount", "", String)
|
||||
property var accounts: value("accounts", [], Array)
|
||||
property string currentAccountIndex: value("currentAccountIndex", -1, Number)
|
||||
property int autoSyncInterval: value("autoSyncInterval", 0, Number)
|
||||
property int previewLineCount: value("previewLineCount", 4, Number)
|
||||
property bool favoritesOnTop: value("favoritesOnTop", true, Boolean)
|
||||
|
@ -53,9 +24,12 @@ ApplicationWindow
|
|||
property bool useMonoFont: value("useMonoFont", false, Boolean)
|
||||
property bool useCapitalX: value("useCapitalX", false, Boolean)
|
||||
|
||||
onCurrentAccountChanged: {
|
||||
account.path = "/apps/harbour-nextcloudnotes/accounts/" + currentAccount
|
||||
notesModel.account = currentAccount
|
||||
onCurrentAccountIndexChanged: {
|
||||
console.log("Current account index: " + currentAccountIndex)
|
||||
if (currentAccountIndex >= 0 && currentAccountIndex < accounts.length) {
|
||||
account = accounts[currentAccountIndex]
|
||||
console.log("Current account: " + account.username + "@" + account.url)
|
||||
}
|
||||
}
|
||||
|
||||
onSortByChanged: {
|
||||
|
@ -68,53 +42,18 @@ ApplicationWindow
|
|||
notesProxyModel.favoritesOnTop = favoritesOnTop
|
||||
}
|
||||
|
||||
function addAccount() {
|
||||
var uuid = uuidv4()
|
||||
var tmpIDs = accounts.value
|
||||
tmpIDs.push(uuid)
|
||||
accounts.value = tmpIDs
|
||||
accounts.sync()
|
||||
return uuid
|
||||
}
|
||||
ConfigurationGroup {
|
||||
id: removeHelperConfGroup
|
||||
}
|
||||
function removeAccount(uuid) {
|
||||
autoSyncTimer.stop()
|
||||
var tmpIDs = accounts.value
|
||||
removeHelperConfGroup.path = "/apps/harbour-nextcloudnotes/accounts/" + uuid
|
||||
for (var i = tmpIDs.length-1; i >= 0; i--) {
|
||||
console.log(tmpIDs)
|
||||
console.log("Checking:" + tmpIDs[i])
|
||||
if (tmpIDs[i] === uuid) {
|
||||
console.log("Found! Removing ...")
|
||||
tmpIDs.splice(i, 1)
|
||||
}
|
||||
console.log(tmpIDs)
|
||||
}
|
||||
if (appSettings.currentAccount === uuid) {
|
||||
appSettings.currentAccount = ""
|
||||
for (var i = tmpIDs.length-1; i >= 0 && appSettings.currentAccount === ""; i--) {
|
||||
if (tmpIDs[i] !== uuid) {
|
||||
appSettings.currentAccount = tmpIDs[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
removeHelperConfGroup.clear()
|
||||
if (autoSyncInterval > 0 && appWindow.visible) {
|
||||
autoSyncTimer.start()
|
||||
}
|
||||
accounts.value = tmpIDs
|
||||
accounts.sync()
|
||||
}
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
function createAccount(user, url) {
|
||||
var hash = accountHash.hash(user, url)
|
||||
console.log("Hash(" + user + "@" + url + ") = " + hash)
|
||||
return hash
|
||||
}
|
||||
function removeAccount(hash) {
|
||||
accounts[hash] = null
|
||||
currentAccount = -1 }
|
||||
}
|
||||
|
||||
property var account
|
||||
|
||||
Notification {
|
||||
id: offlineNotification
|
||||
expireTimeout: 0
|
||||
|
|
|
@ -1,65 +1,41 @@
|
|||
import QtQuick 2.2
|
||||
import Sailfish.Silica 1.0
|
||||
import Nemo.Configuration 1.0
|
||||
import NextcloudNotes 1.0
|
||||
|
||||
Dialog {
|
||||
id: loginDialog
|
||||
|
||||
property string accountId
|
||||
canAccept: false
|
||||
|
||||
property bool legacyLoginPossible: false
|
||||
property bool flowLoginV2Possible: false
|
||||
|
||||
property url server
|
||||
property string username
|
||||
property string password
|
||||
property bool doNotVerifySsl: false
|
||||
property bool allowUnecrypted: false
|
||||
|
||||
property string productName
|
||||
property string version
|
||||
|
||||
onRejected: {
|
||||
appSettings.removeAccount(accountId)
|
||||
}
|
||||
onAccepted: {
|
||||
appSettings.createAccount(username, server)
|
||||
}
|
||||
|
||||
ConfigurationGroup {
|
||||
id: account
|
||||
path: "/apps/harbour-nextcloudnotes/accounts/" + accountId
|
||||
|
||||
property string name: value("name", qsTr("Nextcloud Login"), String)
|
||||
property url server: value("server", "", String)
|
||||
property string version: value("version", "v0.2", String)
|
||||
property string username: value("username", "", String)
|
||||
property string password: account.value("password", "", String)
|
||||
property bool doNotVerifySsl: account.value("doNotVerifySsl", false, Boolean)
|
||||
property bool allowUnecrypted: account.value("allowUnecrypted", false, Boolean)
|
||||
|
||||
Component.onCompleted: {
|
||||
dialogHeader.title = name
|
||||
serverField.text = server ? server : allowUnecrypted ? "http://" : "https://"
|
||||
usernameField.text = username
|
||||
passwordField.text = password
|
||||
unsecureConnectionTextSwitch.checked = doNotVerifySsl
|
||||
unencryptedConnectionTextSwitch.checked = allowUnecrypted
|
||||
if (username !== "" && password !== "") {
|
||||
notesApi.server = server
|
||||
notesApi.username = username
|
||||
notesApi.password = password
|
||||
notesApi.verifySsl = !doNotVerifySsl
|
||||
notesApi.verifyLogin()
|
||||
}
|
||||
}
|
||||
Timer {
|
||||
id: verifyServerTimer
|
||||
onTriggered: notesApi.getNcStatus()
|
||||
}
|
||||
|
||||
/*onStatusChanged: {
|
||||
if (status === PageStatus.Activating)
|
||||
notesApi.getNcStatus()
|
||||
if (status === PageStatus.Deactivating)
|
||||
notesApi.abortFlowV2Login()
|
||||
}*/
|
||||
|
||||
Connections {
|
||||
target: notesApi
|
||||
onStatusInstalledChanged: {
|
||||
if (notesApi.statusInstalled)
|
||||
serverField.focus = false
|
||||
else {
|
||||
dialogHeader.title
|
||||
}
|
||||
}
|
||||
onStatusVersionChanged: {
|
||||
if (notesApi.statusVersion) {
|
||||
|
@ -80,58 +56,77 @@ Dialog {
|
|||
}
|
||||
}
|
||||
onStatusVersionStringChanged: {
|
||||
if (notesApi.statusVersionString)
|
||||
dialogHeader.description = "Nextcloud " + notesApi.statusVersionString
|
||||
if (notesApi.statusVersionString) {
|
||||
version = notesApi.statusVersionString
|
||||
console.log(notesApi.statusVersionString)
|
||||
}
|
||||
}
|
||||
onStatusProductNameChanged: {
|
||||
if (notesApi.statusProductName) {
|
||||
dialogHeader.title = notesApi.statusProductName
|
||||
account.name = notesApi.statusProductName
|
||||
productName = notesApi.statusProductName
|
||||
console.log(notesApi.statusProductName)
|
||||
}
|
||||
}
|
||||
onLoginStatusChanged: {
|
||||
loginDialog.canAccept = false
|
||||
apiProgressBar.indeterminate = false
|
||||
switch(notesApi.loginStatus) {
|
||||
case notesApi.LoginLegacyReady:
|
||||
case NotesApi.LoginLegacyReady:
|
||||
console.log("LoginLegacyReady")
|
||||
apiProgressBar.label = qsTr("Enter your credentials")
|
||||
break;
|
||||
//case notesApi.LoginFlowV2Initiating:
|
||||
// break;
|
||||
case notesApi.LoginFlowV2Polling:
|
||||
apiProgressBar.label = qsTr("Follow the instructions in the browser")
|
||||
case NotesApi.LoginFlowV2Initiating:
|
||||
console.log("LoginFlowV2Initiating")
|
||||
apiProgressBar.indeterminate = true
|
||||
break;
|
||||
case notesApi.LoginFlowV2Success:
|
||||
case NotesApi.LoginFlowV2Polling:
|
||||
console.log("LoginFlowV2Polling")
|
||||
apiProgressBar.label = qsTr("Follow the instructions in the browser")
|
||||
apiProgressBar.indeterminate = true
|
||||
break;
|
||||
case NotesApi.LoginFlowV2Success:
|
||||
console.log("LoginFlowV2Success")
|
||||
notesApi.verifyLogin()
|
||||
break;
|
||||
case notesApi.LoginFlowV2Failed:
|
||||
case NotesApi.LoginFlowV2Failed:
|
||||
console.log("LoginFlowV2Failed")
|
||||
apiProgressBar.label = qsTr("Login failed!")
|
||||
break
|
||||
case notesApi.LoginSuccess:
|
||||
case NotesApi.LoginSuccess:
|
||||
console.log("LoginSuccess")
|
||||
apiProgressBar.label = qsTr("Login successfull!")
|
||||
account.username = notesApi.username
|
||||
account.password = notesApi.password
|
||||
appSettings.currentAccount = accountId
|
||||
loginDialog.canAccept = true
|
||||
break;
|
||||
case notesApi.LoginFailed:
|
||||
case NotesApi.LoginFailed:
|
||||
console.log("LoginFailed")
|
||||
apiProgressBar.label = qsTr("Login failed!")
|
||||
break;
|
||||
default:
|
||||
console.log("None")
|
||||
apiProgressBar.label = ""
|
||||
break;
|
||||
}
|
||||
}
|
||||
onLoginUrlChanged: {
|
||||
if (notesApi.loginUrl) {
|
||||
Qt.openUrlExternally(notesApi.loginUrl)
|
||||
}
|
||||
else {
|
||||
console.log("Login successfull")
|
||||
}
|
||||
}
|
||||
onServerChanged: {
|
||||
if (notesApi.server) {
|
||||
console.log("Login server: " + notesApi.server)
|
||||
account.server = notesApi.server
|
||||
serverField.text = notesApi.server
|
||||
console.log(notesApi.server)
|
||||
server = notesApi.server
|
||||
}
|
||||
}
|
||||
onUsernameChanged: {
|
||||
if (notesApi.username) {
|
||||
console.log(notesApi.username)
|
||||
username = notesApi.username
|
||||
}
|
||||
}
|
||||
onPasswordChanged: {
|
||||
if (notesApi.password) {
|
||||
console.log("***")
|
||||
password = notesApi.password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -147,6 +142,7 @@ Dialog {
|
|||
|
||||
DialogHeader {
|
||||
id: dialogHeader
|
||||
title: qsTr("Nextcloud Login")
|
||||
}
|
||||
|
||||
Image {
|
||||
|
@ -161,8 +157,6 @@ Dialog {
|
|||
id: apiProgressBar
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: parent.width
|
||||
indeterminate: notesApi.loginStatus === notesApi.LoginFlowV2Initiating ||
|
||||
notesApi.loginStatus === notesApi.LoginFlowV2Polling
|
||||
}
|
||||
|
||||
Row {
|
||||
|
@ -170,17 +164,18 @@ Dialog {
|
|||
TextField {
|
||||
id: serverField
|
||||
width: parent.width - statusIcon.width - Theme.horizontalPageMargin
|
||||
placeholderText: qsTr("Nextcloud server")
|
||||
text: server
|
||||
placeholderText: productName ? productName : qsTr("Nextcloud server")
|
||||
label: placeholderText
|
||||
validator: RegExpValidator { regExp: unencryptedConnectionTextSwitch.checked ? /^https?:\/\/([-a-zA-Z0-9@:%._\+~#=].*)/: /^https:\/\/([-a-zA-Z0-9@:%._\+~#=].*)/ }
|
||||
inputMethodHints: Qt.ImhUrlCharactersOnly
|
||||
onClicked: if (text === "") text = "https://"
|
||||
onClicked: if (text === "") text = allowUnecrypted ? "http://" : "https://"
|
||||
onTextChanged: {
|
||||
statusBusyIndicatorTimer.restart()
|
||||
if (acceptableInput) {
|
||||
notesApi.server = text
|
||||
notesApi.getNcStatus()
|
||||
}
|
||||
verifyServerTimer.restart()
|
||||
notesApi.getNcStatus()
|
||||
}
|
||||
//EnterKey.enabled: text.length > 0
|
||||
EnterKey.iconSource: legacyLoginPossible ? "image://theme/icon-m-enter-next" : flowLoginV2Possible ? "image://theme/icon-m-enter-accept" : "image://theme/icon-m-enter-close"
|
||||
|
@ -199,11 +194,7 @@ Dialog {
|
|||
BusyIndicator {
|
||||
anchors.centerIn: parent
|
||||
size: BusyIndicatorSize.Medium
|
||||
running: notesApi.ncStatusStatus === notesApi.NextcloudBusy || (serverField.focus && statusBusyIndicatorTimer.running && !notesApi.statusInstalled)
|
||||
Timer {
|
||||
id: statusBusyIndicatorTimer
|
||||
interval: 200
|
||||
}
|
||||
running: notesApi.ncStatusStatus === notesApi.NextcloudBusy || (verifyServerTimer.running)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -212,10 +203,10 @@ Dialog {
|
|||
id: forceLegacyButton
|
||||
visible: debug || !notesApi.statusInstalled
|
||||
text: qsTr("Enforce legacy login")
|
||||
automaticCheck: true
|
||||
onCheckedChanged: {
|
||||
checked != checked
|
||||
if (!checked) {
|
||||
notesApi.getNcStatus()
|
||||
verifyServerTimer.restart()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -243,6 +234,7 @@ Dialog {
|
|||
TextField {
|
||||
id: usernameField
|
||||
width: parent.width
|
||||
text: username
|
||||
placeholderText: qsTr("Username")
|
||||
label: placeholderText
|
||||
inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase
|
||||
|
@ -254,6 +246,7 @@ Dialog {
|
|||
PasswordField {
|
||||
id: passwordField
|
||||
width: parent.width
|
||||
text: password
|
||||
placeholderText: qsTr("Password")
|
||||
label: placeholderText
|
||||
errorHighlight: text.length === 0// && focus === true
|
||||
|
@ -292,29 +285,30 @@ Dialog {
|
|||
}
|
||||
TextSwitch {
|
||||
id: unsecureConnectionTextSwitch
|
||||
checked: doNotVerifySsl
|
||||
text: qsTr("Do not check certificates")
|
||||
description: qsTr("Enable this option to allow selfsigned certificates")
|
||||
onCheckedChanged: {
|
||||
account.doNotVerifySsl = checked
|
||||
notesApi.verifySsl = !account.doNotVerifySsl
|
||||
notesApi.verifySsl = !checked
|
||||
}
|
||||
}
|
||||
TextSwitch {
|
||||
id: unencryptedConnectionTextSwitch
|
||||
checked: allowUnecrypted
|
||||
automaticCheck: false
|
||||
text: qsTr("Allow unencrypted connections")
|
||||
description: qsTr("")
|
||||
//description: qsTr("")
|
||||
onClicked: {
|
||||
if (checked) {
|
||||
checked = false
|
||||
allowUnecrypted = !checked
|
||||
}
|
||||
else {
|
||||
var dialog = pageStack.push(Qt.resolvedUrl("UnencryptedDialog.qml"))
|
||||
dialog.accepted.connect(function() {
|
||||
checked = true
|
||||
allowUnecrypted = true
|
||||
})
|
||||
dialog.rejected.connect(function() {
|
||||
checked = false
|
||||
allowUnecrypted = false
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ Page {
|
|||
|
||||
onStatusChanged: {
|
||||
if (status === PageStatus.Activating) {
|
||||
if (accounts.value.length <= 0) {
|
||||
if (appSettings.accounts.length <= 0) {
|
||||
addAccountHint.restart()
|
||||
}
|
||||
else {
|
||||
|
@ -32,16 +32,16 @@ Page {
|
|||
}
|
||||
MenuItem {
|
||||
text: qsTr("Add note")
|
||||
enabled: appSettings.currentAccount.length > 0 && notesApi.networkAccessible
|
||||
enabled: account != null && notesApi.networkAccessible
|
||||
onClicked: notesApi.createNote( { 'content': "", 'modified': new Date().valueOf() / 1000 } )
|
||||
}
|
||||
MenuItem {
|
||||
text: notesApi.networkAccessible && !notesApi.busy ? qsTr("Reload") : qsTr("Updating...")
|
||||
enabled: appSettings.currentAccount.length > 0 && notesApi.networkAccessible && !notesApi.busy
|
||||
enabled: account != null && notesApi.networkAccessible && !notesApi.busy
|
||||
onClicked: notes.getAllNotes()
|
||||
}
|
||||
MenuLabel {
|
||||
visible: appSettings.currentAccount.length > 0
|
||||
visible: account != null
|
||||
text: qsTr("Last update") + ": " + (
|
||||
new Date(account.update).valueOf() !== 0 ?
|
||||
new Date(account.update).toLocaleString(Qt.locale(), Locale.ShortFormat) :
|
||||
|
@ -224,7 +224,7 @@ Page {
|
|||
|
||||
ViewPlaceholder {
|
||||
id: noLoginPlaceholder
|
||||
enabled: accounts.value.length <= 0
|
||||
enabled: appSettings.accounts.length <= 0
|
||||
text: qsTr("No account yet")
|
||||
hintText: qsTr("Got to the settings to add an account")
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ Page {
|
|||
}
|
||||
Label {
|
||||
id: noAccountsLabel
|
||||
visible: accounts.value.length <= 0
|
||||
visible: appSettings.accounts.length <= 0
|
||||
text: qsTr("No Nextcloud account yet")
|
||||
font.pixelSize: Theme.fontSizeLarge
|
||||
color: Theme.secondaryHighlightColor
|
||||
|
@ -43,7 +43,7 @@ Page {
|
|||
}
|
||||
Repeater {
|
||||
id: accountRepeater
|
||||
model: accounts.value
|
||||
model: appSettings.accounts
|
||||
|
||||
delegate: ListItem {
|
||||
id: accountListItem
|
||||
|
@ -91,8 +91,7 @@ Page {
|
|||
text: qsTr("Add account")
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
onClicked: {
|
||||
var newAccountID = appSettings.addAccount()
|
||||
var login = pageStack.push(Qt.resolvedUrl("LoginPage.qml"), { accountId: newAccountID, addingNew: true })
|
||||
var login = pageStack.push(Qt.resolvedUrl("LoginPage.qml"), { accountId: "" })
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ Dialog {
|
|||
DialogHeader {
|
||||
}
|
||||
|
||||
Label {
|
||||
LinkedLabel {
|
||||
x: Theme.horizontalPageMargin
|
||||
width: parent.width - 2*x
|
||||
wrapMode: Text.Wrap
|
||||
|
|
14
src/accounthash.h
Normal file
14
src/accounthash.h
Normal file
|
@ -0,0 +1,14 @@
|
|||
#ifndef ACCOUNTHASH_H
|
||||
#define ACCOUNTHASH_H
|
||||
#include <QObject>
|
||||
#include <QCryptographicHash>
|
||||
|
||||
class AccountHash : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE QByteArray hash(const QString username, const QString url) {
|
||||
return QCryptographicHash::hash(QString("%1@%2").arg(username).arg(url).toUtf8(), QCryptographicHash::Sha256);
|
||||
}
|
||||
};
|
||||
|
||||
#endif // ACCOUNTHASH_H
|
|
@ -2,6 +2,7 @@
|
|||
#include <sailfishapp.h>
|
||||
#include <QtQml>
|
||||
#include <QObject>
|
||||
#include "accounthash.h"
|
||||
#include "note.h"
|
||||
#include "notesapi.h"
|
||||
#include "notesmodel.h"
|
||||
|
@ -17,6 +18,7 @@ int main(int argc, char *argv[])
|
|||
|
||||
qDebug() << app->applicationDisplayName() << app->applicationVersion();
|
||||
|
||||
AccountHash* accountHash = new AccountHash;
|
||||
qRegisterMetaType<Note>();
|
||||
NotesModel* notesModel = new NotesModel;
|
||||
NotesProxyModel* notesProxyModel = new NotesProxyModel;
|
||||
|
@ -29,12 +31,15 @@ int main(int argc, char *argv[])
|
|||
NotesApi* notesApi = new NotesApi;
|
||||
notesModel->setNotesApi(notesApi);
|
||||
|
||||
qmlRegisterType<NotesApi>("NextcloudNotes", 1, 0, "NotesApi");
|
||||
|
||||
QQuickView* view = SailfishApp::createView();
|
||||
#ifdef QT_DEBUG
|
||||
view->rootContext()->setContextProperty("debug", QVariant(true));
|
||||
#else
|
||||
view->rootContext()->setContextProperty("debug", QVariant(false));
|
||||
#endif
|
||||
view->rootContext()->setContextProperty("accountHash", accountHash);
|
||||
view->rootContext()->setContextProperty("notesModel", notesModel);
|
||||
view->rootContext()->setContextProperty("notesProxyModel", notesProxyModel);
|
||||
view->rootContext()->setContextProperty("notesApi", notesApi);
|
||||
|
|
218
src/notesapi.cpp
218
src/notesapi.cpp
|
@ -32,7 +32,7 @@ NotesApi::NotesApi(const QString statusEndpoint, const QString loginEndpoint, co
|
|||
m_request.setSslConfiguration(QSslConfiguration::defaultConfiguration());
|
||||
m_request.setHeader(QNetworkRequest::UserAgentHeader, QGuiApplication::applicationDisplayName() + " " + QGuiApplication::applicationVersion() + " - " + QSysInfo::machineHostName());
|
||||
m_request.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/x-www-form-urlencoded").toUtf8());
|
||||
m_request.setRawHeader("OCS-APIREQUEST", "true");
|
||||
m_request.setRawHeader("OCS-APIRequest", "true");
|
||||
m_request.setRawHeader("Accept", "application/json");
|
||||
m_authenticatedRequest = m_request;
|
||||
m_authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, QString("application/json").toUtf8());
|
||||
|
@ -47,102 +47,7 @@ NotesApi::~NotesApi() {
|
|||
disconnect(&m_manager, SIGNAL(sslErrors(QNetworkReply*,QList<QSslError>)), this, SLOT(sslError(QNetworkReply*,QList<QSslError>)));
|
||||
}
|
||||
|
||||
const QList<int> NotesApi::noteIds() {
|
||||
return m_syncedNotes.keys();
|
||||
}
|
||||
|
||||
bool NotesApi::noteExists(const int id) {
|
||||
return m_syncedNotes.contains(id);
|
||||
}
|
||||
|
||||
int NotesApi::noteModified(const int id) {
|
||||
return m_syncedNotes.value(id, -1);
|
||||
}
|
||||
|
||||
bool NotesApi::getAllNotes(const QStringList& exclude) {
|
||||
qDebug() << "Getting all notes";
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint);
|
||||
|
||||
if (!exclude.isEmpty())
|
||||
url.setQuery(QString(EXCLUDE_QUERY).append(exclude.join(",")));
|
||||
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "GET" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_getAllNotesReplies << m_manager.get(m_authenticatedRequest);
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::getNote(const int id, const QStringList& exclude) {
|
||||
qDebug() << "Getting note: " << id;
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id));
|
||||
if (!exclude.isEmpty())
|
||||
url.setQuery(QString(EXCLUDE_QUERY).append(exclude.join(",")));
|
||||
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "GET" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_getNoteReplies << m_manager.get(m_authenticatedRequest);
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::createNote(const QJsonObject& note) {
|
||||
qDebug() << "Creating note";
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint);
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "POST" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_createNoteReplies << m_manager.post(m_authenticatedRequest, QJsonDocument(note).toJson());
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::updateNote(const int id, const QJsonObject& note) {
|
||||
qDebug() << "Updating note: " << id;
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id));
|
||||
if (id >= 0 && url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "PUT" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_updateNoteReplies << m_manager.put(m_authenticatedRequest, QJsonDocument(note).toJson());
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::deleteNote(const int id) {
|
||||
qDebug() << "Deleting note: " << id;
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id));
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "DELETE" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_deleteNoteReplies << m_manager.deleteResource(m_authenticatedRequest);
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::busy() const {
|
||||
return !(m_getAllNotesReplies.empty() &&
|
||||
m_getNoteReplies.empty() &&
|
||||
m_createNoteReplies.empty() &&
|
||||
m_updateNoteReplies.empty() &&
|
||||
m_deleteNoteReplies.empty() &&
|
||||
m_statusReplies.empty() &&
|
||||
m_loginReplies.empty() &&
|
||||
m_pollReplies.empty() &&
|
||||
m_ocsReplies.empty());
|
||||
}
|
||||
|
||||
// Generic API properties
|
||||
void NotesApi::setVerifySsl(bool verify) {
|
||||
if (verify != (m_request.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer)) {
|
||||
m_request.sslConfiguration().setPeerVerifyMode(verify ? QSslSocket::VerifyPeer : QSslSocket::VerifyNone);
|
||||
|
@ -249,6 +154,20 @@ void NotesApi::setPath(QString path) {
|
|||
}
|
||||
}
|
||||
|
||||
// Class status information
|
||||
bool NotesApi::busy() const {
|
||||
return !(m_getAllNotesReplies.empty() &&
|
||||
m_getNoteReplies.empty() &&
|
||||
m_createNoteReplies.empty() &&
|
||||
m_updateNoteReplies.empty() &&
|
||||
m_deleteNoteReplies.empty() &&
|
||||
m_statusReplies.empty() &&
|
||||
m_loginReplies.empty() &&
|
||||
m_pollReplies.empty() &&
|
||||
m_ocsReplies.empty());
|
||||
}
|
||||
|
||||
// Callable functions
|
||||
bool NotesApi::getNcStatus() {
|
||||
QUrl url = apiEndpointUrl(m_statusEndpoint);
|
||||
qDebug() << "GET" << url.toDisplayString();
|
||||
|
@ -323,6 +242,90 @@ void NotesApi::verifyLogin(QString username, QString password) {
|
|||
}
|
||||
}
|
||||
|
||||
const QList<int> NotesApi::noteIds() {
|
||||
return m_syncedNotes.keys();
|
||||
}
|
||||
|
||||
bool NotesApi::noteExists(const int id) {
|
||||
return m_syncedNotes.contains(id);
|
||||
}
|
||||
|
||||
int NotesApi::noteModified(const int id) {
|
||||
return m_syncedNotes.value(id, -1);
|
||||
}
|
||||
|
||||
bool NotesApi::getAllNotes(const QStringList& exclude) {
|
||||
qDebug() << "Getting all notes";
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint);
|
||||
|
||||
if (!exclude.isEmpty())
|
||||
url.setQuery(QString(EXCLUDE_QUERY).append(exclude.join(",")));
|
||||
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "GET" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_getAllNotesReplies << m_manager.get(m_authenticatedRequest);
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::getNote(const int id, const QStringList& exclude) {
|
||||
qDebug() << "Getting note: " << id;
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id));
|
||||
if (!exclude.isEmpty())
|
||||
url.setQuery(QString(EXCLUDE_QUERY).append(exclude.join(",")));
|
||||
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "GET" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_getNoteReplies << m_manager.get(m_authenticatedRequest);
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::createNote(const QJsonObject& note) {
|
||||
qDebug() << "Creating note";
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint);
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "POST" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_createNoteReplies << m_manager.post(m_authenticatedRequest, QJsonDocument(note).toJson());
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::updateNote(const int id, const QJsonObject& note) {
|
||||
qDebug() << "Updating note: " << id;
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id));
|
||||
if (id >= 0 && url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "PUT" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_updateNoteReplies << m_manager.put(m_authenticatedRequest, QJsonDocument(note).toJson());
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NotesApi::deleteNote(const int id) {
|
||||
qDebug() << "Deleting note: " << id;
|
||||
QUrl url = apiEndpointUrl(m_notesEndpoint + QString("/%1").arg(id));
|
||||
if (url.isValid() && !url.scheme().isEmpty() && !url.host().isEmpty()) {
|
||||
qDebug() << "DELETE" << url.toDisplayString();
|
||||
m_authenticatedRequest.setUrl(url);
|
||||
m_deleteNoteReplies << m_manager.deleteResource(m_authenticatedRequest);
|
||||
emit busyChanged(true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const QString NotesApi::errorMessage(int error) const {
|
||||
QString message;
|
||||
switch (error) {
|
||||
|
@ -385,7 +388,7 @@ void NotesApi::replyFinished(QNetworkReply *reply) {
|
|||
|
||||
QByteArray data = reply->readAll();
|
||||
//qDebug() << data;
|
||||
qDebug() << reply->rawHeader("X-Notes-API-Versions");
|
||||
//qDebug() << reply->rawHeader("X-Notes-API-Versions");
|
||||
QJsonDocument json = QJsonDocument::fromJson(data);
|
||||
|
||||
if (m_getAllNotesReplies.contains(reply)) {
|
||||
|
@ -435,25 +438,27 @@ void NotesApi::replyFinished(QNetworkReply *reply) {
|
|||
updateLoginFlow(json.object());
|
||||
else {
|
||||
m_loginStatus = LoginStatus::LoginFailed;
|
||||
emit loginStatusChanged(m_loginStatus);
|
||||
setLoginStatus(m_loginStatus);
|
||||
}
|
||||
m_loginReplies.removeOne(reply);
|
||||
}
|
||||
else if (m_pollReplies.contains(reply)) {
|
||||
qDebug() << "Poll reply, finished";
|
||||
if (reply->error() == QNetworkReply::NoError && json.isObject())
|
||||
qDebug() << "Poll reply";
|
||||
if (reply->error() == QNetworkReply::NoError && json.isObject()) {
|
||||
updateLoginCredentials(json.object());
|
||||
m_pollReplies.removeOne(reply);
|
||||
}
|
||||
else if (reply->error() == QNetworkReply::ContentNotFoundError) {
|
||||
qDebug() << "Polling not finished yet" << reply->url().toDisplayString();
|
||||
m_loginStatus = LoginStatus::LoginFlowV2Polling;
|
||||
emit loginStatusChanged(m_loginStatus);
|
||||
setLoginStatus(m_loginStatus);
|
||||
}
|
||||
else {
|
||||
m_loginStatus = LoginStatus::LoginFailed;
|
||||
emit loginStatusChanged(m_loginStatus);
|
||||
setLoginStatus(m_loginStatus);
|
||||
m_pollReplies.removeOne(reply);
|
||||
abortFlowV2Login();
|
||||
}
|
||||
m_pollReplies.removeOne(reply);
|
||||
abortFlowV2Login();
|
||||
}
|
||||
else if (m_statusReplies.contains(reply)) {
|
||||
qDebug() << "Status reply";
|
||||
|
@ -658,8 +663,9 @@ bool NotesApi::updateLoginCredentials(const QJsonObject &credentials) {
|
|||
|
||||
void NotesApi::setLoginStatus(LoginStatus status, bool *changed) {
|
||||
if (status != m_loginStatus) {
|
||||
if (changed)
|
||||
if (changed) {
|
||||
*changed = true;
|
||||
}
|
||||
m_loginStatus = status;
|
||||
emit loginStatusChanged(m_loginStatus);
|
||||
}
|
||||
|
|
|
@ -70,11 +70,12 @@ public:
|
|||
QObject *parent = nullptr);
|
||||
virtual ~NotesApi();
|
||||
|
||||
// Status codes
|
||||
enum CapabilitiesStatus {
|
||||
CapabilitiesUnknown, // Initial unknown state
|
||||
CapabilitiesBusy, // Gettin information
|
||||
CapabilitiesSuccess, // Capabilities successfully read
|
||||
CapabilitiesStatusFailed // Faild to retreive capabilities
|
||||
CapabilitiesFailed // Faild to retreive capabilities
|
||||
};
|
||||
Q_ENUM(CapabilitiesStatus)
|
||||
|
||||
|
@ -82,7 +83,7 @@ public:
|
|||
NextcloudUnknown, // Initial unknown state
|
||||
NextcloudBusy, // Getting information from the nextcloud server
|
||||
NextcloudSuccess, // Got information about the nextcloud server
|
||||
NextcloudFailed // Error getting information from the nextcloud server, see error()
|
||||
NextcloudFailed // Error getting information from the nextcloud server, see ErrorCodes
|
||||
};
|
||||
Q_ENUM(NextcloudStatus)
|
||||
|
||||
|
@ -94,18 +95,17 @@ public:
|
|||
LoginFlowV2Success, // Finished login flow v2
|
||||
LoginFlowV2Failed, // An error in login flow v2
|
||||
LoginSuccess, // Login has been verified successfull
|
||||
LoginFailed // Login has failed, see error()
|
||||
LoginFailed // Login has failed, see ErrorCodes
|
||||
};
|
||||
Q_ENUM(LoginStatus)
|
||||
|
||||
// Generic API properties
|
||||
bool verifySsl() const { return m_authenticatedRequest.sslConfiguration().peerVerifyMode() == QSslSocket::VerifyPeer; }
|
||||
void setVerifySsl(bool verify);
|
||||
|
||||
QUrl url() const { return m_url; }
|
||||
void setUrl(QUrl url);
|
||||
|
||||
bool urlValid() const { return m_url.isValid(); }
|
||||
|
||||
QString server() const;
|
||||
void setServer(QString server);
|
||||
|
||||
|
@ -127,17 +127,19 @@ public:
|
|||
QString path() const { return m_url.path(); }
|
||||
void setPath(QString path);
|
||||
|
||||
// Class status information
|
||||
bool urlValid() const { return m_url.isValid(); }
|
||||
bool networkAccessible() const { return m_manager.networkAccessible() == QNetworkAccessManager::Accessible; }
|
||||
|
||||
QDateTime lastSync() const { return m_lastSync; }
|
||||
|
||||
bool busy() const;
|
||||
|
||||
// Nextcloud capabilities
|
||||
CapabilitiesStatus capabilitiesStatus() const { return m_capabilitiesStatus; }
|
||||
bool notesAppInstalled() const { return m_capabilities_notesInstalled; }
|
||||
QStringList notesAppApiVersions() const { return m_capabilities_notesApiVersions; }
|
||||
static QString notesAppApiUsedVersion() { return m_capabilities_implementedApiVersion.toString(); }
|
||||
|
||||
// Nextcloud status (status.php)
|
||||
NextcloudStatus ncStatusStatus() const { return m_ncStatusStatus; }
|
||||
bool statusInstalled() const { return m_status_installed; }
|
||||
bool statusMaintenance() const { return m_status_maintenance; }
|
||||
|
@ -148,9 +150,11 @@ public:
|
|||
QString statusProductName() const { return m_status_productname; }
|
||||
bool statusExtendedSupport() const { return m_status_extendedSupport; }
|
||||
|
||||
// Login status
|
||||
LoginStatus loginStatus() const { return m_loginStatus; }
|
||||
QUrl loginUrl() const { return m_loginUrl; }
|
||||
|
||||
// Callable functions
|
||||
Q_INVOKABLE bool getNcStatus();
|
||||
Q_INVOKABLE bool initiateFlowV2Login();
|
||||
Q_INVOKABLE void abortFlowV2Login();
|
||||
|
@ -174,6 +178,7 @@ public:
|
|||
int noteModified(const int id);
|
||||
|
||||
public slots:
|
||||
// Notes API calls
|
||||
Q_INVOKABLE bool getAllNotes(const QStringList& exclude = QStringList());
|
||||
Q_INVOKABLE bool getNote(const int id, const QStringList& exclude = QStringList());
|
||||
Q_INVOKABLE bool createNote(const QJsonObject& note);
|
||||
|
@ -181,9 +186,9 @@ public slots:
|
|||
Q_INVOKABLE bool deleteNote(const int id);
|
||||
|
||||
signals:
|
||||
// Generic API properties
|
||||
void verifySslChanged(bool verify);
|
||||
void urlChanged(QUrl url);
|
||||
void urlValidChanged(bool valid);
|
||||
void serverChanged(QString server);
|
||||
void schemeChanged(QString scheme);
|
||||
void hostChanged(QString host);
|
||||
|
@ -191,16 +196,20 @@ signals:
|
|||
void usernameChanged(QString username);
|
||||
void passwordChanged(QString password);
|
||||
void pathChanged(QString path);
|
||||
void dataFileChanged(QString dataFile);
|
||||
|
||||
// Class status information
|
||||
void urlValidChanged(bool valid);
|
||||
void networkAccessibleChanged(bool accessible);
|
||||
void lastSyncChanged(QDateTime lastSync);
|
||||
void busyChanged(bool busy);
|
||||
|
||||
// Nextcloud capabilities
|
||||
void capabilitiesStatusChanged(CapabilitiesStatus status);
|
||||
void notesAppInstalledChanged(bool installed);
|
||||
void notesAppApiVersionsChanged(QStringList versions);
|
||||
void notesAppApiUsedVersionChanged(QString version);
|
||||
|
||||
// Nextcloud status (status.php)
|
||||
void ncStatusStatusChanged(NextcloudStatus status);
|
||||
void statusInstalledChanged(bool installed);
|
||||
void statusMaintenanceChanged(bool maintenance);
|
||||
|
@ -211,9 +220,11 @@ signals:
|
|||
void statusProductNameChanged(QString productName);
|
||||
void statusExtendedSupportChanged(bool extendedSupport);
|
||||
|
||||
// Login status
|
||||
void loginStatusChanged(LoginStatus status);
|
||||
void loginUrlChanged(QUrl url);
|
||||
|
||||
// Notes API updates
|
||||
void noteCreated(int id, const QJsonObject& note);
|
||||
void noteUpdated(int id, const QJsonObject& note);
|
||||
void noteDeleted(int id);
|
||||
|
|
|
@ -104,7 +104,7 @@ void NotesModel::setNotesApi(NotesApi *notesApi) {
|
|||
mp_notesApi = notesApi;
|
||||
if (mp_notesApi) {
|
||||
// connect stuff
|
||||
//connect(mp_notesApi, SIGNAL(accountChanged(QString)), this, SIGNAL(accountChanged(QString)));
|
||||
connect(mp_notesApi, SIGNAL(accountChanged(QString)), this, SIGNAL(accountChanged(QString)));
|
||||
connect(mp_notesApi, SIGNAL(noteCreated(int,QJsonObject)), this, SLOT(insert(int,QJsonObject)));
|
||||
connect(mp_notesApi, SIGNAL(noteUpdated(int,QJsonObject)), this, SLOT(update(int,QJsonObject)));
|
||||
connect(mp_notesApi, SIGNAL(noteDeleted(int)), this, SLOT(remove(int)));
|
||||
|
|
|
@ -144,7 +144,7 @@
|
|||
</message>
|
||||
<message>
|
||||
<source>Enable this option to allow selfsigned certificates</source>
|
||||
<translation>Auswählen im selbst signierte Zertifikate zu erlauben</translation>
|
||||
<translation>Auswählen um selbst signierte Zertifikate zu erlauben</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Allow unencrypted connections</source>
|
||||
|
|
|
@ -138,103 +138,103 @@
|
|||
<context>
|
||||
<name>LoginPage</name>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="23"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="147"/>
|
||||
<source>Nextcloud Login</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="173"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="170"/>
|
||||
<source>Nextcloud server</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="246"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="240"/>
|
||||
<source>Username</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="257"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="252"/>
|
||||
<source>Password</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="232"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="225"/>
|
||||
<source>Abort</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="100"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="86"/>
|
||||
<source>Follow the instructions in the browser</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="109"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="99"/>
|
||||
<source>Login successfull!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="106"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="115"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="95"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="104"/>
|
||||
<source>Login failed!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="95"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="78"/>
|
||||
<source>Enter your credentials</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="214"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="207"/>
|
||||
<source>Enforce legacy login</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="232"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="225"/>
|
||||
<source>Login</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="232"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="225"/>
|
||||
<source>Re-Login</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="266"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="261"/>
|
||||
<source>Test Login</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="272"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="267"/>
|
||||
<source>Note</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="280"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="275"/>
|
||||
<source>The <a href="https://apps.nextcloud.com/apps/notes">Notes</a> app needs to be installed on the Nextcloud server for this app to work.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="284"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="279"/>
|
||||
<source>Security</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="291"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="286"/>
|
||||
<source><strong>CAUTION: Your password will be saved without any encryption on the device!</strong><br>Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to <i>Settings</i> → <i>Security</i>.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="295"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="291"/>
|
||||
<source>Do not check certificates</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="296"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="292"/>
|
||||
<source>Enable this option to allow selfsigned certificates</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="305"/>
|
||||
<location filename="../qml/pages/LoginPage.qml" line="301"/>
|
||||
<source>Allow unencrypted connections</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -321,32 +321,32 @@
|
|||
<context>
|
||||
<name>NotesApi</name>
|
||||
<message>
|
||||
<location filename="../src/notesapi.cpp" line="330"/>
|
||||
<location filename="../src/notesapi.cpp" line="333"/>
|
||||
<source>No error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/notesapi.cpp" line="333"/>
|
||||
<location filename="../src/notesapi.cpp" line="336"/>
|
||||
<source>No network connection available</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/notesapi.cpp" line="336"/>
|
||||
<location filename="../src/notesapi.cpp" line="339"/>
|
||||
<source>Failed to communicate with the Nextcloud server</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/notesapi.cpp" line="339"/>
|
||||
<location filename="../src/notesapi.cpp" line="342"/>
|
||||
<source>An error occured while establishing an encrypted connection</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/notesapi.cpp" line="342"/>
|
||||
<location filename="../src/notesapi.cpp" line="345"/>
|
||||
<source>Could not authenticate to the Nextcloud instance</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../src/notesapi.cpp" line="345"/>
|
||||
<location filename="../src/notesapi.cpp" line="348"/>
|
||||
<source>Unknown error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -503,152 +503,152 @@
|
|||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="100"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="99"/>
|
||||
<source>Synchronization</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="104"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="103"/>
|
||||
<source>Auto-Sync</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="105"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="104"/>
|
||||
<source>Periodically pull notes from the server</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="112"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="111"/>
|
||||
<source>Disabled</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="112"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="111"/>
|
||||
<source>every</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="114"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="113"/>
|
||||
<source>Minutes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="115"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="114"/>
|
||||
<source>Seconds</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="136"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="135"/>
|
||||
<source>The Answer is 42</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="137"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="136"/>
|
||||
<source>Congratulation you found the Answer to the Ultimate Question of Life, The Universe, and Everything!</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="145"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="144"/>
|
||||
<source>Appearance</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="153"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="152"/>
|
||||
<source>No sorting</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="177"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="176"/>
|
||||
<source>Favorites on top</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="178"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="177"/>
|
||||
<source>Show notes marked as favorite above the others</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="216"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="215"/>
|
||||
<source>Reset</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="219"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="218"/>
|
||||
<source>Reset app settings</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="229"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="228"/>
|
||||
<source>Resetting the app wipes all application data from the device! This includes offline synced notes, app settings and accounts.</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="150"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="149"/>
|
||||
<source>Last edited</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="151"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="150"/>
|
||||
<source>Category</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="152"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="151"/>
|
||||
<source>Title alphabetically</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="155"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="154"/>
|
||||
<source>Sort notes by</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="156"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="155"/>
|
||||
<source>This will also change how the notes are grouped</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="183"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="182"/>
|
||||
<source>Show separator</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="184"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="183"/>
|
||||
<source>Show a separator line between the notes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="194"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="193"/>
|
||||
<source>lines</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="195"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="194"/>
|
||||
<source>Number of lines in the preview</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="200"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="199"/>
|
||||
<source>Editing</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="203"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="202"/>
|
||||
<source>Monospaced font</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="204"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="203"/>
|
||||
<source>Use a monospeced font to edit a note</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="209"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="208"/>
|
||||
<source>Capital 'X' in checkboxes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="210"/>
|
||||
<location filename="../qml/pages/SettingsPage.qml" line="209"/>
|
||||
<source>For interoperability with other apps such as Joplin</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
@ -860,27 +860,27 @@ You can also use other markdown syntax inside them.</source>
|
|||
<context>
|
||||
<name>harbour-nextcloudnotes</name>
|
||||
<message>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="121"/>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="60"/>
|
||||
<source>Notes</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="122"/>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="61"/>
|
||||
<source>Offline</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="123"/>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="62"/>
|
||||
<source>Synced</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="137"/>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="76"/>
|
||||
<source>API error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="130"/>
|
||||
<location filename="../qml/harbour-nextcloudnotes.qml" line="69"/>
|
||||
<source>File error</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
|
|
Loading…
Reference in a new issue