From 3f128bcd63c11a7b4e030879b0774a75a8dac0d3 Mon Sep 17 00:00:00 2001 From: Scharel Clemens Date: Sun, 10 Nov 2019 23:04:42 +0100 Subject: [PATCH] Implemented API in C++. Next: I discovered QSortFilterProxyModel :) --- harbour-nextcloudnotes.pro | 5 +- qml/components/NotesApi.qml | 175 ---------- qml/harbour-nextcloudnotes.qml | 57 +-- qml/pages/NotesPage.qml | 43 +-- src/harbour-nextcloudnotes.cpp | 2 - src/note.cpp | 36 +- src/note.h | 6 + src/notesapi.cpp | 10 +- src/notesapi.h | 8 +- src/notesmodel.cpp | 406 +++++++--------------- src/notesmodel.h | 59 +--- src/sslconfiguration.cpp | 22 -- src/sslconfiguration.h | 28 -- translations/harbour-nextcloudnotes-de.ts | 35 +- translations/harbour-nextcloudnotes-sv.ts | 35 +- translations/harbour-nextcloudnotes.ts | 67 ++-- 16 files changed, 334 insertions(+), 660 deletions(-) delete mode 100644 qml/components/NotesApi.qml delete mode 100644 src/sslconfiguration.cpp delete mode 100644 src/sslconfiguration.h diff --git a/harbour-nextcloudnotes.pro b/harbour-nextcloudnotes.pro index 7a7f476..7edf7e2 100644 --- a/harbour-nextcloudnotes.pro +++ b/harbour-nextcloudnotes.pro @@ -18,13 +18,11 @@ DEFINES += APP_VERSION=\\\"$$VERSION\\\" HEADERS += \ src/notesapi.h \ - src/sslconfiguration.h \ src/notesmodel.h \ src/note.h SOURCES += src/harbour-nextcloudnotes.cpp \ src/notesapi.cpp \ - src/sslconfiguration.cpp \ src/notesmodel.cpp \ src/note.cpp @@ -46,8 +44,7 @@ DISTFILES += qml/harbour-nextcloudnotes.qml \ qml/pages/NotesApi.qml \ qml/pages/MITLicense.qml \ qml/pages/GPLLicense.qml \ - qml/pages/SyntaxPage.qml \ - qml/components/NotesApi.qml + qml/pages/SyntaxPage.qml SAILFISHAPP_ICONS = 86x86 108x108 128x128 172x172 diff --git a/qml/components/NotesApi.qml b/qml/components/NotesApi.qml deleted file mode 100644 index e9bf93d..0000000 --- a/qml/components/NotesApi.qml +++ /dev/null @@ -1,175 +0,0 @@ -import QtQuick 2.5 -import Sailfish.Silica 1.0 -import Nemo.Configuration 1.0 - -Item { - property string response - property var categories: [ ] - property string file: StandardPaths.data + "/" + appSettings.currentAccount + ".json" - property bool saveFile: false - property bool busy: jobsRunning > 0 - property int jobsRunning: 0 - property int status: 0 //204 - property string statusText: "No Content" - - onStatusChanged: { - //console.log("Network response: " + statusText + " (" + status + ")") - } - - /*function getNote(id) { - var dict - if (id) { - for (var i = 0; i < model.count; i++) { - dict = model.get(i) - if (dict.id === id) { - return dict - } - } - } - }*/ - - function apiCall(method, data) { - jobsRunning++ - - var endpoint = account.server + "/index.php/apps/notes/api/" + account.version + "/notes" - if (data) { - if (method === "POST" || method === "PUT") { - console.log("Adding note...") - } - else if (data.id && method === "DELETE") { - console.log("Deleting note...") - } - if (method === "GET" || method === "PUT" || method === "DELETE") { - if (data.id) { - endpoint = endpoint + "/" + data.id - } - } - } - - console.log("Calling " + endpoint) - var apiReq = new XMLHttpRequest - apiReq.open(method, endpoint, true) - apiReq.setRequestHeader('User-Agent', 'SailfishOS/harbour-nextcloudnotes') - apiReq.setRequestHeader('OCS-APIRequest', 'true') - apiReq.setRequestHeader("Content-Type", "application/json") - apiReq.setRequestHeader("Authorization", "Basic " + Qt.btoa(account.username + ":" + account.password)) - apiReq.withCredentials = true - //apiReq.timeout = 5000 - apiReq.onreadystatechange = function() { - if (apiReq.readyState === XMLHttpRequest.DONE) { - statusText = apiReq.statusText - status = apiReq.status - if (apiReq.status === 200) { - response = apiReq.responseText - //console.log(response) - console.log("Network response: " + statusText + " (" + status + ")") - } - else if(apiReq.status === 0) { - statusText = qsTr("Unable to connect") - } - /* - else if (apiReq.status === 304) { - console.log("ETag does not differ!") - } - else if (apiReq.status === 401) { - console.log("Unauthorized!") - } - else if (apiReq.status === 404) { - console.log("Note does not exist!") - }*/ - else { - //console.log("Network error: " + apiReq.statusText + " (" + apiReq.status + ")") - } - jobsRunning-- - } - } - if (method === "GET") { - apiReq.send() - } - else if (method === "POST" || method === "PUT" || method === "DELETE") { - apiReq.send(JSON.stringify(data)) - } - else { - console.log("Unsupported method: " + method) - apiReq.abort() - } - } - - function getNotesFromApi() { - apiCall("GET") - } - - function getNoteFromApi(id) { - if (id) { - apiCall("GET", { 'id': id } ) - } - } - - function createNote(data) { - if (data) - apiCall("POST", data) - } - - function updateNote(id, data) { - if (id && data) { - data.id = id - apiCall("PUT", data) - } - } - - function deleteNote(id) { - if (id) - apiCall("DELETE", { 'id': id } ) - } - - // source: https://stackoverflow.com/a/14339782 - /*function getPrettyDate(date) { - var today = new Date() - today.setHours(0) - today.setMinutes(0) - today.setSeconds(0) - today.setMilliseconds(0) - var compDate = new Date(date*1000) - compDate.setHours(0) - compDate.setMinutes(0) - compDate.setSeconds(0) - compDate.setMilliseconds(0) - if (compDate.getTime() === today.getTime()) { - return qsTr("Today") - } else if ((today.getTime() - compDate.getTime()) === (24 * 60 * 60 * 1000)) { - return qsTr("Yesterday") - } else if ((today.getTime() - compDate.getTime()) <= (7 * 24 * 60 * 60 * 1000)) { - return compDate.toLocaleDateString(Qt.locale(), "dddd") - } else if (today.getFullYear() === compDate.getFullYear()) { - return compDate.toLocaleDateString(Qt.locale(), "MMMM") - } else { - return compDate.toLocaleDateString(Qt.locale(), "MMMM yyyy") - } - }*/ - - /*Component.onCompleted: { - if (saveFile) { - if (account.name === "") { - saveFile = false - } - else { - busy = true - var fileReq = new XMLHttpRequest - fileReq.open("GET", file) - fileReq.onreadystatechange = function() { - if (fileReq.readyState === XMLHttpRequest.DONE) { - if (fileReq.responseText === "") { - update() - } - else { - console.log("Loaded " + account.name + " from local JSON file") - json = fileReq.responseText - busy = false - } - } - } - fileReq.send() - } - } - }*/ -} diff --git a/qml/harbour-nextcloudnotes.qml b/qml/harbour-nextcloudnotes.qml index c6fb5a7..543022b 100644 --- a/qml/harbour-nextcloudnotes.qml +++ b/qml/harbour-nextcloudnotes.qml @@ -2,10 +2,7 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import Nemo.Configuration 1.0 import harbour.nextcloudnotes.notesapi 1.0 -import harbour.nextcloudnotes.notesmodel 1.0 -import harbour.nextcloudnotes.sslconfiguration 1.0 import "pages" -//import "components" ApplicationWindow { @@ -26,8 +23,8 @@ ApplicationWindow property string version: value("version", "v0.2", String) property string username: value("username", "", String) property string password: account.value("password", "", String) - property bool unsecureConnection: account.value("unsecureConnection", false, Boolean) - property bool unencryptedConnection: account.value("unencryptedConnection", false, Boolean) + property bool doNotVerifySsl: account.value("doNotVerifySsl", false, Boolean) + property bool allowUnecrypted: account.value("allowUnecrypted", false, Boolean) property date update: value("update", "", Date) onValuesChanged: console.log("A property of the current account has changed") onNameChanged: console.log("Account: " + name) @@ -37,6 +34,7 @@ ApplicationWindow id: appSettings path: "/apps/harbour-nextcloudnotes/settings" + property bool initialized: false property string currentAccount: value("currentAccount", "", String) property var accountIDs: value("accountIDs", [ ], Array) property int autoSyncInterval: value("autoSyncInterval", 0, Number) @@ -48,10 +46,12 @@ ApplicationWindow property bool useCapitalX: value("useCapitalX", false, Boolean) onCurrentAccountChanged: { account.path = "/apps/harbour-nextcloudnotes/accounts/" + currentAccount - //noteListModel.clear() - //api.getNotesFromApi() - api.getAllNotes(); + account.sync() + if (initialized) + notesApi.getAllNotes(); + autoSyncTimer.restart() } + Component.onCompleted: initialized = true function addAccount() { var uuid = uuidv4() @@ -88,26 +88,18 @@ ApplicationWindow } } - /*SslConfiguration { - id: ssl - checkCert: !account.unsecureConnection - }*/ - Timer { id: autoSyncTimer interval: appSettings.autoSyncInterval * 1000 repeat: true running: interval > 0 && appWindow.visible - triggeredOnStart: true + triggeredOnStart: false onTriggered: { - if (!api.busy) { - //api.getNotesFromApi() - api.getAllNotes(); + if (!notesApi.busy) { + notesApi.getAllNotes(); } else { - triggeredOnStart = false restart() - triggeredOnStart = true } } onIntervalChanged: { @@ -118,33 +110,16 @@ ApplicationWindow } NotesApi { - id: api - /*scheme: "https" + id: notesApi + scheme: account.allowUnecrypted ? "http" : "https" host: account.server path: "/index.php/apps/notes/api/" + account.version username: account.username - password: account.password*/ + password: account.password + sslVerify: !account.doNotVerifySsl + Component.onCompleted: getAllNotes() } - Component.onCompleted: { - api.scheme = "https" - api.host = account.server - api.path = "/index.php/apps/notes/api/" + account.version - api.username = account.username - api.password = account.password - } - - /*NotesApi { - id: api - onResponseChanged: noteListModel.applyJSON(response) - } - - /*NotesModel { - id: noteListModel - sortBy: appSettisignangs.sortBy - favoritesOnTop: appSettings.favoritesOnTop - }*/ - initialPage: Component { NotesPage { } } cover: Qt.resolvedUrl("cover/CoverPage.qml") allowedOrientations: defaultAllowedOrientations diff --git a/qml/pages/NotesPage.qml b/qml/pages/NotesPage.qml index 0f6b71e..c73ac66 100644 --- a/qml/pages/NotesPage.qml +++ b/qml/pages/NotesPage.qml @@ -1,12 +1,12 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 import harbour.nextcloudnotes.note 1.0 -import "../components" +import harbour.nextcloudnotes.notesmodel 1.0 Page { id: page - property string searchText: "" + property NotesModel notesModel: notesApi.model() onStatusChanged: { if (status === PageStatus.Active) { @@ -25,7 +25,7 @@ Page { spacing: Theme.paddingLarge PullDownMenu { - busy: api.busy + busy: notesApi.busy MenuItem { text: qsTr("Settings") @@ -34,12 +34,12 @@ Page { MenuItem { text: qsTr("Add note") enabled: appSettings.currentAccount.length > 0 - onClicked: api.createNote( { 'content': "" } ) + onClicked: notesApi.createNote( { 'content': "" } ) } MenuItem { text: enabled ? qsTr("Reload") : qsTr("Updating...") - enabled: appSettings.currentAccount.length > 0 && !api.busy - onClicked: api.getNotesFromApi() + enabled: appSettings.currentAccount.length > 0 && !notesApi.busy + onClicked: notesApi.getAllNotes() } MenuLabel { visible: appSettings.currentAccount.length > 0 @@ -59,7 +59,7 @@ Page { placeholderText: account.name.length > 0 ? account.name : qsTr("Nextcloud Notes") EnterKey.iconSource: "image://theme/icon-m-enter-close" EnterKey.onClicked: focus = false - onTextChanged: noteListModel.searchText = text + onTextChanged: notesModel.searchText = text } Label { id: description @@ -70,24 +70,25 @@ Page { anchors.bottomMargin: Theme.paddingMedium color: Theme.secondaryHighlightColor font.pixelSize: Theme.fontSizeSmall - text: account.username + "@" + account.server.toString().split("://")[1] + text: account.username + "@" + account.server } BusyIndicator { anchors.verticalCenter: searchField.verticalCenter anchors.right: parent.right anchors.rightMargin: Theme.horizontalPageMargin size: BusyIndicatorSize.Medium - running: api.busy && !busyIndicator.running + running: notesApi.busy && !busyIndicator.running } } currentIndex: -1 - model: api.model() + model: notesModel delegate: BackgroundItem { id: note + visible: inSearch contentHeight: titleLabel.height + previewLabel.height + 2*Theme.paddingSmall height: contentHeight + menu.height width: parent.width @@ -103,7 +104,7 @@ Page { } onClicked: pageStack.push(Qt.resolvedUrl("../pages/NotePage.qml"), - { note: noteListModel.get(index), + { //note: noteListModel.get(index), id: id, modified: modified, title: title, @@ -112,8 +113,8 @@ Page { favorite: favorite, etag: etag, error: error, - errorMessage: errorMessage, - date: date + errorMessage: errorMessage + //date: date }) onPressAndHold: menu.open(note) @@ -131,7 +132,7 @@ Page { icon.source: (favorite ? "image://theme/icon-m-favorite-selected?" : "image://theme/icon-m-favorite?") + (note.highlighted ? Theme.secondaryHighlightColor : Theme.secondaryColor) onClicked: { - api.updateNote(id, {'favorite': !favorite} ) + notesApi.updateNote(id, {'favorite': !favorite} ) } } @@ -201,7 +202,7 @@ Page { text: qsTr("Delete") onClicked: { remorse.execute(note, qsTr("Deleting note"), function() { - api.deleteNote(id) + notesApi.deleteNote(id) }) } } @@ -219,7 +220,7 @@ Page { id: busyIndicator anchors.centerIn: parent size: BusyIndicatorSize.Large - running: notesList.count === 0 && api.busy + running: notesList.count === 0 && notesApi.busy } Label { id: busyLabel @@ -232,7 +233,7 @@ Page { horizontalAlignment: Qt.AlignHCenter text: qsTr("Loading notes...") } - /* + ViewPlaceholder { id: noLoginPlaceholder enabled: appSettings.accountIDs.length <= 0 @@ -242,14 +243,14 @@ Page { ViewPlaceholder { id: noNotesPlaceholder - enabled: api.status === 204 && !busyIndicator.running && !noLoginPlaceholder.enabled + enabled: notesApi.status === 204 && !busyIndicator.running && !noLoginPlaceholder.enabled text: qsTr("No notes yet") hintText: qsTr("Pull down to add a note") } ViewPlaceholder { id: noSearchPlaceholder - enabled: notesList.count === 0 && noteListModel.searchText !== "" + enabled: notesList.count === 0 && notesModel.searchText !== "" text: qsTr("No result") hintText: qsTr("Try another query") } @@ -258,9 +259,9 @@ Page { id: errorPlaceholder enabled: notesList.count === 0 && !busyIndicator.running && !noSearchPlaceholder.enabled && !noNotesPlaceholder.enabled && !noLoginPlaceholder.enabled text: qsTr("An error occurred") - hintText: api.statusText + hintText: notesApi.statusText } - */ + TouchInteractionHint { id: addAccountHint interactionMode: TouchInteraction.Pull diff --git a/src/harbour-nextcloudnotes.cpp b/src/harbour-nextcloudnotes.cpp index f089328..cc8640b 100644 --- a/src/harbour-nextcloudnotes.cpp +++ b/src/harbour-nextcloudnotes.cpp @@ -5,7 +5,6 @@ #include "notesapi.h" #include "note.h" #include "notesmodel.h" -#include "sslconfiguration.h" int main(int argc, char *argv[]) { @@ -20,7 +19,6 @@ int main(int argc, char *argv[]) qmlRegisterType("harbour.nextcloudnotes.notesapi", 1, 0, "NotesApi"); qmlRegisterType("harbour.nextcloudnotes.note", 1, 0, "Note"); qmlRegisterType("harbour.nextcloudnotes.notesmodel", 1, 0, "NotesModel"); - qmlRegisterType("harbour.nextcloudnotes.sslconfiguration", 1, 0, "SslConfiguration"); QQuickView* view = SailfishApp::createView(); diff --git a/src/note.cpp b/src/note.cpp index 91b8104..5eca4d6 100644 --- a/src/note.cpp +++ b/src/note.cpp @@ -42,7 +42,7 @@ Note& Note::operator=(const Note& note) { } bool Note::operator==(const Note& note) const { - return same(note); + return equal(note); } bool Note::same(const Note& note) const { @@ -118,3 +118,37 @@ bool Note::searchInNote(const QString &query, const Note ¬e, SearchAttributes } return queryFound; } + +bool Note::lessThanByDate(const Note &n1, const Note &n2) { + return n1.modified() > n2.modified(); +} + +bool Note::lessThanByCategory(const Note &n1, const Note &n2) { + return n1.category() > n2.category(); +} + +bool Note::lessThanByTitle(const Note &n1, const Note &n2) { + return n1.title() > n2.title(); +} + +bool Note::lessThanByDateFavOnTop(const Note &n1, const Note &n2) { + if (n1.favorite() != n2.favorite()) + return n1.favorite(); + else + return n1.modified() > n2.modified(); +} + +bool Note::lessThanByCategoryFavOnTop(const Note &n1, const Note &n2) { + if (n1.favorite() != n2.favorite()) + return n1.favorite(); + else + return n1.category() > n2.category(); +} + +bool Note::lessThanByTitleFavOnTop(const Note &n1, const Note &n2) { + if (n1.favorite() != n2.favorite()) + return n1.favorite(); + else + return n1.title() > n2.title(); +} + diff --git a/src/note.h b/src/note.h index a781677..eb28a2a 100644 --- a/src/note.h +++ b/src/note.h @@ -64,6 +64,12 @@ public: static Note fromjson(const QJsonObject& jobj); static bool searchInNote(const QString &query, const Note ¬e, SearchAttributes criteria = QFlag(SearchAll), Qt::CaseSensitivity cs = Qt::CaseInsensitive); + static bool lessThanByDate(const Note &n1, const Note &n2); + static bool lessThanByCategory(const Note &n1, const Note &n2); + static bool lessThanByTitle(const Note &n1, const Note &n2); + static bool lessThanByDateFavOnTop(const Note &n1, const Note &n2); + static bool lessThanByCategoryFavOnTop(const Note &n1, const Note &n2); + static bool lessThanByTitleFavOnTop(const Note &n1, const Note &n2); signals: void idChanged(int id); diff --git a/src/notesapi.cpp b/src/notesapi.cpp index 6c0ab8d..119ae12 100644 --- a/src/notesapi.cpp +++ b/src/notesapi.cpp @@ -147,24 +147,24 @@ void NotesApi::getNote(int noteId, QStringList excludeFields) { } } -void NotesApi::createNote(QVariantHash fields) { +void NotesApi::createNote(QVariantMap fields) { QUrl url = m_url; url.setPath(url.path() + "/notes"); if (url.isValid()) { qDebug() << "POST" << url.toDisplayString(); m_request.setUrl(url); - m_replies << m_manager.post(m_request, QJsonDocument(QJsonObject::fromVariantHash(fields)).toJson()); + m_replies << m_manager.post(m_request, QJsonDocument(QJsonObject::fromVariantMap(fields)).toJson()); emit busyChanged(busy()); } } -void NotesApi::updateNote(int noteId, QVariantHash fields) { +void NotesApi::updateNote(int noteId, QVariantMap fields) { QUrl url = m_url; url.setPath(url.path() + QString("/notes/%1").arg(noteId)); if (url.isValid()) { qDebug() << "PUT" << url.toDisplayString(); m_request.setUrl(url); - m_replies << m_manager.put(m_request, QJsonDocument(QJsonObject::fromVariantHash(fields)).toJson()); + m_replies << m_manager.put(m_request, QJsonDocument(QJsonObject::fromVariantMap(fields)).toJson()); emit busyChanged(busy()); } } @@ -197,7 +197,7 @@ void NotesApi::replyFinished(QNetworkReply *reply) { //qDebug() << json; } else { - qDebug() << reply->errorString(); + qDebug() << reply->error() << reply->errorString(); } m_replies.removeAll(reply); reply->deleteLater(); diff --git a/src/notesapi.h b/src/notesapi.h index de8216b..fd2f7e4 100644 --- a/src/notesapi.h +++ b/src/notesapi.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "notesmodel.h" @@ -58,10 +59,10 @@ public: Q_INVOKABLE void getAllNotes(QStringList excludeFields = QStringList()); Q_INVOKABLE void getNote(int noteId, QStringList excludeFields = QStringList()); - Q_INVOKABLE void createNote(QVariantHash fields = QVariantHash()); - Q_INVOKABLE void updateNote(int noteId, QVariantHash fields = QVariantHash()); + Q_INVOKABLE void createNote(QVariantMap fields = QVariantMap()); + Q_INVOKABLE void updateNote(int noteId, QVariantMap fields = QVariantMap()); Q_INVOKABLE void deleteNote(int noteId); - Q_INVOKABLE NotesModel& model() const { return *mp_model; } + Q_INVOKABLE NotesModel* model() const { return mp_model; } signals: void sslVerifyChanged(bool verify); @@ -91,6 +92,7 @@ private: QNetworkRequest m_request; QVector m_replies; NotesModel* mp_model; + QSortFilterProxyModel* mp_modelProxy; // TODO: use! }; #endif // NOTESAPI_H diff --git a/src/notesmodel.cpp b/src/notesmodel.cpp index 29d0a09..e39cff5 100644 --- a/src/notesmodel.cpp +++ b/src/notesmodel.cpp @@ -1,4 +1,5 @@ #include "notesmodel.h" +#include // std::sort #include #include #include @@ -8,11 +9,11 @@ NotesModel::NotesModel(QObject *parent) : QAbstractListModel(parent) { m_sortBy = noSorting; - m_favoritesOnTop = false; + m_favoritesOnTop = true; } NotesModel::~NotesModel() { - clear(); + //clear(); } void NotesModel::setSortBy(QString sortBy) { @@ -37,15 +38,24 @@ void NotesModel::setSearchText(QString searchText) { qDebug() << "Searching by:" << searchText; if (searchText != m_searchText) { m_searchText = searchText; - for (int i = 0; i < m_notes.size(); i++) { - if (m_searchText.isEmpty()) { - m_notes[i].param = true; - } - else { - m_notes[i].param = Note::searchInNote(m_searchText, m_notes[i].note); + emit searchTextChanged(m_searchText); + if (m_searchText.isEmpty()) { + m_invisibleIds.clear(); + emit dataChanged(this->index(0), this->index(m_notes.size())); + } + else { + for (int i = 0; i < m_notes.size(); i++) { + if (Note::searchInNote(m_searchText, m_notes[i])) { + //qDebug() << "Note" << m_notes[i].title() << "in search"; + m_invisibleIds.removeAll(m_notes[i].id()); + } + else { + //qDebug() << "Note" << m_notes[i].title() << "not in search"; + m_invisibleIds.append(m_notes[i].id()); + } + emit dataChanged(this->index(i), this->index(i)); } } - emit searchTextChanged(m_searchText); } } @@ -57,54 +67,46 @@ void NotesModel::clearSearch() { search(); } -bool NotesModel::applyJSONobject(const QJsonObject &jobj) { - if (!jobj.isEmpty()) { - Note note = Note::fromjson(jobj); // TODO connect signals - if (!note.error()) { - int position = indexOf(note.id()); - Note oldNote = get(position); - if (position >= 0 && note.etag() != oldNote.etag()) { - qDebug() << "-- Existing note " << note.title() << "changed, updating the model."; - replaceNote(note); - } - else if (position < 0) { - qDebug() << "-- New note" << note.title() << ", adding it to the model."; - insertNote(note); - } - else { - qDebug() << "-- Existing note " << note.title() << "unchanged, nothing to do."; - } - } - else { - qDebug() << "Note contains an error:" << note.errorMessage(); - } - } - else { - qDebug() << "Unknown JSON object. This message should never occure!"; - return false; - } - return true; -} - bool NotesModel::applyJSON(const QJsonDocument &jdoc) { qDebug() << "Applying new JSON input";// << json; if (!jdoc.isNull()) { if (jdoc.isArray()) { qDebug() << "- It's an array..."; + QVector newNotes; QJsonArray jarr = jdoc.array(); while (!jarr.empty()) { //qDebug() << jarr.count() << "JSON Objects to handle..."; QJsonValue jval = jarr.first(); if (jval.isObject()) { //qDebug() << "It's an object, all fine..."; - applyJSONobject(jval.toObject()); + QJsonObject jobj = jval.toObject(); + if (!jobj.isEmpty()) { + newNotes.append(Note::fromjson(jobj)); + } } jarr.pop_front(); } + for (int i = 0; i < m_notes.size(); ++i) { + bool noteToBeRemoved = true; + for (int j = 0; j < newNotes.size(); ++j) { + if (m_notes[i].id() == newNotes[j].id()) + noteToBeRemoved = false; + } + if (noteToBeRemoved) { + qDebug() << "-- Removing note " << m_notes[i].title(); + removeNote(m_notes[i]); + } + } + while (!newNotes.empty()) { + insertNote(newNotes.first()); + newNotes.pop_front(); + } + return true; } else if (jdoc.isObject()) { qDebug() << "- It's a single object..."; - return applyJSONobject(jdoc.object()); + insertNote(Note::fromjson(jdoc.object())); + return true; } else { qDebug() << "Unknown JSON document. This message should never occure!"; @@ -127,22 +129,29 @@ bool NotesModel::applyJSON(const QString &json) { } int NotesModel::insertNote(const Note ¬e) { - int position = insertPosition(note); - ModelNote modelNote; - modelNote.note = note; - modelNote.param = true; - beginInsertRows(QModelIndex(), position, position); - m_notes.insert(position, modelNote); - endInsertRows(); + int position = indexOf(note.id()); + if (position >= 0) { + if (note.etag() != m_notes[position].etag()) { + qDebug() << "-- Existing note " << note.title() << "changed, updating the model."; + m_notes.replace(position, note); + emit dataChanged(this->index(position), this->index(position)); + } + else { + qDebug() << "-- Existing note " << note.title() << "unchanged, nothing to do."; + } + } + else { + qDebug() << "-- New note" << note.title() << ", adding it to the model."; + position = insertPosition(note); + beginInsertRows(QModelIndex(), position, position); + m_notes.insert(position, note); + endInsertRows(); + } return position; } bool NotesModel::removeNote(const Note ¬e) { - return removeNote(note.id()); -} - -bool NotesModel::removeNote(int id) { - int position = indexOf(id); + int position = m_notes.indexOf(note); if (position >= 0 && position < m_notes.size()) { beginRemoveRows(QModelIndex(), position, position); m_notes.removeAt(position); @@ -152,61 +161,19 @@ bool NotesModel::removeNote(int id) { return false; } -bool NotesModel::replaceNote(const Note ¬e) { - int position = indexOf(note.id()); - if (position >= 0 && position < m_notes.size()) { - ModelNote modelNote; - modelNote.note = note; - modelNote.param = m_notes[position].param; - m_notes.replace(position, modelNote); - QVector roles; - roles << ModifiedRole << TitleRole << CategoryRole << ContentRole << FavoriteRole << EtagRole; - emit dataChanged(this->index(position), this->index(position), roles); - return true; +bool NotesModel::removeNote(int id) { + bool retval = false; + for (int i = 0; i < m_notes.size(); ++i) { + if (m_notes[i].id() == id) { + retval |= removeNote(m_notes[i]); + if (i > 0) i--; + } } - return false; + return retval; } -void NotesModel::clear() { - m_searchText.clear(); - beginRemoveRows(QModelIndex(), 0, rowCount()); - m_notes.clear(); - endRemoveRows(); -} - -int NotesModel::indexOf(int id) const { - for (int i = 0; i < m_notes.size(); i++) { - if (m_notes[i].note.id() == id) - return i; - } - return -1; -} - -Note NotesModel::get(int index) const { - Note note; - if (index >= 0 && index < m_notes.size()) { - note = m_notes[index].note; - } - return note; -} - -/* -bool NotesModel::addNote(Note ¬e) { - m_notes.append(note); - return false; -} - -bool NotesModel::addNotes(QList ¬es) { - for (int i = 0; i < notes.length(); i++) { - addNote(notes[i]); - } - return false; -} -*/ - QHash NotesModel::roleNames() const { return QHash { - {NotesModel::VisibleRole, "visible"}, {NotesModel::IdRole, "id"}, {NotesModel::ModifiedRole, "modified"}, {NotesModel::TitleRole, "title"}, @@ -216,17 +183,17 @@ QHash NotesModel::roleNames() const { {NotesModel::EtagRole, "etag"}, {NotesModel::ErrorRole, "error"}, {NotesModel::ErrorMessageRole, "errorMessage"}, - {NotesModel::DateStringRole, "date"} + {NotesModel::InSearchRole, "inSearch"} }; } QHash NotesModel::sortingNames() const { - QHash criteria; - criteria[sortByDate] = "date"; - criteria[sortByCategory] = "category"; - criteria[sortByTitle] = "title"; - criteria[noSorting] = "none"; - return criteria; + return QHash { + {NotesModel::sortByDate, "date"}, + {NotesModel::sortByCategory, "category"}, + {NotesModel::sortByTitle, "title"}, + {NotesModel::noSorting, "none"} + }; } QStringList NotesModel::sortingCriteria() const { @@ -257,17 +224,19 @@ int NotesModel::rowCount(const QModelIndex &parent) const { QVariant NotesModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); - else if (role == VisibleRole) return m_notes[index.row()].param; - else if (role == IdRole) return m_notes[index.row()].note.id(); - else if (role == ModifiedRole) return m_notes[index.row()].note.modified(); - else if (role == TitleRole) return m_notes[index.row()].note.title(); - else if (role == CategoryRole) return m_notes[index.row()].note.category(); - else if (role == ContentRole) return m_notes[index.row()].note.content(); - else if (role == FavoriteRole) return m_notes[index.row()].note.favorite(); - else if (role == EtagRole) return m_notes[index.row()].note.etag(); - else if (role == ErrorRole) return m_notes[index.row()].note.error(); - else if (role == ErrorMessageRole) return m_notes[index.row()].note.errorMessage(); - else if (role == DateStringRole) return m_notes[index.row()].note.dateString(); + else if (role == IdRole) return m_notes[index.row()].id(); + else if (role == ModifiedRole) return m_notes[index.row()].modified(); + else if (role == TitleRole) return m_notes[index.row()].title(); + else if (role == CategoryRole) return m_notes[index.row()].category(); + else if (role == ContentRole) return m_notes[index.row()].content(); + else if (role == FavoriteRole) return m_notes[index.row()].favorite(); + else if (role == EtagRole) return m_notes[index.row()].etag(); + else if (role == ErrorRole) return m_notes[index.row()].error(); + else if (role == ErrorMessageRole) return m_notes[index.row()].errorMessage(); + else if (role == InSearchRole) { + qDebug() << "Invisible:" << m_invisibleIds.contains(m_notes[index.row()].id()); + return !m_invisibleIds.contains(m_notes[index.row()].id()); + } return QVariant(); } @@ -275,99 +244,47 @@ QMap NotesModel::itemData(const QModelIndex &index) const { QMap map; if (!index.isValid()) return map; else { - for (int role = VisibleRole; role <= ErrorMessageRole; role++) { + for (int role = Qt::UserRole; role < Qt::UserRole + 10; ++role) { map.insert(role, data(index, role)); } } return map; } -bool NotesModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid()) return false; - else if (role == ModifiedRole && m_notes[index.row()].note.modified() != value.toUInt()) { - m_notes[index.row()].note.setModified(value.toInt()); - emit dataChanged(this->index(index.row()), this->index(index.row()), QVector { 1, role } ); // TODO remove when signals from Note are connected - emit dataChanged(this->index(index.row()), this->index(index.row()), QVector { 1, DateStringRole} ); // TODO remove when signals from Note are connected - sort(); - return true; - } - else if (role == CategoryRole && m_notes[index.row()].note.category() != value.toString()) { - m_notes[index.row()].note.setCategory(value.toString()); - emit dataChanged(this->index(index.row()), this->index(index.row()), QVector { 1, role } ); // TODO remove when signals from Note are connected - sort(); - return true; - } - else if (role == ContentRole && m_notes[index.row()].note.content() != value.toString()) { - m_notes[index.row()].note.setContent(value.toString()); - emit dataChanged(this->index(index.row()), this->index(index.row()), QVector { 1, role } ); // TODO remove when signals from Note are connected - sort(); - return true; - } - else if (role == FavoriteRole && m_notes[index.row()].note.favorite() != value.toBool()) { - m_notes[index.row()].note.setFavorite(value.toBool()); - emit dataChanged(this->index(index.row()), this->index(index.row()), QVector { 1, role } ); // TODO remove when signals from Note are connected - sort(); - return true; - } - return false; -} - -bool NotesModel::setItemData(const QModelIndex &index, const QMap &roles) { - if (!index.isValid()) return false; - else if (roles.contains(ModifiedRole) || roles.contains(CategoryRole) || roles.contains(ContentRole) || roles.contains(FavoriteRole)) { - QMap::const_iterator i = roles.constBegin(); - while (i != roles.constEnd()) { - setData(index, i.value(), i.key()); - i++; - } - } - return false; -} - void NotesModel::sort() { qDebug() << "Sorting notes in the model"; - QList > notes; - QMap > map; - QMap > favorites; - if (m_sortBy == sortingNames()[sortByDate]) { - emit layoutAboutToBeChanged(QList (), VerticalSortHint); - for (int i = 0; i < m_notes.size(); i++) { - if (m_favoritesOnTop && m_notes[i].note.favorite()) - favorites.insert(QString::number(std::numeric_limits::max() - m_notes[i].note.modified()), m_notes[i]); - else - map.insert(QString::number(std::numeric_limits::max() - m_notes[i].note.modified()), m_notes[i]); + emit layoutAboutToBeChanged(QList (), VerticalSortHint); + if (m_favoritesOnTop) { + if (m_sortBy == sortingNames()[sortByDate]) { + std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByDateFavOnTop); } - notes = favorites.values(); - notes.append(map.values()); - m_notes = notes; - emit layoutChanged(QList (), VerticalSortHint); - } - else if (m_sortBy == sortingNames()[sortByCategory]) { - emit layoutAboutToBeChanged(QList (), VerticalSortHint); - for (int i = 0; i < m_notes.size(); i++) { - if (m_favoritesOnTop && m_notes[i].note.favorite()) - favorites.insert(m_notes[i].note.category(), m_notes[i]); - else - map.insert(m_notes[i].note.category(), m_notes[i]); + else if (m_sortBy == sortingNames()[sortByCategory]) { + std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByCategoryFavOnTop); } - notes = favorites.values(); - notes.append(map.values()); - m_notes = notes; - emit layoutChanged(QList (), VerticalSortHint); - } - else if (m_sortBy == sortingNames()[sortByTitle]) { - emit layoutAboutToBeChanged(QList (), VerticalSortHint); - for (int i = 0; i < m_notes.size(); i++) { - if (m_favoritesOnTop && m_notes[i].note.favorite()) - favorites.insert(m_notes[i].note.title(), m_notes[i]); - else - map.insert(m_notes[i].note.title(), m_notes[i]); + else if (m_sortBy == sortingNames()[sortByTitle]) { + std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByTitleFavOnTop); } - notes = favorites.values(); - notes.append(map.values()); - m_notes = notes; - emit layoutChanged(QList (), VerticalSortHint); } + else { + if (m_sortBy == sortingNames()[sortByDate]) { + std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByDate); + } + else if (m_sortBy == sortingNames()[sortByCategory]) { + std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByCategory); + } + else if (m_sortBy == sortingNames()[sortByTitle]) { + std::sort(m_notes.begin(), m_notes.end(), Note::lessThanByTitle); + } + } + emit layoutChanged(QList (), VerticalSortHint); +} + +int NotesModel::indexOf(int id) const { + for (int i = 0; i < m_notes.size(); i++) { + if (m_notes[i].id() == id) + return i; + } + return -1; } int NotesModel::insertPosition(const Note &n) const { @@ -375,7 +292,7 @@ int NotesModel::insertPosition(const Note &n) const { int upper = m_notes.size(); while (lower < upper) { int middle = qFloor(lower + (upper-lower) / 2); - bool result = noteLessThan(n, m_notes[middle].note); + bool result = noteLessThan(n, m_notes[middle]); if (result) upper = middle; else @@ -386,22 +303,13 @@ int NotesModel::insertPosition(const Note &n) const { bool NotesModel::noteLessThan(const Note &n1, const Note &n2) const { if (m_sortBy == sortingNames()[sortByDate]) { - if (m_favoritesOnTop && n1.favorite() != n2.favorite()) - return n1.favorite(); - else - return n1.modified() > n2.modified(); + return m_favoritesOnTop ? Note::lessThanByDateFavOnTop(n1, n2) : Note::lessThanByDate(n1, n2); } else if (m_sortBy == sortingNames()[sortByCategory]) { - if (m_favoritesOnTop && n1.favorite() != n2.favorite()) - return n1.favorite(); - else - return n1.category() < n2.category(); + return m_favoritesOnTop ? Note::lessThanByCategoryFavOnTop(n1, n2) : Note::lessThanByCategory(n1, n2); } else if (m_sortBy == sortingNames()[sortByTitle]) { - if (m_favoritesOnTop && n1.favorite() != n2.favorite()) - return n1.favorite(); - else - return n1.title() < n2.title(); + return m_favoritesOnTop ? Note::lessThanByTitleFavOnTop(n1, n2) : Note::lessThanByTitle(n1, n2); } else { if (m_favoritesOnTop && n1.favorite() != n2.favorite()) @@ -409,73 +317,3 @@ bool NotesModel::noteLessThan(const Note &n1, const Note &n2) const { } return true; } - -/*bool NotesModel::noteLessThanByDate(const Note &n1, const Note &n2) { - if (m_favoritesOnTop && n1.favorite != n2.favorite) - return n1.favorite; - else - return n1.modified > n2.modified; -} - -bool NotesModel::noteLessThanByCategory(const Note &n1, const Note &n2) { - if (m_favoritesOnTop && n1.favorite != n2.favorite) - return n1.favorite; - else - return n1.category < n2.category; -} - -bool NotesModel::noteLessThanByTitle(const Note &n1, const Note &n2) { - if (m_favoritesOnTop && n1.favorite != n2.favorite) - return n1.favorite; - else - return n1.title < n2.title; -}*/ - -/* -bool NotesModel::insertRow(int row, const QModelIndex &parent) { - beginInsertRows(parent, row, row); - m_notes.insert(row, Note()); - endInsertRows(); - return true; -} - -bool NotesModel::insertRows(int row, int count, const QModelIndex &parent) { - if (count > 0) { - beginInsertRows(parent, row, row+count); - for (int i = 0; i < count; i++) { - m_notes.insert(row + i, Note()); - } - endInsertRows(); - return true; - } - else { - return false; - } -} - -bool NotesModel::removeRow(int row, const QModelIndex &parent) { - if (row >= 0 && row < m_notes.size()) { - beginRemoveRows(parent, row, row); - m_notes.removeAt(row); - endRemoveRows(); - return true; - } - else { - return false; - } -} - -bool NotesModel::removeRows(int row, int count, const QModelIndex &parent) { - if (row >= 0 && row < m_notes.size()) { - beginRemoveRows(parent, row, count); - for (int i = 0; i < count && row + i < m_notes.size(); i++) { - m_notes.removeAt(row); - } - endRemoveRows(); - return true; - } - else { - return false; - } -} -*/ diff --git a/src/notesmodel.h b/src/notesmodel.h index af4f28c..c5c2e49 100644 --- a/src/notesmodel.h +++ b/src/notesmodel.h @@ -5,12 +5,6 @@ #include #include "note.h" -template -struct ModelNote { - N note; - P param; -}; - class NotesModel : public QAbstractListModel { Q_OBJECT public: @@ -32,29 +26,20 @@ public: Q_INVOKABLE void search(QString searchText = QString()); Q_INVOKABLE void clearSearch(); - Q_INVOKABLE bool applyJSON(const QJsonDocument &jdoc); - Q_INVOKABLE bool applyJSON(const QString &json); - Q_INVOKABLE int insertNote(const Note ¬e); - Q_INVOKABLE bool removeNote(const Note ¬e); - Q_INVOKABLE bool removeNote(int position); - Q_INVOKABLE bool replaceNote(const Note ¬e); - Q_INVOKABLE void clear(); - - Q_INVOKABLE int indexOf(int id) const; - Q_INVOKABLE Note get(int index) const; + bool applyJSON(const QJsonDocument &jdoc); + bool applyJSON(const QString &json); enum NoteRoles { - VisibleRole = Qt::UserRole, - IdRole = Qt::UserRole + 1, - ModifiedRole = Qt::UserRole + 2, - TitleRole = Qt::UserRole + 3, - CategoryRole = Qt::UserRole + 4, - ContentRole = Qt::UserRole + 5, - FavoriteRole = Qt::UserRole + 6, - EtagRole = Qt::UserRole + 7, - ErrorRole = Qt::UserRole + 8, - ErrorMessageRole = Qt::UserRole + 9, - DateStringRole = Qt::UserRole + 10 + IdRole = Qt::UserRole, + ModifiedRole = Qt::UserRole + 1, + TitleRole = Qt::UserRole + 2, + CategoryRole = Qt::UserRole + 3, + ContentRole = Qt::UserRole + 4, + FavoriteRole = Qt::UserRole + 5, + EtagRole = Qt::UserRole + 6, + ErrorRole = Qt::UserRole + 7, + ErrorMessageRole = Qt::UserRole + 8, + InSearchRole = Qt::UserRole + 9 }; QHash roleNames() const; @@ -71,14 +56,6 @@ public: virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role) const; QMap itemData(const QModelIndex &index) const; - //virtual QVariant headerData(int section, Qt::Orientation orientation, int role) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role); - virtual bool setItemData(const QModelIndex &index, const QMap &roles); - - //bool insertRow(int row, const QModelIndex &parent); - //bool insertRows(int row, int count, const QModelIndex &parent); - //bool removeRow(int row, const QModelIndex &parent); - //bool removeRows(int row, int count, const QModelIndex &parent); protected: @@ -89,19 +66,21 @@ signals: void searchTextChanged(QString searchText); private: - QList > m_notes; + QVector m_notes; + QVector m_invisibleIds; QString m_sortBy; bool m_favoritesOnTop; QString m_searchText; void sort(); //void update(); - bool applyJSONobject(const QJsonObject &jobj); + int insertNote(const Note ¬e); + bool replaceNote(const Note ¬e); + bool removeNote(const Note ¬e); + bool removeNote(int id); + int indexOf(int id) const; int insertPosition(const Note &n) const; bool noteLessThan(const Note &n1, const Note &n2) const; - /*static bool noteLessThanByDate(const Note &n1, const Note &n2); - static bool noteLessThanByCategory(const Note &n1, const Note &n2); - static bool noteLessThanByTitle(const Note &n1, const Note &n2);*/ }; #endif // NOTESMODEL_H diff --git a/src/sslconfiguration.cpp b/src/sslconfiguration.cpp deleted file mode 100644 index 006b385..0000000 --- a/src/sslconfiguration.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "sslconfiguration.h" - -#include - -SslConfiguration::SslConfiguration(QObject *parent) : QObject(parent), _checkCert(true) { - checkCertConfig = noCheckConfig = QSslConfiguration::defaultConfiguration(); - noCheckConfig.setPeerVerifyMode(QSslSocket::VerifyNone); - QSslConfiguration::setDefaultConfiguration(checkCertConfig); -} - -bool SslConfiguration::checkCert() { - return _checkCert; -} - -void SslConfiguration::setCheckCert(bool check) { - if (_checkCert != check) { - qDebug() << "Changing SSL Cert check to" << check; - _checkCert = check; - QSslConfiguration::setDefaultConfiguration(_checkCert ? checkCertConfig : noCheckConfig); - emit checkCertChanged(_checkCert); - } -} diff --git a/src/sslconfiguration.h b/src/sslconfiguration.h deleted file mode 100644 index fcd7664..0000000 --- a/src/sslconfiguration.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef SSLCONFIGURATION_H -#define SSLCONFIGURATION_H - -#include -#include -#include - -class SslConfiguration : public QObject -{ - Q_OBJECT - Q_PROPERTY(bool checkCert READ checkCert WRITE setCheckCert NOTIFY checkCertChanged) -public: - explicit SslConfiguration(QObject *parent = nullptr); - -public slots: - bool checkCert(); - void setCheckCert(bool check); - -signals: - void checkCertChanged(bool check); - -private: - bool _checkCert; - QSslConfiguration checkCertConfig; - QSslConfiguration noCheckConfig; -}; - -#endif // SSLCONFIGURATION_H diff --git a/translations/harbour-nextcloudnotes-de.ts b/translations/harbour-nextcloudnotes-de.ts index 3f65e3f..c1c0d3c 100644 --- a/translations/harbour-nextcloudnotes-de.ts +++ b/translations/harbour-nextcloudnotes-de.ts @@ -213,13 +213,6 @@ GeƤndert - - NotesApi - - Unable to connect - - - NotesPage @@ -270,6 +263,34 @@ Open the settings to configure your Nextcloud accounts + + No account yet + + + + Got to the settings to add an account + + + + No notes yet + + + + Pull down to add a note + + + + No result + + + + Try another query + + + + An error occurred + + SettingsPage diff --git a/translations/harbour-nextcloudnotes-sv.ts b/translations/harbour-nextcloudnotes-sv.ts index b62afb1..ea553ac 100644 --- a/translations/harbour-nextcloudnotes-sv.ts +++ b/translations/harbour-nextcloudnotes-sv.ts @@ -213,13 +213,6 @@ Ingen kategori - - NotesApi - - Unable to connect - - - NotesPage @@ -270,6 +263,34 @@ Open the settings to configure your Nextcloud accounts + + No account yet + + + + Got to the settings to add an account + + + + No notes yet + + + + Pull down to add a note + + + + No result + + + + Try another query + + + + An error occurred + + SettingsPage diff --git a/translations/harbour-nextcloudnotes.ts b/translations/harbour-nextcloudnotes.ts index 4bc858d..86df888 100644 --- a/translations/harbour-nextcloudnotes.ts +++ b/translations/harbour-nextcloudnotes.ts @@ -259,73 +259,100 @@ - - NotesApi - - - Unable to connect - - - NotesPage - + Settings - + Add note - + Reload - + Updating... - + Last update - + never - + Nextcloud Notes - + Modified - + Delete - + Deleting note - + Loading notes... - + + No account yet + + + + + Got to the settings to add an account + + + + + No notes yet + + + + + Pull down to add a note + + + + + No result + + + + + Try another query + + + + + An error occurred + + + + Open the settings to configure your Nextcloud accounts