diff --git a/src/db/script.sql b/src/db/script.sql index 000e1c6..e71686b 100644 --- a/src/db/script.sql +++ b/src/db/script.sql @@ -1,50 +1,58 @@ CREATE TABLE IF NOT EXISTS NOTES ( id TEXT PRIMARY KEY, title TEXT, url TEXT, color TEXT, favorite INT, pin INT, adddate DATE, modified DATE ); CREATE TABLE IF NOT EXISTS NOTES_SYNC ( id TEXT, server TEXT, user TEXT, stamp TEXT, PRIMARY KEY(server, stamp), FOREIGN KEY(id) REFERENCES NOTES(id) ); CREATE TABLE IF NOT EXISTS BOOKS ( -id TEXT PRIMARY KEY, +title TEXT PRIMARY KEY, url TEXT, -title TEXT NOT NULL, favorite INT, adddate DATE, modified DATE ); CREATE TABLE IF NOT EXISTS BOOKLETS ( id TEXT, book TEXT, url TEXT, title TEXT NOT NULL, adddate DATE, modified DATE, PRIMARY KEY(id, book), -FOREIGN KEY(book) REFERENCES BOOKS(id) +FOREIGN KEY(book) REFERENCES BOOKS(title) +); + +CREATE TABLE IF NOT EXISTS BOOKLETS_SYNC ( +id TEXT, +server TEXT, +user TEXT, +stamp TEXT, +PRIMARY KEY(server, stamp), +FOREIGN KEY(id) REFERENCES BOOKLETS(id) ); CREATE TABLE IF NOT EXISTS LINKS ( url TEXT PRIMARY KEY, title TEXT, preview TEXT, color TEXT, favorite INT, pin INT, adddate DATE, modified DATE ); diff --git a/src/models/books/booklet.cpp b/src/models/books/booklet.cpp index 886e8dd..faa7ce2 100644 --- a/src/models/books/booklet.cpp +++ b/src/models/books/booklet.cpp @@ -1,120 +1,122 @@ #include "booklet.h" #include "syncer.h" +#include "nextnote.h" Booklet::Booklet(Syncer *_syncer, QObject *parent) : MauiList(parent), syncer(_syncer) -{ +{ + connect(this->syncer, &Syncer::bookletReady, [&](FMH::MODEL_LIST booklets) { emit this->preListChanged(); this->m_list = booklets; emit this->postListChanged(); }); connect(this, &Booklet::bookChanged, syncer, &Syncer::getBooklet); } FMH::MODEL_LIST Booklet::items() const { return this->m_list; } void Booklet::setSortBy(const Booklet::SORTBY &sort) { } Booklet::SORTBY Booklet::getSortBy() const { return this->sort; } void Booklet::setOrder(const Booklet::ORDER &order) { } Booklet::ORDER Booklet::getOrder() const { return this->order; } QString Booklet::getBook() const { return m_book; } -void Booklet::setBook(const QString &book) //book id +void Booklet::setBook(const QString &book) //book id title { if (m_book == book) return; this->setBookTitle(book); m_book = book; emit bookChanged(m_book); } void Booklet::insert(const QVariantMap &data) { emit this->preItemAppended(); auto __booklet = FMH::toModel(data); __booklet[FMH::MODEL_KEY::MODIFIED] = QDateTime::currentDateTime().toString(Qt::TextDate); __booklet[FMH::MODEL_KEY::ADDDATE] = QDateTime::currentDateTime().toString(Qt::TextDate); this->syncer->insertBooklet(this->m_book, __booklet); this->m_list << __booklet; emit this->postItemAppended(); } void Booklet::update(const QVariantMap &data, const int &index) { qDebug()<< "Trying to udpate a booklet" << data << index; if(index < 0 || index >= this->m_list.size()) return; auto newData = this->m_list[index]; QVector roles; for(const auto &key : data.keys()) if(newData[FMH::MODEL_NAME_KEY[key]] != data[key].toString()) { newData[FMH::MODEL_NAME_KEY[key]] = data[key].toString(); roles << FMH::MODEL_NAME_KEY[key]; } this->m_list[index] = newData; newData[FMH::MODEL_KEY::MODIFIED] = QDateTime::currentDateTime().toString(Qt::TextDate); this->syncer->updateBooklet(newData[FMH::MODEL_KEY::ID], this->m_book, newData); emit this->updateModel(index, roles); } void Booklet::remove(const int &index) { } void Booklet::sortList() { } void Booklet::setBookTitle(const QString &title) { if (m_bookTitle == title) return; m_bookTitle = title; emit bookTitleChanged(m_bookTitle); } QVariantMap Booklet::get(const int &index) const { if(index >= this->m_list.size() || index < 0) return QVariantMap(); return FMH::toMap(this->m_list.at(index)); } diff --git a/src/models/books/books.cpp b/src/models/books/books.cpp index 8be6c42..69a518f 100644 --- a/src/models/books/books.cpp +++ b/src/models/books/books.cpp @@ -1,117 +1,131 @@ #include "books.h" #include "syncer.h" #include "nextnote.h" #include "booklet.h" +#ifdef STATIC_MAUIKIT +#include "mauiaccounts.h" +#include "mauiapp.h" +#else +#include +#include +#endif + Books::Books(QObject *parent) : MauiList(parent), syncer(new Syncer(this)), m_booklet(new Booklet(syncer, this)) { this->syncer->setProvider(new NextNote); - connect(this, &Books::currentBookChanged, this, &Books::openBook); + const auto m_account = MauiApp::instance()->getAccounts(); + connect(m_account, &MauiAccounts::currentAccountChanged, [&](QVariantMap currentAccount) + { + Q_UNUSED(currentAccount) + this->syncer->getBooks(); + }); + connect(this, &Books::currentBookChanged, this, &Books::openBook); connect(syncer, &Syncer::booksReady, [&](FMH::MODEL_LIST books) { emit this->preListChanged(); this->m_list = books; qDebug()<< "ALL THE BOOKS ARE < "<< this->m_list; emit this->postListChanged(); }); this->syncer->getBooks(); } FMH::MODEL_LIST Books::items() const { return this->m_list; } void Books::setSortBy(const Books::SORTBY &sort) { } Books::SORTBY Books::getSortBy() const { return this->sort; } void Books::setOrder(const Books::ORDER &order) { } Books::ORDER Books::getOrder() const { return this->order; } Booklet *Books::getBooklet() const { return m_booklet; } int Books::getCurrentBook() const { return m_currentBook; } void Books::sortList() { } QVariantMap Books::get(const int &index) const { if(index >= this->m_list.size() || index < 0) return QVariantMap(); return FMH::toMap(this->m_list.at(index)); } bool Books::insert(const QVariantMap &book) { emit this->preItemAppended(); auto __book = FMH::toModel(book); __book[FMH::MODEL_KEY::THUMBNAIL] = "qrc:/booklet.svg"; __book[FMH::MODEL_KEY::LABEL] =__book[FMH::MODEL_KEY::TITLE]; __book[FMH::MODEL_KEY::MODIFIED] = QDateTime::currentDateTime().toString(Qt::TextDate); __book[FMH::MODEL_KEY::ADDDATE] = QDateTime::currentDateTime().toString(Qt::TextDate); this->syncer->insertBook(__book); this->m_list << __book; qDebug() << m_list; emit this->postItemAppended(); return true; } bool Books::update(const QVariantMap &data, const int &index) { return false; } bool Books::remove(const int &index) { return false; } void Books::openBook(const int &index) { if(index >= this->m_list.size() || index < 0) return; - this->m_booklet->setBook(this->m_list.at(index)[FMH::MODEL_KEY::ID]); + this->m_booklet->setBook(this->m_list.at(index)[FMH::MODEL_KEY::TITLE]); this->m_booklet->setBookTitle(this->m_list.at(index)[FMH::MODEL_KEY::TITLE]); } void Books::setCurrentBook(int currentBook) { if (m_currentBook == currentBook) return; m_currentBook = currentBook; emit currentBookChanged(m_currentBook); } diff --git a/src/models/notes/notes.cpp b/src/models/notes/notes.cpp index 7eeec14..53cd296 100644 --- a/src/models/notes/notes.cpp +++ b/src/models/notes/notes.cpp @@ -1,213 +1,211 @@ #include "notes.h" #include "syncer.h" #include "nextnote.h" #ifdef STATIC_MAUIKIT #include "tagging.h" #include "fm.h" +#include "mauiaccounts.h" +#include "mauiapp.h" #else #include #include +#include +#include #endif Notes::Notes(QObject *parent) : MauiList(parent), syncer(new Syncer(this)) { qDebug()<< "CREATING NOTES LIST"; this->syncer->setProvider(new NextNote); - connect(this, &Notes::accountChanged, syncer, &Syncer::getNotes); + const auto m_account = MauiApp::instance()->getAccounts(); + connect(m_account, &MauiAccounts::currentAccountChanged, [&](QVariantMap currentAccount) + { + Q_UNUSED(currentAccount) + this->syncer->getNotes(); + }); + connect(this, &Notes::sortByChanged, this, &Notes::sortList); connect(this, &Notes::orderChanged, this, &Notes::sortList); connect(syncer, &Syncer::notesReady, [&](FMH::MODEL_LIST data) { emit this->preListChanged(); this->notes = data; emit this->postListChanged(); }); this->syncer->getNotes(); } void Notes::sortList() { emit this->preListChanged(); const auto key = static_cast(this->sort); qDebug()<< "SORTING LIST BY"<< this->sort; std::sort(this->notes.begin(), this->notes.end(), [&](const FMH::MODEL &e1, const FMH::MODEL &e2) -> bool { switch(key) { case FMH::MODEL_KEY::FAVORITE: { return e1[key] == "true"; } case FMH::MODEL_KEY::ADDDATE: case FMH::MODEL_KEY::MODIFIED: { const auto date1 = QDateTime::fromString(e1[key], Qt::TextDate); const auto date2 = QDateTime::fromString(e2[key], Qt::TextDate); if(this->order == Notes::ORDER::ASC) { if(date1.secsTo(QDateTime::currentDateTime()) > date2.secsTo(QDateTime::currentDateTime())) return true; } if(this->order == Notes::ORDER::DESC) { if(date1.secsTo(QDateTime::currentDateTime()) < date2.secsTo(QDateTime::currentDateTime())) return true; } break; } case FMH::MODEL_KEY::TITLE: case FMH::MODEL_KEY::COLOR: { const auto str1 = QString(e1[key]).toLower(); const auto str2 = QString(e2[key]).toLower(); if(this->order == Notes::ORDER::ASC) { if(str1 < str2) return true; } if(this->order == Notes::ORDER::DESC) { if(str1 > str2) return true; } break; } default: if(e1[key] < e2[key]) return true; } return false; }); emit this->postListChanged(); } FMH::MODEL_LIST Notes::items() const { return this->notes; } void Notes::setSortBy(const Notes::SORTBY &sort) { if(this->sort == sort) return; this->sort = sort; emit this->sortByChanged(); } Notes::SORTBY Notes::getSortBy() const { return this->sort; } void Notes::setOrder(const Notes::ORDER &order) { if(this->order == order) return; this->order = order; emit this->orderChanged(); } Notes::ORDER Notes::getOrder() const { return this->order; } bool Notes::insert(const QVariantMap ¬e) { emit this->preItemAppended(); auto __note = FMH::toModel(note); __note[FMH::MODEL_KEY::MODIFIED] = QDateTime::currentDateTime().toString(Qt::TextDate); __note[FMH::MODEL_KEY::ADDDATE] = QDateTime::currentDateTime().toString(Qt::TextDate); this->syncer->insertNote(__note); this->notes << __note; emit this->postItemAppended(); return true; } bool Notes::update(const QVariantMap &data, const int &index) { if(index < 0 || index >= this->notes.size()) return false; auto newData = this->notes[index]; QVector roles; for(const auto &key : data.keys()) if(newData[FMH::MODEL_NAME_KEY[key]] != data[key].toString()) { newData[FMH::MODEL_NAME_KEY[key]] = data[key].toString(); roles << FMH::MODEL_NAME_KEY[key]; } this->notes[index] = newData; newData[FMH::MODEL_KEY::MODIFIED] = QDateTime::currentDateTime().toString(Qt::TextDate); this->syncer->updateNote(newData[FMH::MODEL_KEY::ID], newData); emit this->updateModel(index, roles); return true; } bool Notes::remove(const int &index) { if(index < 0 || index >= this->notes.size()) return false; emit this->preItemRemoved(index); this->syncer->removeNote(this->notes.at(index)[FMH::MODEL_KEY::ID]); this->notes.removeAt(index); emit this->postItemRemoved(); return true; } -void Notes::setAccount(const QVariantMap &account) -{ - this->m_account = account; - syncer->setAccount(FM::toModel(this->m_account)); - emit accountChanged(); -} - -QVariantMap Notes::getAccount() const -{ - return this->m_account; -} - QVariantList Notes::getTags(const int &index) { // if(index < 0 || index >= this->notes.size()) // return QVariantList(); // auto id = this->notes.at(index)[FMH::MODEL_KEY::ID]; // return this->tag->getAbstractTags(OWL::TABLEMAP[OWL::TABLE::NOTES], id); return QVariantList(); } QVariantMap Notes::get(const int &index) const { if(index >= this->notes.size() || index < 0) return QVariantMap(); return FMH::toMap(this->notes.at(index)); } diff --git a/src/models/notes/notes.h b/src/models/notes/notes.h index 8e1d7f2..360bfd1 100644 --- a/src/models/notes/notes.h +++ b/src/models/notes/notes.h @@ -1,79 +1,74 @@ #ifndef NOTES_H #define NOTES_H #include #include "owl.h" #ifdef STATIC_MAUIKIT #include "fmh.h" #include "mauilist.h" #else #include #include #endif class Syncer; class Notes : public MauiList { Q_OBJECT Q_PROPERTY(SORTBY sortBy READ getSortBy WRITE setSortBy NOTIFY sortByChanged) Q_PROPERTY(ORDER order READ getOrder WRITE setOrder NOTIFY orderChanged) - Q_PROPERTY(QVariantMap account READ getAccount WRITE setAccount NOTIFY accountChanged) public: enum ORDER : uint8_t { DESC, ASC }; Q_ENUM(ORDER) enum SORTBY : uint8_t { TITLE = FMH::MODEL_KEY::TITLE, ADDDATE = FMH::MODEL_KEY::ADDDATE, MODIFIED = FMH::MODEL_KEY::MODIFIED, COLOR = FMH::MODEL_KEY::COLOR, FAVORITE = FMH::MODEL_KEY::FAVORITE, PIN = FMH::MODEL_KEY::PIN }; Q_ENUM(SORTBY) explicit Notes(QObject *parent = nullptr); FMH::MODEL_LIST items() const override final; void setSortBy(const SORTBY &sort); SORTBY getSortBy() const; void setOrder(const ORDER &order); ORDER getOrder() const; - void setAccount(const QVariantMap &account); - QVariantMap getAccount() const; - private: Syncer *syncer; FMH::MODEL_LIST notes; QVariantMap m_account; void sortList(); SORTBY sort = SORTBY::MODIFIED; ORDER order = ORDER::DESC; signals: void orderChanged(); void sortByChanged(); - void accountChanged(); public slots: QVariantList getTags(const int &index); QVariantMap get(const int &index) const; bool insert(const QVariantMap ¬e); bool update(const QVariantMap &data, const int &index); bool remove(const int &index); }; #endif // NOTES_H diff --git a/src/providers/abstractnotesprovider.h b/src/providers/abstractnotesprovider.h index 0316bef..92fd2de 100644 --- a/src/providers/abstractnotesprovider.h +++ b/src/providers/abstractnotesprovider.h @@ -1,152 +1,166 @@ #ifndef ABSTRACTNOTESPROVIDER_H #define ABSTRACTNOTESPROVIDER_H #include #include #ifdef STATIC_MAUIKIT #include "fmh.h" #else #include #endif /** * @brief The AbstractNoteSyncer class * is an abstraction for different services backend to sync notes. * Different services to be added to Buho are expected to derived from this. */ class AbstractNotesProvider : public QObject { Q_OBJECT public: AbstractNotesProvider(QObject *parent) : QObject(parent) {} virtual ~AbstractNotesProvider() {} /** * @brief setCredentials * sets the credential to authenticate to the provider server * @param account * the account data is represented by FMH::MODEL */ virtual void setCredentials(const FMH::MODEL &account) final { this->m_user = account[FMH::MODEL_KEY::USER]; this->m_password = account[FMH::MODEL_KEY::PASSWORD]; this->m_provider = QUrl(account[FMH::MODEL_KEY::SERVER]).host(); } virtual QString user() final { return this->m_user; } virtual QString provider() final { return this->m_provider; } /** * @brief isValid * check if the account acredentials are valid * by checking they are not empty or null * @return * true if the credentials are all set or false is somethign is missing */ virtual bool isValid() final { return !(this->m_user.isEmpty() || this->m_user.isNull() || this->m_provider.isEmpty() || this->m_provider.isNull() || this->m_password.isEmpty() || this->m_password.isNull()); } /** * @brief getNote * gets a note identified by an ID * @param id * When the process is done it shoudl emit the noteReady(FMH::MODEL) signal */ // virtual FMH::MODEL getNote(const QString &id) = 0; virtual void getNote(const QString &id) = 0; + virtual void getBooklet(const QString &id) = 0; /** * @brief getNotes * returns all the notes or queried notes * When the process is done it shoudl emit the notesReady(FMH::MODEL_LIST) signal */ virtual void getNotes() = 0; + virtual void getBooklets() = 0; // virtual void getNotes() const {} // virtual FMH::MODEL_LIST getNotes(const QString &query = QString()) = 0; // virtual FMH::MODEL_LIST getNotes(const QString &query = QString()) const = 0; /** * @brief insertNote * inserts a new note to the server * @param note * takes the new note to be inserted represented as FMH::MODEL * When the process is done it shoudl emit the noteInserted(FMH::MODEL) signal */ // virtual bool insertNote(const FMH::MODEL ¬e) = 0; virtual void insertNote(const FMH::MODEL ¬e) = 0; + virtual void insertBooklet(const FMH::MODEL &booklet) = 0; /** * @brief updateNote * allows to update a note in the server, it takes an ID and the updated note * @param id * id of the note to be updated * @param note * the note prepresented as FMH::MODEL contening the up-to-date values * When the process is done it shoudl emit the noteUpdated(FMH::MODEL) signal */ // virtual bool updateNote(const QString &id, const FMH::MODEL ¬e) = 0; virtual void updateNote(const QString &id, const FMH::MODEL ¬e) = 0; + virtual void updateBooklet(const QString &id, const FMH::MODEL &booklet) = 0; /** * @brief removeNote * removes a note from the server * @param id * ID of the note to be removed * When the process is done it shoudl emit the noteRemoved(FMH::MODEL) signal */ // virtual bool removeNote(const QString &id) = 0; virtual void removeNote(const QString &id) = 0; + virtual void removeBooklet(const QString &id) = 0; protected: QString m_user = ""; QString m_password = ""; QString m_provider = ""; template void request(const QString &url, const QMap &header, T cb) // inline void request(const QString &url, const QMap &header, std::functioncb) { auto downloader = new FMH::Downloader; connect(downloader, &FMH::Downloader::dataReady, [&, downloader = std::move(downloader)](const QByteArray &array) { // if(cb != nullptr) cb(array); downloader->deleteLater(); }); downloader->getArray(url, header); } signals: - void noteReady(FMH::MODEL note); - void notesReady(FMH::MODEL_LIST notes); - void noteInserted(FMH::MODEL note); - void noteUpdated(FMH::MODEL note); - void noteRemoved(); + void noteReady(FMH::MODEL note); + void bookletReady(FMH::MODEL booklet); + + void notesReady(FMH::MODEL_LIST notes); + void bookletsReady(FMH::MODEL_LIST booklets); + + void noteInserted(FMH::MODEL note); + void bookletInserted(FMH::MODEL booklet); + + void noteUpdated(FMH::MODEL note); + void bookletUpdated(FMH::MODEL booklet); + + void noteRemoved(); + void bookletRemoved(); /** * @brief responseReady * gets emitted when the data is ready after requesting the array * with &Downloader::getArray() */ void responseReady(QByteArray array); /** * @brief responseError * emitted if there's an error when trying to get the array */ void responseError(QString); }; #endif // ABSTRACTNOTESPROVIDER_H diff --git a/src/providers/nextnote.cpp b/src/providers/nextnote.cpp index 8ec9c70..9ad6aa8 100644 --- a/src/providers/nextnote.cpp +++ b/src/providers/nextnote.cpp @@ -1,228 +1,346 @@ #include "nextnote.h" #include #include #include #include #include #ifdef STATIC_MAUIKIT #include "fm.h" #else #include #endif const QString NextNote::API = QStringLiteral("https://PROVIDER/index.php/apps/notes/api/v0.2/"); static const inline QNetworkRequest formRequest(const QUrl &url, const QString &user, const QString &password) { if(!url.isValid() && !user.isEmpty() && !password.isEmpty()) return QNetworkRequest(); const QString concatenated = user + ":" + password; const QByteArray data = concatenated.toLocal8Bit().toBase64(); const QString headerData = "Basic " + data; // QVariantMap headers // { // {"Authorization", headerData.toLocal8Bit()}, // {QString::number(QNetworkRequest::ContentTypeHeader),"application/json"} // }; QNetworkRequest request; request.setUrl(QUrl(url)); request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json"); request.setRawHeader(QString("Authorization").toLocal8Bit(), headerData.toLocal8Bit()); return request; } NextNote::NextNote(QObject *parent) : AbstractNotesProvider(parent) { } NextNote::~NextNote() { } void NextNote::getNote(const QString &id) { auto url = QString(NextNote::API+"%1%2").replace("PROVIDER", this->m_provider).arg("notes/", id); QString concatenated = this->m_user + ":" + this->m_password; QByteArray data = concatenated.toLocal8Bit().toBase64(); QString headerData = "Basic " + data; QMap header {{"Authorization", headerData.toLocal8Bit()}}; auto downloader = new FMH::Downloader; connect(downloader, &FMH::Downloader::dataReady, [&, downloader = std::move(downloader)](QByteArray array) { const auto notes = this->parseNotes(array); emit this->noteReady(notes.isEmpty() ? FMH::MODEL() : notes.first()); downloader->deleteLater(); }); downloader->getArray(url, header); } +void NextNote::getBooklet(const QString &id) +{ + +} + void NextNote::sendNotes(QByteArray array) { // emit this->notesReady(notes); } void NextNote::getNotes() { auto url = NextNote::formatUrl(this->m_user, this->m_password, this->m_provider)+"notes"; QString concatenated = this->m_user + ":" + this->m_password; QByteArray data = concatenated.toLocal8Bit().toBase64(); QString headerData = "Basic " + data; QMap header {{"Authorization", headerData.toLocal8Bit()}}; const auto downloader = new FMH::Downloader; connect(downloader, &FMH::Downloader::dataReady, [&, downloader = std::move(downloader)](QByteArray array) { - emit this->notesReady(this->parseNotes(array)); + //exclude notes that have its own category + FMH::MODEL_LIST notes; + for(const auto &data : this->parseNotes(array)) + if(data[FMH::MODEL_KEY::CATEGORY].isEmpty() || data[FMH::MODEL_KEY::CATEGORY].isNull()) + notes << data; + + emit this->notesReady(notes); + downloader->deleteLater(); + }); + + downloader->getArray(url, header); +} + +void NextNote::getBooklets() +{ + auto url = NextNote::formatUrl(this->m_user, this->m_password, this->m_provider)+"notes"; + + QString concatenated = this->m_user + ":" + this->m_password; + QByteArray data = concatenated.toLocal8Bit().toBase64(); + QString headerData = "Basic " + data; + + QMap header {{"Authorization", headerData.toLocal8Bit()}}; + + const auto downloader = new FMH::Downloader; + connect(downloader, &FMH::Downloader::dataReady, [&, downloader = std::move(downloader)](QByteArray array) + { + //exclude notes that have its own category + FMH::MODEL_LIST booklets; + for(const auto &data : this->parseNotes(array)) + if(!data[FMH::MODEL_KEY::CATEGORY].isEmpty() && !data[FMH::MODEL_KEY::CATEGORY].isNull()) + booklets << data; + + emit this->bookletsReady(booklets); downloader->deleteLater(); }); downloader->getArray(url, header); } void NextNote::insertNote(const FMH::MODEL ¬e) { QByteArray payload = QJsonDocument::fromVariant(FM::toMap(note)).toJson(); qDebug() << "UPLOADING NEW NOT" << QVariant(payload).toString(); const auto url = QString(NextNote::API+"%1").replace("PROVIDER", this->m_provider).arg("notes"); const auto request = formRequest(url, this->m_user, this->m_password); auto restclient = new QNetworkAccessManager; //constructor QNetworkReply *reply = restclient->post(request,payload); connect(reply, &QNetworkReply::finished, [=, __note = note]() { qDebug() << "Note insertyion finished?"; const auto notes = this->parseNotes(reply->readAll()); emit this->noteInserted([&]() -> FMH::MODEL { FMH::MODEL note; if(!notes.isEmpty()) { note = notes.first(); note[FMH::MODEL_KEY::STAMP] = note[FMH::MODEL_KEY::ID]; //adds the id of the local note as a stamp note[FMH::MODEL_KEY::ID] = __note[FMH::MODEL_KEY::ID]; //adds the id of the local note as a stamp note[FMH::MODEL_KEY::SERVER] = this->m_provider; //adds the provider server address note[FMH::MODEL_KEY::USER] = this->m_user; //adds the user name } return note; }()); restclient->deleteLater(); }); } +void NextNote::insertBooklet(const FMH::MODEL &booklet) +{ + QByteArray payload = QJsonDocument::fromVariant(FM::toMap(booklet)).toJson(); + qDebug() << "UPLOADING NEW BOOKLET" << QVariant(payload).toString(); + + const auto url = QString(NextNote::API+"%1").replace("PROVIDER", this->m_provider).arg("notes"); + + const auto request = formRequest(url, this->m_user, this->m_password); + + auto restclient = new QNetworkAccessManager; //constructor + QNetworkReply *reply = restclient->post(request,payload); + connect(reply, &QNetworkReply::finished, [=, __booklet = booklet]() + { + qDebug() << "Note insertyion finished?"; + const auto notes = this->parseNotes(reply->readAll()); + emit this->bookletInserted([&]() -> FMH::MODEL { + FMH::MODEL note; + if(!notes.isEmpty()) + { + note = notes.first(); + note[FMH::MODEL_KEY::STAMP] = note[FMH::MODEL_KEY::ID]; //adds the id of the local note as a stamp + note[FMH::MODEL_KEY::ID] = __booklet[FMH::MODEL_KEY::ID]; //adds the id of the local note as a stamp + note[FMH::MODEL_KEY::SERVER] = this->m_provider; //adds the provider server address + note[FMH::MODEL_KEY::USER] = this->m_user; //adds the user name + } + return note; + }()); + + restclient->deleteLater(); + }); +} + void NextNote::updateNote(const QString &id, const FMH::MODEL ¬e) { if(id.isEmpty() || note.isEmpty()) { qWarning()<< "The id or note are empty. Can not proceed. NextNote::update"; return; } QByteArray payload = QJsonDocument::fromVariant(FM::toMap(FMH::filterModel(note, {FMH::MODEL_KEY::CONTENT, FMH::MODEL_KEY::FAVORITE, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::CATEGORY}))).toJson(); qDebug() << "UPDATING NOTE" << QVariant(payload).toString(); const auto url = QString(NextNote::API+"%1%2").replace("PROVIDER", this->m_provider).arg("notes/", id); qDebug()<< "tryiong to update note" << url; const auto request = formRequest(url, this->m_user, this->m_password); auto restclient = new QNetworkAccessManager; //constructor QNetworkReply *reply = restclient->put(request, payload); connect(reply, &QNetworkReply::finished, [=, __note = note]() { qDebug() << "Note update finished?" << reply->errorString(); const auto notes = this->parseNotes(reply->readAll()); emit this->noteUpdated([&]() -> FMH::MODEL { FMH::MODEL note; if(notes.isEmpty()) return note; note = notes.first(); note[FMH::MODEL_KEY::STAMP] = note[FMH::MODEL_KEY::ID]; //adds the id of the local note as a stamp note[FMH::MODEL_KEY::ID] = __note[FMH::MODEL_KEY::ID]; //adds the id of the local note as a stamp note[FMH::MODEL_KEY::SERVER] = this->m_provider; //adds the provider server address note[FMH::MODEL_KEY::USER] = this->m_user; //adds the user name return note; }()); restclient->deleteLater(); }); } +void NextNote::updateBooklet(const QString &id, const FMH::MODEL &booklet) +{ + if(id.isEmpty() || booklet.isEmpty()) + { + qWarning()<< "The id or note are empty. Can not proceed. NextNote::update"; + return; + } + + QByteArray payload = QJsonDocument::fromVariant(FM::toMap(FMH::filterModel(booklet, {FMH::MODEL_KEY::CONTENT, + FMH::MODEL_KEY::FAVORITE, + FMH::MODEL_KEY::MODIFIED, + FMH::MODEL_KEY::CATEGORY}))).toJson(); + qDebug() << "UPDATING BOOKLET" << QVariant(payload).toString(); + + const auto url = QString(NextNote::API+"%1%2").replace("PROVIDER", this->m_provider).arg("notes/", id); + + qDebug()<< "tryiong to update note" << url; + const auto request = formRequest(url, this->m_user, this->m_password); + + auto restclient = new QNetworkAccessManager; //constructor + QNetworkReply *reply = restclient->put(request, payload); + connect(reply, &QNetworkReply::finished, [=, __booklet = booklet]() + { + qDebug() << "Note update finished?" << reply->errorString(); + const auto booklets = this->parseNotes(reply->readAll()); + emit this->bookletUpdated([&]() -> FMH::MODEL { + FMH::MODEL booklet; + + if(booklets.isEmpty()) + return booklet; + + booklet = booklets.first(); + booklet[FMH::MODEL_KEY::STAMP] = booklet[FMH::MODEL_KEY::ID]; //adds the stamp to the local note form the remote id + booklet[FMH::MODEL_KEY::ID] = __booklet[FMH::MODEL_KEY::TITLE]; //adds back the id of the local booklet + booklet[FMH::MODEL_KEY::SERVER] = this->m_provider; //adds the provider server address + booklet[FMH::MODEL_KEY::USER] = this->m_user; //adds the user name + + return booklet; + }()); + + restclient->deleteLater(); + }); +} + void NextNote::removeNote(const QString &id) { if(id.isEmpty()) { qWarning()<< "The id is empty. Can not proceed. NextNote::remove"; return; } const auto url = QString(NextNote::API+"%1%2").replace("PROVIDER", this->m_provider).arg("notes/", id); const auto request = formRequest(url, this->m_user, this->m_password); qDebug()<< "trying to remove nextnote <<" << url; auto restclient = new QNetworkAccessManager; //constructor QNetworkReply *reply = restclient->deleteResource(request); connect(reply, &QNetworkReply::finished, [=]() { qDebug() << "Note remove finished?" << reply->errorString(); emit this->noteRemoved(); restclient->deleteLater(); }); } +void NextNote::removeBooklet(const QString &id) +{ + +} + const QString NextNote::formatUrl(const QString &user, const QString &password, const QString &provider) { auto url = NextNote::API; url.replace("USER", user); url.replace("PASSWORD", password); url.replace("PROVIDER", provider); return url; } const FMH::MODEL_LIST NextNote::parseNotes(const QByteArray &array) { FMH::MODEL_LIST res; qDebug()<< "trying to parse notes" << array; QJsonParseError jsonParseError; QJsonDocument jsonResponse = QJsonDocument::fromJson(static_cast(array).toUtf8(), &jsonParseError); if (jsonParseError.error != QJsonParseError::NoError) { qDebug()<< "ERROR PARSING"; return res; } const auto data = jsonResponse.toVariant(); if(data.isNull() || !data.isValid()) return res; if(!data.toList().isEmpty()) { for(const auto &map : data.toList()) res << FM::toModel(map.toMap()); } else res << FM::toModel(data.toMap()); return res; } diff --git a/src/providers/nextnote.h b/src/providers/nextnote.h index 7fd3f6d..0509ca2 100644 --- a/src/providers/nextnote.h +++ b/src/providers/nextnote.h @@ -1,40 +1,49 @@ #ifndef NEXTNOTE_H #define NEXTNOTE_H #include #include #include "abstractnotesprovider.h" #include /** * @brief The NextNote class follows the NextCloud API specification * for syncing notes. */ class NextNote : public AbstractNotesProvider { Q_OBJECT public: explicit NextNote(QObject *parent = nullptr); ~NextNote() override final; void getNote(const QString &id) override final; + void getBooklet(const QString &id) override final; + void getNotes() override final; + void getBooklets() override final; + void insertNote(const FMH::MODEL ¬e) override final; + void insertBooklet(const FMH::MODEL &booklet) override final; + void updateNote(const QString &id, const FMH::MODEL ¬e) override final; + void updateBooklet(const QString &id, const FMH::MODEL &booklet) override final; + void removeNote(const QString &id) override final; + void removeBooklet(const QString &id) override final; private: const static QString API; static const QString formatUrl(const QString &user, const QString &password, const QString &provider); static const FMH::MODEL_LIST parseNotes(const QByteArray &array); // template // void request(const QString &url, const QMap &header, T cb); // void request(const QString &url, const QMap &header, std::functioncb); signals: public slots: void sendNotes(QByteArray array); }; #endif // NEXTNOTE_H diff --git a/src/syncing/syncer.cpp b/src/syncing/syncer.cpp index a821de3..431f6f8 100644 --- a/src/syncing/syncer.cpp +++ b/src/syncing/syncer.cpp @@ -1,547 +1,655 @@ #include "syncer.h" #include "db/db.h" #include "abstractnotesprovider.h" #include #ifdef STATIC_MAUIKIT #include "tagging.h" #include "fm.h" +#include "mauiaccounts.h" +#include "mauiapp.h" #else #include #include +#include +#include #endif Syncer::Syncer(QObject *parent) : QObject(parent), tag(Tagging::getInstance()), db(DB::getInstance()), - provider(nullptr) {} + provider(nullptr) { + + const auto m_account = MauiApp::instance()->getAccounts(); + connect(m_account, &MauiAccounts::currentAccountChanged, [&](QVariantMap currentAccount) + { + this->setAccount(FMH::toModel(currentAccount)); + }); +} void Syncer::setAccount(const FMH::MODEL &account) { if(this->provider) this->provider->setCredentials(account); } void Syncer::setProvider(AbstractNotesProvider *provider) { this->provider = std::move(provider); this->provider->setParent(this); this->provider->disconnect(); this->setConections(); } void Syncer::insertNote(FMH::MODEL ¬e) { if(!this->insertNoteLocal(note)) { qWarning()<< "The note could not be inserted locally, " "therefore it was not attempted to insert it to the remote provider server, " "even if it existed."; return; } this->insertNoteRemote(note); emit this->noteInserted(note, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note inserted on the DB locally"}); } void Syncer::updateNote(const QString &id, const FMH::MODEL ¬e) { if(!this->updateNoteLocal(id, note)) { qWarning()<< "The note could not be updated locally, " "therefore it was not attempted to update it on the remote server provider, " "even if it existed."; return; } //to update remote note we need to pass the stamp as the id const auto stamp = Syncer::noteStampFromId(this->db, id); if(!stamp.isEmpty()) this->updateNoteRemote(stamp, note); emit this->noteUpdated(note, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note updated on the DB locally"}); } void Syncer::removeNote(const QString &id) { //to remove the remote note we need to pass the stamp as the id, //and before removing the note locally we need to retireved first const auto stamp = Syncer::noteStampFromId(this->db, id); if(!this->removeNoteLocal(id)) { qWarning()<< "The note could not be inserted locally, " "therefore it was not attempted to insert it to the remote provider server, " "even if it existed."; return; } if(!stamp.isEmpty()) this->removeNoteRemote(stamp); emit this->noteRemoved(FMH::MODEL(), {STATE::TYPE::LOCAL, STATE::STATUS::OK, "The note has been removed from the local DB"}); } void Syncer::getNotes() { const auto notes = this->collectAllNotes(); if(this->provider && this->provider->isValid()) this->provider->getNotes(); else qWarning()<< "Credentials are missing to get notes or the provider has not been set"; emit this->notesReady(notes); } void Syncer::getBooks() { const auto books = this->collectAllBooks(); - // this service is still missing - // if(this->provider && this->provider->isValid()) - // this->provider->getNotes(); - // else - // qWarning()<< "Credentials are missing to get notes or the provider has not been set"; + if(this->provider && this->provider->isValid()) + this->provider->getBooklets(); + else + qWarning()<< "Credentials are missing to get notes or the provider has not been set"; emit this->booksReady(books); } void Syncer::insertBook(FMH::MODEL &book) { if(!this->insertBookLocal(book)) { qWarning()<< "Could not insert Book, Syncer::insertBook"; return; } emit this->bookInserted(book, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Book inserted locally sucessfully"}); } -void Syncer::getBooklet(const QString &id) +void Syncer::getBooklet(const QString &bookId) { - const auto res = this->db->getDBData(QString("select * from booklets where book = '%1'").arg(id)); + const auto res = this->db->getDBData(QString("select * from booklets where book = '%1'").arg(bookId)); emit this->bookletReady(res); } + void Syncer::updateBooklet(const QString &id, const QString &bookId, FMH::MODEL &booklet) { if(!this->updateBookletLocal(id, bookId, booklet)) { qWarning()<< "The booklet could not be updated locally, " "therefore it was not attempted to update it on the remote server provider, " "even if it existed."; return; } - //to update remote booklet we need to pass the stamp as the id - // const auto stamp = Syncer::noteStampFromId(this->db, id); - // if(!stamp.isEmpty()) - // this->updateNoteRemote(stamp, note); + // to update remote booklet we need to pass the stamp as the id + const auto stamp = Syncer::bookletStampFromId(this->db, id); + qDebug()<< "booklet stamp from id" << stamp; + + if(!stamp.isEmpty()) + this->updateBookletRemote(stamp, bookId, booklet); emit this->bookletUpdated(booklet, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Booklet updated locally on the DB"}); } void Syncer::insertBooklet(const QString &bookId, FMH::MODEL &booklet) { if(!this->insertBookletLocal(bookId, booklet)) { qWarning()<< "Could not insert Booklet, Syncer::insertBooklet"; return; } + this->insertBookletRemote(bookId, booklet); emit this->bookletInserted(booklet, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Booklet inserted locally sucessfully"}); } void Syncer::addId(FMH::MODEL &model) { const auto id = QUuid::createUuid().toString(); model[FMH::MODEL_KEY::ID] = id; } const QString Syncer::noteIdFromStamp(DB *_db, const QString &provider, const QString &stamp) { return [&]() -> QString { const auto data = _db->getDBData(QString("select id from notes_sync where server = '%1' AND stamp = '%2'").arg(provider, stamp)); return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::ID]; }(); } const QString Syncer::noteStampFromId(DB *_db, const QString &id) { return [&]() -> QString { const auto data = _db->getDBData(QString("select stamp from notes_sync where id = '%1'").arg(id)); return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::STAMP]; }(); } +const QString Syncer::bookletIdFromStamp(DB *_db, const QString &provider, const QString &stamp) +{ + return [&]() -> QString { + const auto data = _db->getDBData(QString("select id from booklets_sync where server = '%1' AND stamp = '%2'").arg(provider, stamp)); + return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::ID]; + }(); +} + +const QString Syncer::bookletStampFromId(DB *_db, const QString &id) +{ + return [&]() -> QString { + const auto data = _db->getDBData(QString("select stamp from booklets_sync where id = '%1'").arg(id)); + return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::STAMP]; + }(); +} + void Syncer::setConections() { connect(this->provider, &AbstractNotesProvider::noteInserted, [&](FMH::MODEL note) { qDebug()<< "STAMP OF THE NEWLY INSERTED NOTE" << note[FMH::MODEL_KEY::ID] << note; this->db->insert(OWL::TABLEMAP[OWL::TABLE::NOTES_SYNC], FMH::toMap(FMH::filterModel(note, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::STAMP, FMH::MODEL_KEY::USER, FMH::MODEL_KEY::SERVER}))); emit this->noteInserted(note, {STATE::TYPE::REMOTE, STATE::STATUS::OK, "Note inserted on server provider"}); }); + connect(this->provider, &AbstractNotesProvider::bookletInserted, [&](FMH::MODEL booklet) + { + qDebug()<< "STAMP OF THE NEWLY INSERTED BOOKLET" << booklet[FMH::MODEL_KEY::ID] << booklet; + this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKLETS_SYNC], FMH::toMap(FMH::filterModel(booklet, {FMH::MODEL_KEY::ID, + FMH::MODEL_KEY::STAMP, + FMH::MODEL_KEY::USER, + FMH::MODEL_KEY::SERVER}))); + emit this->bookletInserted(booklet, {STATE::TYPE::REMOTE, STATE::STATUS::OK, "Booklet inserted on server provider"}); + }); + connect(this->provider, &AbstractNotesProvider::notesReady, [&](FMH::MODEL_LIST notes) { // qDebug()<< "SERVER NOETS READY "<< notes; //if there are no notes in the provider server, then just return if(notes.isEmpty()) return; qDebug()<< "NOETS READY << " << notes; // there might be two case scenarios: // the note exists locally in the db, so it needs to be updated with the server version // the note does not exists locally, so it needs to be inserted into the db for(const auto ¬e : notes) { const auto id = Syncer::noteIdFromStamp(this->db, this->provider->provider(), note[FMH::MODEL_KEY::ID]); // if the id is empty then the note does nto exists, so ithe note is inserted into the local db if(id.isEmpty()) { //here insert the note into the db auto __note = FMH::filterModel(note, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::CONTENT, FMH::MODEL_KEY::FAVORITE, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::ADDDATE}); __note[FMH::MODEL_KEY::MODIFIED] = QDateTime::fromSecsSinceEpoch(note[FMH::MODEL_KEY::MODIFIED].toInt()).toString(Qt::TextDate); __note[FMH::MODEL_KEY::ADDDATE] = __note[FMH::MODEL_KEY::MODIFIED]; if(!this->insertNoteLocal(__note)) { qWarning()<< "Remote note could not be inserted to the local storage"; continue; } __note[FMH::MODEL_KEY::STAMP] = note[FMH::MODEL_KEY::ID]; __note[FMH::MODEL_KEY::USER] = this->provider->user(); __note[FMH::MODEL_KEY::SERVER] = this->provider->provider(); this->db->insert(OWL::TABLEMAP[OWL::TABLE::NOTES_SYNC], FMH::toMap(FMH::filterModel(__note, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::STAMP, FMH::MODEL_KEY::USER, FMH::MODEL_KEY::SERVER}))); }else { //the note exists in the db locally, so update it auto __note = FMH::filterModel(note, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::CONTENT, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::FAVORITE}); __note[FMH::MODEL_KEY::MODIFIED] = QDateTime::fromSecsSinceEpoch(note[FMH::MODEL_KEY::MODIFIED].toInt()).toString(Qt::TextDate); this->updateNoteLocal(id, __note); emit this->noteInserted(note, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note inserted on local db, from the server provider"}); } } emit this->notesReady(this->collectAllNotes()); }); + connect(this->provider, &AbstractNotesProvider::bookletsReady, [&](FMH::MODEL_LIST booklets) + { + // qDebug()<< "SERVER NOETS READY "<< notes; + + //if there are no notes in the provider server, then just return + if(booklets.isEmpty()) + return; + + qDebug()<< "Booklets READY << " << booklets; + // there might be two case scenarios: + // the booklet exists locally in the db, so it needs to be updated with the server version + // the booklet does not exists locally, so it needs to be inserted into the db + for(const auto &booklet : booklets) + { + const auto id = Syncer::bookletIdFromStamp(this->db, this->provider->provider(), booklet[FMH::MODEL_KEY::ID]); //the id is actually the stamp id + + // if the id is empty then the booklet does not exists, so insert the booklet into the local db + if(id.isEmpty()) + { + //here insert the note into the db + auto __booklet = FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE, + FMH::MODEL_KEY::CONTENT, + FMH::MODEL_KEY::MODIFIED, + FMH::MODEL_KEY::ADDDATE}); + + __booklet[FMH::MODEL_KEY::MODIFIED] = QDateTime::fromSecsSinceEpoch(booklet[FMH::MODEL_KEY::MODIFIED].toInt()).toString(Qt::TextDate); + __booklet[FMH::MODEL_KEY::ADDDATE] = __booklet[FMH::MODEL_KEY::MODIFIED]; + + if(!this->insertBookletLocal(booklet[FMH::MODEL_KEY::CATEGORY], __booklet)) + { + qWarning()<< "Remote booklet could not be inserted to the local storage"; + continue; + } + + __booklet[FMH::MODEL_KEY::STAMP] = booklet[FMH::MODEL_KEY::ID]; + __booklet[FMH::MODEL_KEY::USER] = this->provider->user(); + __booklet[FMH::MODEL_KEY::SERVER] = this->provider->provider(); + + + this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKLETS_SYNC], FMH::toMap(FMH::filterModel(__booklet, {FMH::MODEL_KEY::ID, + FMH::MODEL_KEY::STAMP, + FMH::MODEL_KEY::USER, + FMH::MODEL_KEY::SERVER}))); + + }else + { + //the booklet exists in the db locally, so update it + auto __booklet = FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE, + FMH::MODEL_KEY::CONTENT, + FMH::MODEL_KEY::MODIFIED, + FMH::MODEL_KEY::FAVORITE}); + __booklet[FMH::MODEL_KEY::MODIFIED] = QDateTime::fromSecsSinceEpoch(booklet[FMH::MODEL_KEY::MODIFIED].toInt()).toString(Qt::TextDate); + this->updateNoteLocal(id, __booklet); + emit this->noteInserted(booklet, {STATE::TYPE::LOCAL, STATE::STATUS::OK, "Note inserted on local db, from the server provider"}); + } + } + + emit this->booksReady(this->collectAllBooks()); //??? + }); + connect(this->provider, &AbstractNotesProvider::noteUpdated, [&](FMH::MODEL note) { const auto id = Syncer::noteIdFromStamp(this->db, this->provider->provider(), note[FMH::MODEL_KEY::ID]); if(!note.isEmpty()) this->updateNoteLocal(id, FMH::filterModel(note, {FMH::MODEL_KEY::TITLE})); emit this->noteUpdated(note, {STATE::TYPE::REMOTE, STATE::STATUS::OK, "Note updated on server provider"}); }); + connect(this->provider, &AbstractNotesProvider::bookletUpdated, [&](FMH::MODEL booklet) + { + const auto id = Syncer::bookletIdFromStamp(this->db, this->provider->provider(), booklet[FMH::MODEL_KEY::ID]); + if(!booklet.isEmpty()) + this->updateBookletLocal(id, booklet[FMH::MODEL_KEY::CATEGORY], FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE})); + emit this->bookletUpdated(booklet, {STATE::TYPE::REMOTE, STATE::STATUS::OK, "Booklet updated on server provider"}); + }); + connect(this->provider, &AbstractNotesProvider::noteRemoved, [&]() { emit this->noteRemoved(FMH::MODEL(), {STATE::TYPE::REMOTE, STATE::STATUS::OK, "The note has been removed from the remove server provider"}); }); } bool Syncer::insertNoteLocal(FMH::MODEL ¬e) { if(note.isEmpty()) { qWarning()<< "Could not insert note locally. The note is empty"; return false; } Syncer::addId(note); //adds a local id to the note // create a file for the note const auto __notePath = Syncer::saveNoteFile(OWL::NotesPath, note); if(__notePath.isEmpty()) { qWarning()<< "File could not be saved. Syncer::insertNoteLocal."; return false; } note[FMH::MODEL_KEY::URL] = __notePath.toString(); qDebug()<< "note saved to <<" << __notePath; auto __noteMap = FMH::toMap(FMH::filterModel(note, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::COLOR, FMH::MODEL_KEY::URL, FMH::MODEL_KEY::PIN, FMH::MODEL_KEY::FAVORITE, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::ADDDATE})); if(this->db->insert(OWL::TABLEMAP[OWL::TABLE::NOTES], __noteMap)) { for(const auto &tg : note[FMH::MODEL_KEY::TAG].split(",", QString::SplitBehavior::SkipEmptyParts)) this->tag->tagAbstract(tg, OWL::TABLEMAP[OWL::TABLE::NOTES], note[FMH::MODEL_KEY::ID], note[FMH::MODEL_KEY::COLOR]); return true; } return false; } void Syncer::insertNoteRemote(FMH::MODEL ¬e) { if(this->provider && this->provider->isValid()) this->provider->insertNote(note); } bool Syncer::updateNoteLocal(const QString &id, const FMH::MODEL ¬e) { for(const auto &tg : note[FMH::MODEL_KEY::TAG]) this->tag->tagAbstract(tg, OWL::TABLEMAP[OWL::TABLE::NOTES], id); this->saveNoteFile(OWL::NotesPath, note); return this->db->update(OWL::TABLEMAP[OWL::TABLE::NOTES], FMH::toMap(FMH::filterModel(note, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::COLOR, FMH::MODEL_KEY::PIN, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::FAVORITE})), QVariantMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ID], id}}); } void Syncer::updateNoteRemote(const QString &id, const FMH::MODEL ¬e) { if(this->provider && this->provider->isValid()) this->provider->updateNote(id, note); } bool Syncer::removeNoteLocal(const QString &id) { this->db->remove(OWL::TABLEMAP[OWL::TABLE::NOTES_SYNC], {{FMH::MODEL_NAME[FMH::MODEL_KEY::ID], id}}); return this->db->remove(OWL::TABLEMAP[OWL::TABLE::NOTES], {{FMH::MODEL_NAME[FMH::MODEL_KEY::ID], id}}); } void Syncer::removeNoteRemote(const QString &id) { if(this->provider && this->provider->isValid()) this->provider->removeNote(id); } bool Syncer::insertBookLocal(FMH::MODEL &book) { const auto __path = QUrl::fromLocalFile(OWL::BooksPath+book[FMH::MODEL_KEY::TITLE]); if(FMH::fileExists(__path)) { qWarning()<< "The directory for the book already exists. Syncer::insertBookLocal" << book[FMH::MODEL_KEY::TITLE]; return false; - } if(!FM::createDir(QUrl::fromLocalFile(OWL::BooksPath), book[FMH::MODEL_KEY::TITLE])) { qWarning() << "Could not create directory for the given book name. Syncer::insertBookLocal" << book[FMH::MODEL_KEY::TITLE]; return false; } - Syncer::addId(book); book[FMH::MODEL_KEY::URL] = __path.toString(); - return(this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKS], FMH::toMap(FMH::filterModel(book,{FMH::MODEL_KEY::ID, - FMH::MODEL_KEY::URL, + return(this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKS], FMH::toMap(FMH::filterModel(book,{FMH::MODEL_KEY::URL, FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::FAVORITE, FMH::MODEL_KEY::ADDDATE, FMH::MODEL_KEY::MODIFIED} )))); } void Syncer::insertBookRemote(FMH::MODEL &book) { } bool Syncer::updateBookLocal(const QString &id, const FMH::MODEL &book) { return false; } void Syncer::updateBookRemote(const QString &id, const FMH::MODEL &book) { } bool Syncer::removeBookLocal(const QString &id) { return false; } void Syncer::removeBookRemote(const QString &id) { } bool Syncer::insertBookletLocal(const QString &bookId, FMH::MODEL &booklet) { qDebug()<< "trying to insert booklet" << booklet; if(bookId.isEmpty() || booklet.isEmpty()) { qWarning()<< "Could not insert booklet. Reference to book id or booklet are empty"; return false; } Syncer::addId(booklet); //adds a local id to the booklet - // create a file for the note - const auto __bookTitle = [&]() -> QString { - const auto data = this->db->getDBData(QString("select title from books where id = '%1'").arg(bookId)); - return data.isEmpty() ? QString() : data.first()[FMH::MODEL_KEY::TITLE]; } (); - - if(__bookTitle.isEmpty() || !FMH::fileExists(QUrl::fromLocalFile(OWL::BooksPath+__bookTitle))) + if(!FMH::fileExists(QUrl::fromLocalFile(OWL::BooksPath+bookId))) { - qWarning()<< "The book does not exists in the db or the directory is missing. Syncer::insertBookletLocal"; - return false; + qWarning()<< "The book does not exists in the db or the directory is missing. Syncer::insertBookletLocal. " + "Creating a new book registry" << bookId; + + FMH::MODEL __book; + __book[FMH::MODEL_KEY::TITLE] = bookId; + this->insertBook(__book); } - const auto __bookletPath = Syncer::saveNoteFile(OWL::BooksPath+__bookTitle+"/", booklet); + const auto __bookletPath = Syncer::saveNoteFile(OWL::BooksPath+bookId+"/", booklet); if(__bookletPath.isEmpty()) { qWarning()<< "File could not be saved. Syncer::insertBookletLocal"; return false; } booklet[FMH::MODEL_KEY::URL] = __bookletPath.toString(); booklet[FMH::MODEL_KEY::BOOK] = bookId; qDebug()<< "booklet saved to <<" << __bookletPath; auto __bookletMap = FMH::toMap(FMH::filterModel(booklet, {FMH::MODEL_KEY::ID, FMH::MODEL_KEY::BOOK, FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::URL, FMH::MODEL_KEY::MODIFIED, FMH::MODEL_KEY::ADDDATE})); if(this->db->insert(OWL::TABLEMAP[OWL::TABLE::BOOKLETS], __bookletMap)) { // for(const auto &tg : booklet[FMH::MODEL_KEY::TAG].split(",", QString::SplitBehavior::SkipEmptyParts)) // this->tag->tagAbstract(tg, OWL::TABLEMAP[OWL::TABLE::NOTES], booklet[FMH::MODEL_KEY::ID], booklet[FMH::MODEL_KEY::COLOR]); return true; } return false; } void Syncer::insertBookletRemote(const QString &bookId, FMH::MODEL &booklet) { - + qDebug()<< "trying to insert booklet remotely" << (this->provider ? "provider exists" : "failed provider") << this->provider->isValid(); + booklet[FMH::MODEL_KEY::CATEGORY] = bookId; + if(this->provider && this->provider->isValid()) + this->provider->insertBooklet(booklet); } bool Syncer::updateBookletLocal(const QString &id, const QString &bookId, const FMH::MODEL &booklet) { // for(const auto &tg : booklet[FMH::MODEL_KEY::TAG]) // this->tag->tagAbstract(tg, OWL::TABLEMAP[OWL::TABLE::NOTES], id); const QUrl __path = QFileInfo(booklet[FMH::MODEL_KEY::URL]).dir().path(); qDebug()<< "Updating local txt file as"<< __path.toLocalFile(); const auto __bookletPath = Syncer::saveNoteFile(__path.toLocalFile()+"/", booklet); if(__bookletPath.isEmpty()) { qWarning()<< "File could not be saved. Syncer::insertBookletLocal"; return false; } - return this->db->update(OWL::TABLEMAP[OWL::TABLE::BOOKLETS], FMH::toMap(FMH::filterModel(booklet, {FMH::MODEL_KEY::TITLE, FMH::MODEL_KEY::MODIFIED})), QVariantMap {{FMH::MODEL_NAME[FMH::MODEL_KEY::ID], id}, {FMH::MODEL_NAME[FMH::MODEL_KEY::BOOK], bookId}}); } -void Syncer::updateBookletRemote(const QString &id, const QString &bookId, const FMH::MODEL &booklet) +void Syncer::updateBookletRemote(const QString &id, const QString &bookId, FMH::MODEL &booklet) { - + booklet[FMH::MODEL_KEY::CATEGORY] = bookId; + if(this->provider && this->provider->isValid()) + this->provider->updateBooklet(id, booklet); } bool Syncer::removeBookletLocal(const QString &id) { return false; } void Syncer::removeBookletRemote(const QString &id) { } const FMH::MODEL_LIST Syncer::collectAllNotes() { FMH::MODEL_LIST __notes = this->db->getDBData("select * from notes"); for(auto ¬e : __notes) note[FMH::MODEL_KEY::CONTENT] = this->noteFileContent(note[FMH::MODEL_KEY::URL]); return __notes; } const FMH::MODEL_LIST Syncer::collectAllBooks() { -// return this->db->getDBData("select b.*, count(distinct bl.id) as count from books b inner join booklets bl on bl.book = b.id"); + // return this->db->getDBData("select b.*, count(distinct bl.id) as count from books b inner join booklets bl on bl.book = b.id"); return this->db->getDBData("select * from books"); } const QUrl Syncer::saveNoteFile(const QString &dir, const FMH::MODEL &data) { if(data.isEmpty() /*|| !data.contains(FMH::MODEL_KEY::CONTENT)*/) { qWarning() << "the note is empty, therefore it could not be saved into a file"; return QUrl(); } QFile file(dir+data[FMH::MODEL_KEY::ID]+QStringLiteral(".txt")); file.open(QFile::WriteOnly); file.write(data[FMH::MODEL_KEY::CONTENT].toUtf8()); file.close(); return QUrl::fromLocalFile(file.fileName()); } const QString Syncer::noteFileContent(const QUrl &path) { if(!path.isLocalFile()) { qWarning()<< "Can not open note file, the url is not a local path"; return QString(); } QFile file(path.toLocalFile()); file.open(QFile::ReadOnly); const auto content = file.readAll(); file.close(); return QString(content); } diff --git a/src/syncing/syncer.h b/src/syncing/syncer.h index 63b12ec..61723d7 100644 --- a/src/syncing/syncer.h +++ b/src/syncing/syncer.h @@ -1,296 +1,300 @@ #ifndef SYNCER_H #define SYNCER_H #include #ifdef STATIC_MAUIKIT #include "fmh.h" #else #include #endif /** * @brief The Syncer class * This interfaces between local storage and cloud * Its work is to try and keep thing synced and do the background work on updating notes * from local to cloud and viceversa. * This interface should be used to handle the whol offline and online work, * instead of manually inserting to the db or the cloud providers */ struct STATE { enum TYPE : uint { LOCAL, REMOTE }; enum STATUS : uint { OK, ERROR }; TYPE type; STATUS status; QString msg = QString(); }; class DB; class AbstractNotesProvider; class Tagging; class Syncer: public QObject { Q_OBJECT public: explicit Syncer(QObject *parent = nullptr); /** * @brief setProviderAccount * sets the credentials to the current account * for the current provider being used * @param account * the account data represented by FMH::MODEL * where the valid keys are: * FMH::MODEL_KEY::USER user name * FMH::MODEL_KEY::PASSWORD users password * FMH::MODEL_KEY::PROVIDER the url to the provider server */ void setAccount(const FMH::MODEL &account); /** * @brief setProvider * sets the provider interface * this allows to change the provider source * @param provider * the provider must inherit the asbtract class AbstractNotesProvider. * The value passed is then moved to this class private property Syncer::provider */ void setProvider(AbstractNotesProvider *provider); //// NOTES INTERFACES /// interfaces with the the notes from both, local and remote /** * @brief insertNote * saves a new note online and offline * The signal Syncer::noteInserted(FMH::MODEL, STATE) is emitted, * indicating the created note and the transaction resulting state * @param note * the note to be stored represented by FMH::MODEL */ void insertNote(FMH::MODEL ¬e); /** * @brief updateNote * Update online and offline an existing note. * The signal Syncer::noteUpdated(FMH::MODEL, STATE) is emitted, * indicating the updated note and the transaction resulting state * @param id * ID of the existing note * @param note * the new note contents represented by FMH::MODEL */ void updateNote(const QString &id, const FMH::MODEL ¬e); /** * @brief removeNote * remove a note from online and offline storage * The signal Syncer::noteRemoved(FMH::MODEL, STATE) is emitted, * indicating the removed note and the transaction resulting state * @param id * ID of the exisiting note */ void removeNote(const QString &id); /** * @brief getNote * Retrieves an existing note, whether the note is located offline or online. * When the note is ready the signal Syncer::noteReady(FMH::MODEL) is emitted * @param id * ID of the exisiting note */ void getNote(const QString &id); /** * @brief getNotes * Retrieves all the notes, online and offline notes. * When the notes are ready the signal Syncer::notesReady(FMH::MODEL_LIST) is emitted. */ void getNotes(); ////BOOKS & BOOKLET INTERFACES /// interfaces with the the books and booklets from both, local and remote /** * @brief getBooks * Retrieves all the books, online and offline. * When the books are ready the signal Syncer::booksReady(FMH::MODEL_LIST) is emitted */ void getBooks(); /** * @brief getBook * @param id */ void getBook(const QString &id); /** * @brief insertBook * @param book */ void insertBook(FMH::MODEL &book); /** * @brief updateBook * @param id * @param book */ void updateBook(const QString &id, const FMH::MODEL &book); /** * @brief removeBook * @param id */ void removeBook(const QString &id); //BOOKLETS INTERFACES /** * @brief getBooklet * @param id */ - void getBooklet(const QString &id); + void getBooklet(const QString &bookId); /** * @brief updateBooklet * @param id * @param booklet */ void updateBooklet(const QString &id, const QString &bookId, FMH::MODEL &booklet); /** * @brief insertBooklet * @param booklet */ void insertBooklet(const QString &bookId, FMH::MODEL &booklet); /** * @brief removeBooklet * @param id */ void removeBooklet(const QString &id); private: /** * @brief tag * Instance of the Maui project tag-ger. It adds tags to the abtract notes * For online tagging one could use the categories ? */ Tagging *tag; /** * @brief db * Instance to the data base storing the notes information and location, * offline and online. */ DB *db; /** * @brief server * Abstract instance to the online server to perfom CRUD actions */ AbstractNotesProvider *provider; /** * @brief syncNote * Has the job to sync a note between the offline and online versions * @param id * ID of the note to be synced */ void syncNote(const QString &id); /** * @brief stampNote * Adds an stamp id to identify the note offline and online * @param note * the note model is passed by ref and a STAMP key value is inserted */ static void addId(FMH::MODEL &model); static const QString noteIdFromStamp(DB *_db, const QString &provider, const QString &stamp) ; - static const QString noteStampFromId(DB *_db, const QString &id) ; + static const QString noteStampFromId(DB *_db, const QString &id); + + static const QString bookletIdFromStamp(DB *_db, const QString &provider, const QString &stamp) ; + static const QString bookletStampFromId(DB *_db, const QString &id) ; + void setConections(); protected: /** * @brief insertLocal * performs the insertion of a new note in the local storage * @param note * note to be inserted * @return bool * true if the note was inserted sucessfully in the local storage */ bool insertNoteLocal(FMH::MODEL ¬e); /** * @brief insertRemote * perfroms the insertion of a new note in the remote provider server * @param note * the note to be inserted */ void insertNoteRemote(FMH::MODEL ¬e); bool updateNoteLocal(const QString &id, const FMH::MODEL ¬e); void updateNoteRemote(const QString &id, const FMH::MODEL ¬e); bool removeNoteLocal(const QString &id); void removeNoteRemote(const QString &id); bool insertBookLocal(FMH::MODEL &book); void insertBookRemote(FMH::MODEL &book); bool updateBookLocal(const QString &id, const FMH::MODEL &book); void updateBookRemote(const QString &id, const FMH::MODEL &book); bool removeBookLocal(const QString &id); void removeBookRemote(const QString &id); bool insertBookletLocal(const QString &bookId, FMH::MODEL &booklet); void insertBookletRemote(const QString &bookId, FMH::MODEL &booklet); bool updateBookletLocal(const QString &id, const QString &bookId, const FMH::MODEL &booklet); - void updateBookletRemote(const QString &id, const QString &bookId, const FMH::MODEL &booklet); + void updateBookletRemote(const QString &id, const QString &bookId, FMH::MODEL &booklet); bool removeBookletLocal(const QString &id); void removeBookletRemote(const QString &id); const FMH::MODEL_LIST collectAllNotes(); const FMH::MODEL_LIST collectAllBooks(); static inline const QUrl saveNoteFile(const QString &dir, const FMH::MODEL &data); static inline const QString noteFileContent(const QUrl &path); signals: //FOR NOTES void noteInserted(FMH::MODEL note, STATE state); void noteUpdated(FMH::MODEL note, STATE state); void noteRemoved(FMH::MODEL note, STATE state); void noteReady(FMH::MODEL note); void notesReady(FMH::MODEL_LIST notes); //FOR BOOKS void bookInserted(FMH::MODEL book, STATE state); void bookUpdated(FMH::MODEL book, STATE state); void bookRemoved(FMH::MODEL book, STATE state); void bookReady(FMH::MODEL book); void booksReady(FMH::MODEL_LIST books); //FOR BOOKLETS void bookletInserted(FMH::MODEL booklet, STATE state); void bookletUpdated(FMH::MODEL booklet, STATE state); void bookletRemoved(FMH::MODEL booklet, STATE state); void bookletReady(FMH::MODEL_LIST booklets); public slots: }; #endif // SYNCER_H diff --git a/src/utils/owl.h b/src/utils/owl.h index df54e5b..4068799 100644 --- a/src/utils/owl.h +++ b/src/utils/owl.h @@ -1,143 +1,145 @@ #ifndef OWL_H #define OWL_H #include #include #include #include #include #include #include #include #include #include #include namespace OWL { Q_NAMESPACE enum class TABLE : uint8_t { NOTES, NOTES_SYNC, BOOKS, BOOKLETS, + BOOKLETS_SYNC, LINKS, NONE }; static const QMap TABLEMAP = { {TABLE::NOTES,"notes"}, {TABLE::NOTES_SYNC,"notes_sync"}, {TABLE::BOOKS,"books"}, {TABLE::BOOKLETS,"booklets"}, + {TABLE::BOOKLETS_SYNC,"booklets_sync"}, {TABLE::LINKS,"links"}, }; // enum KEY : uint8_t // { // URL, // UPDATED, // ID, // TITLE, // BODY, // FAV, // COLOR, // ADD_DATE, // TAG, // PREVIEW, // IMAGE, // LINK, // PIN, // NONE // }; Q_ENUM_NS(KEY); // typedef QHash DB; // typedef QList DB_LIST; // static const DB KEYMAP = // { // {KEY::ID, "id"}, // {KEY::BODY, "body"}, // {KEY::UPDATED, "updated"}, // {KEY::TITLE, "title"}, // {KEY::URL, "url"}, // {KEY::FAV, "fav"}, // {KEY::PIN, "pin"}, // {KEY::COLOR, "color"}, // {KEY::ADD_DATE, "addDate"}, // {KEY::TAG, "tag"}, // {KEY::PREVIEW, "preview"}, // {KEY::IMAGE, "image"}, // {KEY::LINK, "link"} // }; // static const QHash MAPKEY = // { // {KEYMAP[KEY::ID], KEY::ID}, // {KEYMAP[KEY::BODY], KEY::BODY}, // {KEYMAP[KEY::UPDATED], KEY::UPDATED}, // {KEYMAP[KEY::TITLE], KEY::TITLE}, // {KEYMAP[KEY::URL], KEY::URL}, // {KEYMAP[KEY::FAV], KEY::FAV}, // {KEYMAP[KEY::PIN], KEY::PIN}, // {KEYMAP[KEY::COLOR], KEY::COLOR}, // {KEYMAP[KEY::ADD_DATE], KEY::ADD_DATE}, // {KEYMAP[KEY::TAG], KEY::TAG}, // {KEYMAP[KEY::PREVIEW], KEY::PREVIEW}, // {KEYMAP[KEY::IMAGE], KEY::IMAGE}, // {KEYMAP[KEY::LINK], KEY::LINK} // }; const QString CollectionDBPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+"/buho/"; const QString NotesPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+"/buho/notes/"; const QString BooksPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+"/buho/books/"; const QString LinksPath = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)+"/buho/links/"; const QString App = "Buho"; const QString version = "1.0"; const QString comment = "Notes taking and link collector manager"; const QString DBName = "collection.db"; inline void saveJson(QJsonDocument document, QString fileName) { QFile jsonFile(fileName); jsonFile.open(QFile::WriteOnly); jsonFile.write(document.toJson()); jsonFile.close(); } inline QVariantMap openJson(const QString &url) { QString val; QFile file; file.setFileName(url); file.open(QIODevice::ReadOnly | QIODevice::Text); val = file.readAll(); file.close(); QJsonDocument d = QJsonDocument::fromJson(val.toUtf8()); QJsonObject obj = d.object(); return obj.toVariantMap(); } inline QString saveImage(QByteArray array, const QString &path) { if(!array.isNull()&&!array.isEmpty()) { QImage img; img.loadFromData(array); QString name = path; name.replace("/", "-"); name.replace("&", "-"); QString format = "JPEG"; if (img.save(path+".jpg", format.toLatin1(), 100)) return path+".jpg"; else qDebug() << "couldn't save artwork"; }else qDebug()<<"array is empty"; return QString(); } } #endif // OWL_H diff --git a/src/views/notes/NotesView.qml b/src/views/notes/NotesView.qml index 4b7729f..6fb8211 100644 --- a/src/views/notes/NotesView.qml +++ b/src/views/notes/NotesView.qml @@ -1,242 +1,241 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.3 import org.kde.mauikit 1.0 as Maui import org.kde.kirigami 2.7 as Kirigami import Notes 1.0 import "../../widgets" Maui.Page { property var currentNote : ({}) property alias cardsView : cardsView property alias model : notesModel property alias list : notesList property alias currentIndex : cardsView.currentIndex signal noteClicked(var note) padding: space.big headBar.drawBorder: false headBar.visible: !cardsView.holder.visible title : cardsView.count + " notes" headBar.leftContent: [ ToolButton { icon.name: cardsView.gridView ? "view-list-details" : "view-list-icons" onClicked: { cardsView.gridView = !cardsView.gridView } } ] headBar.rightContent: [ ToolButton { icon.name: "view-sort" onClicked: sortMenu.open(); Menu { id: sortMenu parent: parent MenuItem { text: qsTr("Ascedent") checkable: false checked: notesList.order === Notes.ASC onTriggered: notesList.order = Notes.ASC } MenuItem { text: qsTr("Descendent") checkable: false checked: notesList.order === Notes.DESC onTriggered: notesList.order = Notes.DESC } MenuSeparator{} MenuItem { text: qsTr("Title") checkable: false checked: notesList.sortBy === Notes.TITLE onTriggered: notesList.sortBy = Notes.TITLE } MenuItem { text: qsTr("Color") checkable: true checked: notesList.sortBy === Notes.COLOR onTriggered: notesList.sortBy = Notes.COLOR } MenuItem { text: qsTr("Add date") checkable: false checked: notesList.sortBy === Notes.ADDDATE onTriggered: notesList.sortBy = Notes.ADDDATE } MenuItem { text: qsTr("Updated") checkable: false checked: notesList.sortBy === Notes.Modified onTriggered: notesList.sortBy = Notes.Modified } MenuItem { text: qsTr("Fav") checkable: false checked: notesList.sortBy === Notes.FAVORITE onTriggered: notesList.sortBy = Notes.FAVORITE } } }, ToolButton { id: pinButton icon.name: "pin" checkable: true icon.color: checked ? Kirigami.Theme.highlightColor : Kirigami.Theme.textColor } ] Notes { id: notesList - account: root.currentAccount } Maui.BaseModel { id: notesModel list: notesList } ColumnLayout { anchors.fill: parent spacing: 0 Rectangle { visible: pinButton.checked Layout.alignment: Qt.AlignVCenter Layout.fillWidth: true height: cardsView.itemHeight CardsList { id: pinnedList height: parent.height *0.9 width: parent.width * 0.9 anchors.centerIn: parent itemHeight: cardsView.itemHeight * 0.9 itemWidth: itemHeight * 1.5 onItemClicked: noteClicked(cardsView.model.get(index)) } color: Kirigami.Theme.backgroundColor radius: radiusV border.color: Qt.darker(Kirigami.Theme.backgroundColor, 1.4) } CardsView { id: cardsView Layout.fillHeight: true Layout.fillWidth: true width: parent.width holder.emoji: "qrc:/Type.png" holder.emojiSize: iconSizes.huge holder.isMask: false holder.title : "No notes!" holder.body: "Click here to create a new note" model: notesModel delegate: CardDelegate { id: delegate cardWidth: Math.min(cardsView.cellWidth, cardsView.itemWidth) - Kirigami.Units.largeSpacing * 2 cardHeight: cardsView.itemHeight anchors.left: parent.left anchors.leftMargin: cardsView.width <= cardsView.itemWidth ? 0 : (index % 2 === 0 ? Math.max(0, cardsView.cellWidth - cardsView.itemWidth) : cardsView.cellWidth) onClicked: { currentIndex = index currentNote = notesList.get(index) noteClicked(currentNote) } onRightClicked: { currentIndex = index currentNote = notesList.get(index) cardsView.menu.popup() } onPressAndHold: { currentIndex = index currentNote = notesList.get(index) cardsView.menu.popup() } } Connections { target: cardsView.holder onActionTriggered: newNote() } Connections { target: cardsView.menu onOpened: { cardsView.menu.isFav = currentNote.fav == 1 cardsView.menu.isPin = currentNote.pin == 1 } onDeleteClicked: notesList.remove(cardsView.currentIndex) onColorClicked: { notesList.update(({"color": color}), cardsView.currentIndex) } onFavClicked: { notesList.update(({"fav": fav}), cardsView.currentIndex) } onPinClicked: { notesList.update(({"pin": pin}), cardsView.currentIndex) } onCopyClicked: { Maui.Handy.copyToClipboard(currentNote.title+"\n"+currentNote.body) } } } } } diff --git a/src/widgets/ColorsBar.qml b/src/widgets/ColorsBar.qml index a0ede50..c382fcf 100644 --- a/src/widgets/ColorsBar.qml +++ b/src/widgets/ColorsBar.qml @@ -1,132 +1,134 @@ import QtQuick 2.0 import QtQuick.Controls 2.2 import org.kde.kirigami 2.7 as Kirigami Row { signal colorPicked(color color) anchors.verticalCenter: parent.verticalCenter spacing: space.medium property string currentColor property int size : iconSizes.medium Rectangle { color:"#ffded4" anchors.verticalCenter: parent.verticalCenter height: size width: height radius: radiusV border.color: Qt.darker(color, 1.7) MouseArea { anchors.fill: parent onClicked: { currentColor = parent.color colorPicked(currentColor) } } } Rectangle { color:"#d3ffda" anchors.verticalCenter: parent.verticalCenter height: size width: height radius: radiusV border.color: Qt.darker(color, 1.7) MouseArea { anchors.fill: parent onClicked: { currentColor = parent.color colorPicked(currentColor) } } } Rectangle { color:"#caf3ff" anchors.verticalCenter: parent.verticalCenter height: size width: height radius: radiusV border.color: Qt.darker(color, 1.7) MouseArea { anchors.fill: parent onClicked: { currentColor = parent.color colorPicked(currentColor) } } } Rectangle { color:"#dbd8ff" anchors.verticalCenter: parent.verticalCenter height: size width: height radius: radiusV border.color: Qt.darker(color, 1.7) MouseArea { anchors.fill: parent onClicked: { currentColor = parent.color colorPicked(currentColor) } } } Rectangle { color:"#ffcdf4" anchors.verticalCenter: parent.verticalCenter height: size width: height radius: radiusV border.color: Qt.darker(color, 1.7) MouseArea { anchors.fill: parent onClicked: { currentColor = parent.color colorPicked(currentColor) } } } Rectangle { + Kirigami.Theme.inherit: false + Kirigami.Theme.colorSet: Kirigami.Theme.View color: Kirigami.Theme.backgroundColor anchors.verticalCenter: parent.verticalCenter height: size width: height radius: radiusV border.color: Qt.darker(color, 1.7) MouseArea { anchors.fill: parent onClicked: { currentColor = parent.color colorPicked(currentColor) } } } }