diff --git a/harbour-nextcloudnotes.pro b/harbour-nextcloudnotes.pro index 2186a15..8f0b754 100644 --- a/harbour-nextcloudnotes.pro +++ b/harbour-nextcloudnotes.pro @@ -31,7 +31,8 @@ DISTFILES += qml/harbour-nextcloudnotes.qml \ qml/pages/EditPage.qml \ qml/pages/SettingsPage.qml \ qml/pages/AboutPage.qml \ - qml/pages/MarkdownPage.qml + qml/pages/MarkdownPage.qml \ + qml/pages/UnencryptedDialog.qml SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172 diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml index 7f886c6..1b9a22e 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -10,6 +10,7 @@ CoverBackground { CoverActionList { id: coverAction + enabled: appSettings.currentAccount >= 0 CoverAction { iconSource: "image://theme/icon-cover-new" diff --git a/qml/harbour-nextcloudnotes.qml b/qml/harbour-nextcloudnotes.qml index 864128e..97c11f5 100644 --- a/qml/harbour-nextcloudnotes.qml +++ b/qml/harbour-nextcloudnotes.qml @@ -10,35 +10,50 @@ ApplicationWindow ConfigurationGroup { id: appSettings path: "/apps/harbour-nextcloudnotes/settings" - property var accounts: [ ] // FIXME - property int currentAccount: 0 // FIXME - // For testing - Component.onCompleted: { - //appSettings.clear() - //accounts[0] = { server: "127.0.0.1", username: "fu", password: "bar", lastUpdate: new Date(0) } - //accounts[1] = { server: "127.0.0.2", username: "fu", password: "bar", lastUpdate: new Date(0) } - //accounts[2] = { server: "127.0.0.3", username: "fu", password: "bar", lastUpdate: new Date(0) } - console.log("Configured accounts: " + accounts.length) - for(var i=0; i 0 && passwordField.text.length > 0) - onAccepted: { - account = { - server: serverField.text, - username: usernameField.text, - password: passwordField.text, - lastUpdate: new Date(0) - } + property var accountID + ConfigurationGroup { + id: account + path: "/apps/harbour-nextcloudnotes/accounts/" + accountID } - Column { - width: parent.width + canAccept: (nameField.text.length > 0 && serverField.acceptableInput && usernameField.text.length > 0 && passwordField.text.length > 0) + onAccepted: { + account.setValue("name", nameField.text) + account.setValue("server", serverField.text) + account.setValue("username", usernameField.text) + account.setValue("password", passwordField.text) + //accounts.itemAt(iAccount).unsecureConnection = unsecureConnectionTextSwitch.checked + //accounts.itemAt(iAccount).unencryptedConnection = unencryptedConnectionTextSwitch.checked + account.setValue("valid", true) + account.sync() + } - DialogHeader { - id: header - //title: qsTr("Nextcloud Login") - acceptText: qsTr("Login") - } + SilicaFlickable { + anchors.fill: parent + contentHeight: column.height - Image { - anchors.horizontalCenter: parent.horizontalCenter - height: Theme.itemSizeHuge - fillMode: Image.PreserveAspectFit - source: "../img/nextcloud-logo-transparent.png" - } - - TextField { - id: serverField - focus: true + Column { + id: column width: parent.width - text: (typeof(account) !== 'undefined' && account.server.toString().length > 0) ? account.server : "https://" - placeholderText: qsTr("Nextcloud server") - label: placeholderText + " " + qsTr("(starting with \"https://\")") - inputMethodHints: Qt.ImhUrlCharactersOnly - // regExp from https://stackoverflow.com/a/3809435 (EDIT: removed ? after https to force SSL) - validator: RegExpValidator { regExp: /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/ } // TODO disable unencrypted communication - EnterKey.enabled: acceptableInput - EnterKey.iconSource: "image://theme/icon-m-enter-next" - EnterKey.onClicked: usernameField.focus = true - } - TextField { - id: usernameField - width: parent.width - text: (typeof(account) !== 'undefined' && account.username.length > 0) ? account.username : "" - placeholderText: qsTr("Username") - label: placeholderText - inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase - errorHighlight: text.length === 0 && focus === true - EnterKey.enabled: text.length > 0 - EnterKey.iconSource: "image://theme/icon-m-enter-next" - EnterKey.onClicked: passwordField.focus = true - } + DialogHeader { + id: header + //title: qsTr("Nextcloud Login") + acceptText: qsTr("Login") + } - PasswordField { - id: passwordField - width: parent.width - text: (typeof(account) !== 'undefined' && account.password.length > 0) ? account.password : "" - label: placeholderText - errorHighlight: text.length === 0 && focus === true - EnterKey.enabled: text.length > 0 - EnterKey.iconSource: "image://theme/icon-m-enter-accept" - EnterKey.onClicked: loginDialog.accept() + Image { + anchors.horizontalCenter: parent.horizontalCenter + height: Theme.itemSizeHuge + fillMode: Image.PreserveAspectFit + source: "../img/nextcloud-logo-transparent.png" + } + + TextField { + id: nameField + focus: true + width: parent.width + text: account.value("name", qsTr("My Nextcloud account"), String) + placeholderText: qsTr("Account name") + label: placeholderText + errorHighlight: text.length === 0// && focus === true + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: serverField.focus = true + } + + TextField { + id: serverField + width: parent.width + text: account.value("server", "https://", String) + placeholderText: qsTr("Nextcloud server") + label: placeholderText + " " + qsTr("(starting with \"https://\")") + inputMethodHints: Qt.ImhUrlCharactersOnly + // regExp from https://stackoverflow.com/a/3809435 (EDIT: removed ? after https to force SSL) + validator: RegExpValidator { regExp: /https:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)/ } + errorHighlight: !acceptableInput// && focus === true + EnterKey.enabled: acceptableInput + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: usernameField.focus = true + } + + TextField { + id: usernameField + width: parent.width + text: account.value("username", "", String) + placeholderText: qsTr("Username") + label: placeholderText + inputMethodHints: Qt.ImhNoPredictiveText | Qt.ImhNoAutoUppercase + errorHighlight: text.length === 0// && focus === true + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: passwordField.focus = true + } + + PasswordField { + id: passwordField + width: parent.width + text: account.value("password", "", String) + label: placeholderText + errorHighlight: text.length === 0// && focus === true + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-enter-accept" + EnterKey.onClicked: loginDialog.accept() + } + + SectionHeader { + text: qsTr("Security") + } + Label { + x: Theme.horizontalPageMargin + width: parent.width - 2*x + wrapMode: Text.Wrap + color: Theme.secondaryColor + text: qsTr("Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to SettingsSecurity.") + } + /*TextSwitch { + id: unsecureConnectionTextSwitch + checked: appSettings.unsecureConnection + automaticCheck: true + text: qsTr("Do not check certificates") + description: qsTr("Enable this option to allow selfsigned certificates") + onCheckedChanged: { + if (checked) { + + } + else { + unencryptedConnection.checked = false + } + } + } + TextSwitch { + id: unencryptedConnectionTextSwitch + enabled: unsecureConnectionTextSwitch.checked + checked: appSettings.unencryptedConnection + automaticCheck: false + text: qsTr("Allow unencrypted connection") + description: qsTr("") + onClicked: { + if (!checked) { + var dialog = pageStack.push(Qt.resolvedUrl("UnencryptedDialog.qml")) + dialog.accepted.connect(function() { + checked = true + }) + dialog.rejected.connect(function() { + checked = false + }) + } + else + checked = false + } + }*/ } } } diff --git a/qml/pages/NotesApi.qml b/qml/pages/NotesApi.qml index c661827..09ab80d 100644 --- a/qml/pages/NotesApi.qml +++ b/qml/pages/NotesApi.qml @@ -2,12 +2,8 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 Item { - property string name - property var account property var model: ListModel { } - - property string json - property string file: StandardPaths.data + "/" + name + ".json" + property string file: StandardPaths.data + "/" + account.name + ".json" property bool saveFile: false property bool busy: false //property date lastUpdate: new Date(0) @@ -92,7 +88,7 @@ Item { callApi("DELETE", { 'id': id } ) } - onJsonChanged: refresh() + //onJsonChanged: refresh() function flush() { json = "" @@ -100,7 +96,7 @@ Item { filePut.open("PUT", file) filePut.send(json) model.clear() - account.lastUpdate = new Date(0) + account.update = new Date(0) status = 200 } @@ -127,7 +123,7 @@ Item { function parseJson() { var elements = JSON.parse(json) if (elements === null) { - console.log("Error parsing " + name + "-JSON") + console.log("Error parsing " + account.name + "-JSON") elements = "" json = "" return null @@ -140,7 +136,7 @@ Item { /*Component.onCompleted: { if (saveFile) { - if (name === "") { + if (account.name === "") { saveFile = false } else { @@ -153,7 +149,7 @@ Item { update() } else { - console.log("Loaded " + name + " from local JSON file") + console.log("Loaded " + account.name + " from local JSON file") json = fileReq.responseText busy = false } diff --git a/qml/pages/NotesPage.qml b/qml/pages/NotesPage.qml index 54694af..1e2fb86 100644 --- a/qml/pages/NotesPage.qml +++ b/qml/pages/NotesPage.qml @@ -12,28 +12,32 @@ Page { PullDownMenu { busy: notes.busy - MenuLabel { - visible: appSettings.accounts.length > 0 - text: appSettings.accounts.length > 0 ? - (qsTr("Last update") + ": " + - (appSettings.accounts[appSettings.currentAccount].lastUpdate.value === 0 ? - appSettings.accounts[appSettings.currentAccount].lastUpdate.toLocaleString(Qt.locale(), Locale.ShortFormat) : - qsTr("never"))) : "" - } - MenuItem { - text: qsTr("Reload") - visible: appSettings.accounts.length > 0 - onClicked: notes.getNotes() - } MenuItem { text: qsTr("Settings") onClicked: pageStack.push(Qt.resolvedUrl("SettingsPage.qml")) } MenuItem { text: qsTr("Add note") - enabled: appSettings.accounts.length > 0 + enabled: !notes.busy + visible: account.server.length > 0 onClicked: console.log("Add note") } + MenuItem { + text: qsTr("Reload") + enabled: !notes.busy + visible: account.server.length > 0 + onClicked: notes.getNotes() + } + MenuLabel { + visible: account.server.length > 0 + text: qsTr("Last update") + ": " + + account.update.value !== 0 ? + new Date(account.update).toLocaleString(Qt.locale(), Locale.ShortFormat) : + qsTr("never") + //(new Date(appSettings.value("accountUpdates", [appSettings.currentAccount])).value === 0 ? + //new Date(appSettings.value("accountUpdates", [appSettings.currentAccount])).toLocaleString(Qt.locale(), Locale.ShortFormat) : + //qsTr("never")) + } } header: SearchField { @@ -43,12 +47,12 @@ Page { EnterKey.iconSource: "image://theme/icon-m-enter-close" EnterKey.onClicked: focus = false - enabled: false //notesList.count > 0 // TODO + enabled: notesList.count > 0 } currentIndex: -1 Component.onCompleted: { - if (appSettings.accounts.length > 0) { + if (account.valid) { notes.getNotes() } } @@ -141,33 +145,33 @@ Page { running: visible } - ViewPlaceholder { - id: noLoginPlaceholder - enabled: (appSettings.accounts.length === 0) - text: qsTr("No accounts yet") - } - ViewPlaceholder { enabled: notesList.count === 0 && !notes.busy && !noLoginPlaceholder.enabled text: qsTr("No notes yet") hintText: qsTr("Pull down to add a note") } + ViewPlaceholder { + id: noLoginPlaceholder + enabled: appSettings.accountIDs.length <= 0 + text: qsTr("No account yet") + hintText: qsTr("Got to the settings to add an account") + } + + TouchInteractionHint { + id: addAccountHint + Component.onCompleted: if(!account.valid) restart() + interactionMode: TouchInteraction.Pull + direction: TouchInteraction.Down + } + InteractionHintLabel { + anchors.fill: parent + text: qsTr("Open the settings to configure your Nextcloud accounts") + opacity: addAccountHint.running ? 1.0 : 0.0 + Behavior on opacity { FadeAnimation {} } + width: parent.width + } + VerticalScrollDecorator { flickable: notesList } } - - TouchInteractionHint { - id: addAccountHint - Component.onCompleted: if (appSettings.accounts.length === 0) restart() - interactionMode: TouchInteraction.Pull - direction: TouchInteraction.Down - } - InteractionHintLabel { - anchors.fill: parent - text: qsTr("Open the settings to add a Nextcloud account") - opacity: addAccountHint.running ? 1.0 : 0.0 - Behavior on opacity { FadeAnimation {} } - width: parent.width - } - } diff --git a/qml/pages/SettingsPage.qml b/qml/pages/SettingsPage.qml index 43191b6..3ce114b 100644 --- a/qml/pages/SettingsPage.qml +++ b/qml/pages/SettingsPage.qml @@ -1,5 +1,6 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +import Nemo.Configuration 1.0 Page { id: page @@ -30,40 +31,51 @@ Page { } Label { id: noAccountsLabel - visible: appSettings.accounts.length === 0 + visible: appSettings.accountIDs.length <= 0 text: qsTr("No Nextcloud account yet") font.pixelSize: Theme.fontSizeLarge color: Theme.secondaryHighlightColor anchors.horizontalCenter: parent.horizontalCenter } Repeater { - model: appSettings.accounts + model: appSettings.accountIDs.length delegate: ListItem { id: listItem + ConfigurationGroup { + id: account + path: "/apps/harbour-nextcloudnotes/accounts/" + appSettings.accountIDs[index] + } + TextSwitch { anchors.verticalCenter: parent.verticalCenter automaticCheck: false checked: index === appSettings.currentAccount - text: appSettings.accounts[index].username + "@" + appSettings.accounts[index].server - description: checked ? qsTr("Press and hold to edit") : qsTr("Click to choose as active account") - onClicked: appSettings.currentAccount = index + text: account.value("name", qsTr("Account") + " " + (index+1), String) + //enabled: account.value("valid", false, Boolean) + description: account.value("valid", false, Boolean) ? account.value("username", qsTr("user"), String) + "@" + account.value("server", qsTr("server"), String) : qsTr("Press and hold to configure") + onClicked: if (account.value("valid", false, Boolean)) appSettings.currentAccount = index onPressAndHold: listItem.openMenu() } menu: ContextMenu { MenuItem { - text: qsTr("Edit") + text: qsTr("Configure") onClicked: { - var login = pageStack.push(Qt.resolvedUrl("LoginDialog.qml"), { account: appSettings.accounts[index] } ) + var login = pageStack.push(Qt.resolvedUrl("LoginDialog.qml"), { accountID: appSettings.accountIDs[index] }) login.accepted.connect(function() { - console.log(login.account.username + ":" + login.account.password + "@" + login.account.server.toString()) - appSettings.accounts[index] = login.account + update() + }) + login.rejected.connect(function() { + }) } } - MenuItem { + /*MenuItem { text: qsTr("Delete") - visible: false // TODO - } + onClicked: { + accounts.itemAt(index).clear() + // TODO reorder items + } + }*/ } } } @@ -71,23 +83,16 @@ Page { text: qsTr("Add account") anchors.horizontalCenter: parent.horizontalCenter onClicked: { - var login = pageStack.push(Qt.resolvedUrl("LoginDialog.qml")) + var login = pageStack.push(Qt.resolvedUrl("LoginDialog.qml"), { accountID: accounts.add() }) login.accepted.connect(function() { - var list = appSettings.accounts - list.push(login.account) - appSettings.accounts = list - appSettings.sync() - appSettings.currentAccount = appSettings.accounts.length - appSettings.sync() - notes.account = appSettings.accounts[appSettings.currentAccount] - notes.getNotes() + var tmpIDs = appSettings.accountIDs + tmpIDs.push(login.accountID) + appSettings.accountIDs = tmpIDs + }) + login.rejected.connect(function() { }) } } - - SectionHeader { - text: qsTr("Security") - } } VerticalScrollDecorator {} diff --git a/qml/pages/UnencryptedDialog.qml b/qml/pages/UnencryptedDialog.qml new file mode 100644 index 0000000..4c52811 --- /dev/null +++ b/qml/pages/UnencryptedDialog.qml @@ -0,0 +1,34 @@ +import QtQuick 2.0 +import Sailfish.Silica 1.0 + +Dialog { + id: unencryptedDialog + + canAccept: textSwitch.checked + + SilicaFlickable { + anchors.fill: parent + contentHeight: column.height + + Column { + id: column + width: parent.width + + DialogHeader { + } + + Label { + x: Theme.horizontalPageMargin + width: parent.width - 2*x + wrapMode: Text.Wrap + linkColor: Theme.highlightColor + text: qsTr("Your username and password will be transferred unencrypted over the network when you enable this option.
Do not accept unless you know exactly what you are doing!

More information...") + } + + TextSwitch { + id: textSwitch + text: qsTr("I do understand") + } + } + } +} diff --git a/translations/harbour-nextcloudnotes.ts b/translations/harbour-nextcloudnotes.ts index b26954c..cf17dda 100644 --- a/translations/harbour-nextcloudnotes.ts +++ b/translations/harbour-nextcloudnotes.ts @@ -48,6 +48,26 @@ Nextcloud server + + Account name + + + + Security + + + + + + + + Please consider creating a dedicated app password! Open your Nextcloud in a browser and go to <i>Settings</i> → <i>Security</i>. + + + + My Nextcloud account + + MarkdownPage @@ -105,10 +125,6 @@ Reload - - No accounts yet - - never @@ -117,6 +133,14 @@ Open the settings to add a Nextcloud account + + No account yet + + + + Got to the settings to add an account + + SettingsPage @@ -128,36 +152,39 @@ Accounts - - Add account - - - - No Nextcloud account yet - - About - Press and hold to edit + user - Click to choose as active account + server - Edit + Account - Delete + Press and hold to configure - Security + Configure + + + + + UnencryptedDialog + + I do understand + + + + <strong>Your username and password will be transferred unencrypted over the network when you enable this option.<br>Do not accept unless you know exactly what you are doing!</strong ><br><a href="https://github.com/nextcloud/notes/wiki/API-0.2">More information...</a>