~aleteoryx/muditaos

c8c4f82080c1fd95c54c4fc28d64b9eed2255f92 — Piotr Tanski 5 years ago 9fdb298
[EGD-4487] Application notes implemented. (#1094)

* [EGD-4487] Implementation of notes application.
56 files changed, 2094 insertions(+), 680 deletions(-)

M changelog.md
M image/assets/lang/English.json
M image/user/db/notes_001.sql
M image/user/db/notes_002.sql
A module-apps/BasePresenter.hpp
M module-apps/application-notes/ApplicationNotes.cpp
M module-apps/application-notes/ApplicationNotes.hpp
M module-apps/application-notes/CMakeLists.txt
D module-apps/application-notes/NotesModel.cpp
D module-apps/application-notes/NotesModel.hpp
A module-apps/application-notes/data/NoteSwitchData.cpp
A module-apps/application-notes/data/NoteSwitchData.hpp
A module-apps/application-notes/model/NotesListModel.cpp
A module-apps/application-notes/model/NotesListModel.hpp
A module-apps/application-notes/model/NotesRepository.cpp
A module-apps/application-notes/model/NotesRepository.hpp
A module-apps/application-notes/presenter/NoteEditWindowPresenter.cpp
A module-apps/application-notes/presenter/NoteEditWindowPresenter.hpp
A module-apps/application-notes/presenter/NotePreviewWindowPresenter.cpp
A module-apps/application-notes/presenter/NotePreviewWindowPresenter.hpp
A module-apps/application-notes/presenter/NotesMainWindowPresenter.cpp
A module-apps/application-notes/presenter/NotesMainWindowPresenter.hpp
A module-apps/application-notes/style/NoteEditStyle.hpp
A module-apps/application-notes/style/NotePreviewStyle.hpp
A module-apps/application-notes/style/NotesListStyle.hpp
A module-apps/application-notes/style/NotesMainWindowStyle.hpp
M module-apps/application-notes/widgets/NotesItem.cpp
M module-apps/application-notes/widgets/NotesItem.hpp
A module-apps/application-notes/windows/NoteEditWindow.cpp
A module-apps/application-notes/windows/NoteEditWindow.hpp
A module-apps/application-notes/windows/NoteMainWindow.cpp
A module-apps/application-notes/windows/NoteMainWindow.hpp
A module-apps/application-notes/windows/NotePreviewWindow.cpp
A module-apps/application-notes/windows/NotePreviewWindow.hpp
D module-apps/application-notes/windows/NotesEditWindow.cpp
D module-apps/application-notes/windows/NotesEditWindow.hpp
D module-apps/application-notes/windows/NotesMainWindow.cpp
D module-apps/application-notes/windows/NotesMainWindow.hpp
A module-apps/application-notes/windows/NotesOptions.cpp
A module-apps/application-notes/windows/NotesOptions.hpp
M module-db/CMakeLists.txt
M module-db/Interface/NotesRecord.cpp
M module-db/Interface/NotesRecord.hpp
M module-db/Interface/Record.hpp
M module-db/Tables/NotesTable.cpp
M module-db/Tables/NotesTable.hpp
M module-db/Tables/Table.hpp
A module-db/queries/notes/QueryNoteRemove.cpp
A module-db/queries/notes/QueryNoteRemove.hpp
A module-db/queries/notes/QueryNoteStore.cpp
A module-db/queries/notes/QueryNoteStore.hpp
A module-db/queries/notes/QueryNotesGet.cpp
A module-db/queries/notes/QueryNotesGet.hpp
M module-db/tests/CMakeLists.txt
A module-db/tests/NotesRecord_tests.cpp
A module-db/tests/NotesTable_tests.cpp
M changelog.md => changelog.md +1 -0
@@ 3,6 3,7 @@
[Current release]

### Added
* `[notes]` Notes application implemented.

### Changed


M image/assets/lang/English.json => image/assets/lang/English.json +7 -3
@@ 122,10 122,14 @@
  "app_options_contact_edit": "Edit Contact",

  "app_notes_title_main": "Notes",
  "app_notes_new_note": "New Note",
  "app_notes_save": "SAVE",
  "app_notes_edit_new_note": "Edit/New Note",
  "app_notes_copy_text": "Copy text",
  "app_notes_copy_paste": "Paste text",
  "app_notes_edit": "EDIT",
  "app_notes_back": "BACK",
  "app_notes_edited": "Edited",
  "app_notes_delete_note": "Delete",
  "app_notes_note_delete_confirmation": "Do you really want to delete this note?",
  "app_notes_no_notes": "There are no notes yet.\nPress Left arrow to add new.",

  "app_calllog_title_main": "Calls",
  "app_calllog_new_note": "New Note",

M image/user/db/notes_001.sql => image/user/db/notes_001.sql +1 -2
@@ 1,6 1,5 @@
CREATE TABLE IF NOT EXISTS notes(
    _id INTEGER PRIMARY KEY,
    date INTEGER,
    snippet TEXT DEFAULT '',
    path TEXT DEFAULT ''
    snippet TEXT DEFAULT ''
);

M image/user/db/notes_002.sql => image/user/db/notes_002.sql +26 -26
@@ 1,36 1,36 @@
BEGIN;
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (1,1565586181,'snippet 1','/data/applications/notes/1565586181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (2,1565586381,'snippet 2
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (1,1565586181,'snippet 1');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (2,1565586381,'snippet 2
222
222
222','/data/applications/notes/1565586381.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (3,1565588181,'snippet 3
333333333333333333333333333333333333333333333333333','/data/applications/notes/1565588181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (4,1565596181,'snippet 4 4444444444444444444444444444444444444444444444444444444444444444444444444444','/data/applications/notes/1565596181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (5,1565606181,'snippet 5 555555
222');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (3,1565588181,'snippet 3
333333333333333333333333333333333333333333333333333');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (4,1565596181,'snippet 4 4444444444444444444444444444444444444444444444444444444444444444444444444444');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (5,1565606181,'snippet 5 555555
555555555
55555','/data/applications/notes/1565606181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (6,1565616181,'snippet 66666 66666 66666 66666 66666','/data/applications/notes/1565616181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (7,1565626181,'snippet 7
','/data/applications/notes/1565626181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (8,1565646181,'snippet 8
55555');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (6,1565616181,'snippet 66666 66666 66666 66666 66666');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (7,1565626181,'snippet 7
');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (8,1565646181,'snippet 8

8','/data/applications/notes/1565646181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (9,1565666181,'snippet 9
8');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (9,1565666181,'snippet 9

9','/data/applications/notes/1565666181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (10,1565686181,'snippet 10
9');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (10,1565686181,'snippet 10
10

10','/data/applications/notes/1565686181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (11,1565706181,'snippet 11','/data/applications/notes/1565706181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (12,1565726181,'snippet 12','/data/applications/notes/1565726181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (13,1565746181,'snippet 13','/data/applications/notes/1565746181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (14,1565766181,'snippet 14','/data/applications/notes/1565766181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (15,1565786181,'snippet 15','/data/applications/notes/1565786181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (16,1565806181,'snippet 16
','/data/applications/notes/1565806181.txt');
INSERT OR IGNORE INTO notes ("_id","date","snippet","path") VALUES (17,1565826181,'snippet 17
10');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (11,1565706181,'snippet 11');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (12,1565726181,'snippet 12');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (13,1565746181,'snippet 13');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (14,1565766181,'snippet 14');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (15,1565786181,'snippet 15');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (16,1565806181,'snippet 16
');
INSERT OR IGNORE INTO notes ("_id","date","snippet") VALUES (17,1565826181,'snippet 17
17
17','/data/applications/notes/1565826181.txt');
17');
COMMIT;

A module-apps/BasePresenter.hpp => module-apps/BasePresenter.hpp +29 -0
@@ 0,0 1,29 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <cassert>

namespace app
{
    template <typename VIEW> class BasePresenter
    {
      public:
        virtual ~BasePresenter() noexcept = default;

        void attach(VIEW *oth) noexcept
        {
            view = oth;
        }

        VIEW *getView() const noexcept
        {
            assert(view != nullptr);
            return view;
        }

      private:
        VIEW *view = nullptr;
    };
} // namespace app

M module-apps/application-notes/ApplicationNotes.cpp => module-apps/application-notes/ApplicationNotes.cpp +59 -36
@@ 1,67 1,70 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "ApplicationNotes.hpp"

#include "MessageType.hpp"
#include "windows/NotesMainWindow.hpp"
#include "windows/NotesEditWindow.hpp"
#include "windows/NoteMainWindow.hpp"
#include "windows/NotePreviewWindow.hpp"
#include "windows/NoteEditWindow.hpp"

#include <service-db/DBMessage.hpp>
#include <service-db/QueryMessage.hpp>

#include "ApplicationNotes.hpp"
#include <module-apps/application-notes/presenter/NotesMainWindowPresenter.hpp>
#include <module-apps/application-notes/presenter/NotePreviewWindowPresenter.hpp>
#include <module-apps/application-notes/presenter/NoteEditWindowPresenter.hpp>
#include <module-apps/windows/OptionWindow.hpp>
#include <module-apps/windows/Dialog.hpp>

namespace app
{
    namespace
    {
        constexpr auto NotesStackSize = 4096U;
    } // namespace

    ApplicationNotes::ApplicationNotes(std::string name, std::string parent, StartInBackground startInBackground)
        : Application(name, parent, startInBackground, 4096)
        : Application(name, parent, startInBackground, NotesStackSize)
    {}

    ApplicationNotes::~ApplicationNotes()
    {
        LOG_INFO("deleting");
    }

    // Invoked upon receiving data message
    sys::MessagePointer ApplicationNotes::DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp)
    {

        auto retMsg = Application::DataReceivedHandler(msgl);
        // if message was handled by application's template there is no need to process further.
        if ((reinterpret_cast<sys::ResponseMessage *>(retMsg.get())->retCode == sys::ReturnCodes::Success)) {
        if (static_cast<sys::ResponseMessage *>(retMsg.get())->retCode == sys::ReturnCodes::Success) {
            return retMsg;
        }

        bool handled = false;

        // handle database response
        if (resp != nullptr) {
            handled = true;
            switch (resp->responseTo) {
            case MessageType::DBNotesGetLimitOffset: {
                if (getCurrentWindow()->onDatabaseMessage(resp))
                    refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
            } break;
            case MessageType::DBQuery:
                if (auto queryResponse = dynamic_cast<db::QueryResponse *>(resp); queryResponse != nullptr) {
                    if (auto result = queryResponse->getResult(); result && result->hasListener()) {
                        if (result->handle()) {
                            refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
                        }
                    }
                }
                break;
            default:
                break;
            }
            return msgHandled();
        }

        if (handled)
            return std::make_shared<sys::ResponseMessage>();
        else
            return std::make_shared<sys::ResponseMessage>(sys::ReturnCodes::Unresolved);
        return msgNotHandled();
    }

    // Invoked during initialization
    sys::ReturnCodes ApplicationNotes::InitHandler()
    {

        auto ret = Application::InitHandler();
        if (ret != sys::ReturnCodes::Success)
        const auto ret = Application::InitHandler();
        if (ret != sys::ReturnCodes::Success) {
            return ret;
        }

        createUserInterface();

        setActiveWindow(gui::name::window::main_window);

        return ret;
    }



@@ 70,17 73,37 @@ namespace app
        return sys::ReturnCodes::Success;
    }

    sys::ReturnCodes ApplicationNotes::SwitchPowerModeHandler(const sys::ServicePowerMode mode)
    {
        return sys::ReturnCodes::Success;
    }

    void ApplicationNotes::createUserInterface()
    {
        windowsFactory.attach(gui::name::window::main_window, [](Application *app, const std::string &name) {
            return std::make_unique<gui::NotesMainWindow>(app);
        windowsFactory.attach(gui::name::window::main_window, [this](Application *app, const std::string &name) {
            auto notesRepository = std::make_unique<notes::NotesDBRepository>(this);
            auto notesProvider   = std::make_shared<notes::NotesListModel>(this, std::move(notesRepository));
            auto presenter       = std::make_unique<notes::NotesMainWindowPresenter>(notesProvider);
            return std::make_unique<notes::NoteMainWindow>(app, std::move(presenter));
        });
        windowsFactory.attach(gui::name::window::main_window, [](Application *app, const std::string &name) {
            return std::make_unique<gui::NotesEditWindow>(app);
        windowsFactory.attach(gui::name::window::note_preview, [this](Application *app, const std::string &name) {
            auto notesRepository = std::make_unique<notes::NotesDBRepository>(this);
            auto presenter       = std::make_unique<notes::NotePreviewWindowPresenter>(std::move(notesRepository));
            return std::make_unique<notes::NotePreviewWindow>(app, std::move(presenter));
        });
        windowsFactory.attach(gui::name::window::note_edit, [this](Application *app, const std::string &name) {
            auto notesRepository = std::make_unique<notes::NotesDBRepository>(this);
            auto presenter       = std::make_unique<notes::NoteEditWindowPresenter>(std::move(notesRepository));
            return std::make_unique<notes::NoteEditWindow>(app, std::move(presenter));
        });
        windowsFactory.attach(gui::name::window::note_confirm_dialog, [](Application *app, const std::string &name) {
            return std::make_unique<gui::DialogYesNo>(app, name);
        });
        windowsFactory.attach(
            utils::localize.get("app_phonebook_options_title"),
            [](Application *app, const std::string &name) { return std::make_unique<gui::OptionWindow>(app, name); });
    }

    void ApplicationNotes::destroyUserInterface()
    {}

} /* namespace app */
} // namespace app

M module-apps/application-notes/ApplicationNotes.hpp => module-apps/application-notes/ApplicationNotes.hpp +15 -15
@@ 1,12 1,18 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#ifndef MODULE_APPS_APPLICATION_NOTES_APPLICATIONNOTES_HPP_
#define MODULE_APPS_APPLICATION_NOTES_APPLICATIONNOTES_HPP_
#pragma once

#include "NotesModel.hpp"
#include "module-apps/application-notes/model/NotesListModel.hpp"
#include "Application.hpp"

namespace gui::name::window
{
    inline constexpr auto note_preview        = "NotePreview";
    inline constexpr auto note_edit           = "NoteEdit";
    inline constexpr auto note_confirm_dialog = "ConfirmDialog";
} // namespace gui::name::window

namespace app
{
    inline constexpr auto name_notes = "ApplicationNotes";


@@ 14,18 20,14 @@ namespace app
    class ApplicationNotes : public Application
    {
      public:
        ApplicationNotes(std::string name                    = name_notes,
                         std::string parent                  = {},
                         StartInBackground startInBackground = {false});
        virtual ~ApplicationNotes();
        explicit ApplicationNotes(std::string name                    = name_notes,
                                  std::string parent                  = {},
                                  StartInBackground startInBackground = {false});

        sys::MessagePointer DataReceivedHandler(sys::DataMessage *msgl, sys::ResponseMessage *resp) override;
        sys::ReturnCodes InitHandler() override;
        sys::ReturnCodes DeinitHandler() override;

        sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override final
        {
            return sys::ReturnCodes::Success;
        }
        sys::ReturnCodes SwitchPowerModeHandler(const sys::ServicePowerMode mode) override;

        void createUserInterface() override;
        void destroyUserInterface() override;


@@ 38,6 40,4 @@ namespace app
            return {{manager::actions::Launch}};
        }
    };
} /* namespace app */

#endif /* MODULE_APPS_APPLICATION_NOTES_APPLICATIONNOTES_HPP_ */
} // namespace app

M module-apps/application-notes/CMakeLists.txt => module-apps/application-notes/CMakeLists.txt +24 -13
@@ 1,12 1,9 @@

include_directories( ${CMAKE_PROJECT_NAME}

include_directories( ${CMAKE_PROJECT_NAME}
	PUBLIC
		"${CMAKE_CURRENT_LIST_DIR}"
)

include_directories( ${PROJECT_NAME}

	PUBLIC
		"${CMAKE_CURRENT_LIST_DIR}"
)


@@ 15,19 12,33 @@ target_sources( ${PROJECT_NAME}

	PRIVATE
		"${CMAKE_CURRENT_LIST_DIR}/ApplicationNotes.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/NotesModel.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/model/NotesListModel.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/model/NotesRepository.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/presenter/NotesMainWindowPresenter.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/presenter/NotePreviewWindowPresenter.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/presenter/NoteEditWindowPresenter.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/NotesItem.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NotesMainWindow.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NotesEditWindow.cpp"
		
		"${CMAKE_CURRENT_LIST_DIR}/windows/NoteMainWindow.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NotePreviewWindow.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NoteEditWindow.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NotesOptions.cpp"
		"${CMAKE_CURRENT_LIST_DIR}/data/NoteSwitchData.cpp"
	PUBLIC
		"${CMAKE_CURRENT_LIST_DIR}/ApplicationNotes.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/NotesModel.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NotesMainWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NotesEditWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/model/NotesListModel.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/model/NotesRepository.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/presenter/NotesMainWindowPresenter.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/presenter/NotePreviewWindowPresenter.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/presenter/NoteEditWindowPresenter.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/widgets/NotesItem.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NoteMainWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NotePreviewWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NoteEditWindow.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/windows/NotesOptions.hpp"
		"${CMAKE_CURRENT_LIST_DIR}/data/NoteSwitchData.hpp"
)
target_include_directories( ${PROJECT_NAME}
target_include_directories(${PROJECT_NAME}
    PRIVATE
        service-db
    )
)


D module-apps/application-notes/NotesModel.cpp => module-apps/application-notes/NotesModel.cpp +0 -57
@@ 1,57 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <service-db/DBServiceAPI.hpp>
#include "widgets/NotesItem.hpp"

#include "NotesModel.hpp"
#include "ListView.hpp"

#include <module-utils/Utils.hpp>
#include <module-services/service-db/agents/settings/SystemSettings.hpp>

NotesModel::NotesModel(app::Application *app) : DatabaseModel(app)
{}

unsigned int NotesModel::requestRecordsCount()
{
    recordsCount = DBServiceAPI::NotesGetCount(application);
    return recordsCount;
}

void NotesModel::requestRecords(const uint32_t offset, const uint32_t limit)
{
    DBServiceAPI::NotesGetLimitOffset(application, offset, limit);
}

bool NotesModel::updateRecords(std::vector<NotesRecord> records)
{
#if DEBUG_DB_MODEL_DATA == 1
    LOG_DEBUG("Offset: %" PRIu32 ", Limit: %" PRIu32 " Count: %" PRIu32 "", offset, limit, count);
    for (uint32_t i = 0; i < records.get()->size(); ++i) {
        LOG_DEBUG("id: %" PRIu32 ", filename: %s",
                  records.get()->operator[](i).ID,
                  records.get()->operator[](i).path.c_str());
    }
#endif

    DatabaseModel::updateRecords(std::move(records));
    list->onProviderDataUpdate();

    return true;
}

unsigned int NotesModel::getMinimalItemHeight() const
{
    return 146;
}

gui::ListItem *NotesModel::getItem(gui::Order order)
{
    std::shared_ptr<NotesRecord> note = getRecord(order);

    auto *item = new gui::NotesItem(this, !(application->isTimeFormat12()));

    item->setNote(note);
    return item;
}

D module-apps/application-notes/NotesModel.hpp => module-apps/application-notes/NotesModel.hpp +0 -33
@@ 1,33 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#ifndef MODULE_APPS_APPLICATION_NOTES_NOTESMODEL_HPP_
#define MODULE_APPS_APPLICATION_NOTES_NOTESMODEL_HPP_

#include <vector>

#include "Interface/NotesRecord.hpp"
#include "DatabaseModel.hpp"
#include "NotesRecord.hpp"
#include "Application.hpp"
#include "ListItemProvider.hpp"

/*
 *
 */
class NotesModel : public app::DatabaseModel<NotesRecord>, public gui::ListItemProvider
{
  public:
    NotesModel(app::Application *app);

    // virtual methods
    bool updateRecords(std::vector<NotesRecord> records) override;
    void requestRecords(const uint32_t offset, const uint32_t limit) override;

    // virtual methods for ListViewProvider
    unsigned int getMinimalItemHeight() const override;
    gui::ListItem *getItem(gui::Order order) override;
    [[nodiscard]] unsigned int requestRecordsCount() override;
};

#endif /* MODULE_APPS_APPLICATION_NOTES_NOTESMODEL_HPP_ */

A module-apps/application-notes/data/NoteSwitchData.cpp => module-apps/application-notes/data/NoteSwitchData.cpp +16 -0
@@ 0,0 1,16 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NoteSwitchData.hpp"

namespace app::notes
{
    NoteSwitchData::NoteSwitchData(NotesRecord record)
        : gui::SwitchData(std::string{"NotePreview"}), record{std::move(record)}
    {}

    const NotesRecord &NoteSwitchData::getRecord() const noexcept
    {
        return record;
    }
} // namespace app::notes

A module-apps/application-notes/data/NoteSwitchData.hpp => module-apps/application-notes/data/NoteSwitchData.hpp +22 -0
@@ 0,0 1,22 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-gui/gui/SwitchData.hpp>

#include <module-db/Interface/NotesRecord.hpp>

namespace app::notes
{
    class NoteSwitchData : public gui::SwitchData
    {
      public:
        explicit NoteSwitchData(NotesRecord record);

        const NotesRecord &getRecord() const noexcept;

      private:
        NotesRecord record;
    };
} // namespace app::notes

A module-apps/application-notes/model/NotesListModel.cpp => module-apps/application-notes/model/NotesListModel.cpp +81 -0
@@ 0,0 1,81 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotesListModel.hpp"

#include "module-apps/application-notes/widgets/NotesItem.hpp"
#include "module-apps/application-notes/style/NotesListStyle.hpp"
#include "module-apps/application-notes/windows/NotesOptions.hpp"
#include "module-apps/application-notes/data/NoteSwitchData.hpp"
#include <module-apps/messages/OptionsWindow.hpp>

#include <module-gui/gui/widgets/ListView.hpp>
#include <module-gui/gui/input/InputEvent.hpp>

namespace app::notes
{
    NotesListItemProvider::NotesListItemProvider(Application *app) : DatabaseModel(app)
    {}

    NotesListModel::NotesListModel(app::Application *app, std::shared_ptr<AbstractNotesRepository> notesRepository)
        : NotesListItemProvider(app), notesRepository{std::move(notesRepository)}
    {}

    unsigned int NotesListModel::requestRecordsCount()
    {
        return recordsCount;
    }

    bool NotesListModel::updateRecords(std::vector<NotesRecord> records)
    {
        DatabaseModel::updateRecords(std::move(records));
        list->onProviderDataUpdate();
        return true;
    }

    unsigned int NotesListModel::getMinimalItemHeight() const
    {
        return style::list::item::Height;
    }

    gui::ListItem *NotesListModel::getItem(gui::Order order)
    {
        std::shared_ptr<NotesRecord> note = getRecord(order);
        if (!note) {
            return nullptr;
        }

        auto item               = new gui::NotesItem(note);
        item->activatedCallback = [this, note = note.get()](gui::Item &) {
            application->switchWindow(gui::name::window::note_preview, std::make_unique<NoteSwitchData>(*note));
            return true;
        };
        item->inputCallback = [this, note = note.get()](gui::Item &, const gui::InputEvent &event) {
            if (event.isShortPress() && event.is(gui::KeyCode::KEY_LF)) {
                application->switchWindow(
                    utils::localize.get("app_phonebook_options_title"),
                    std::make_unique<gui::OptionsWindowOptions>(noteListOptions(application, *note, *notesRepository)));
            }
            return false;
        };
        return item;
    }

    void NotesListModel::requestRecords(uint32_t offset, uint32_t limit)
    {
        notesRepository->get(
            offset, limit, [this](const std::vector<NotesRecord> &records, unsigned int notesRepoCount) {
                return onNotesRetrieved(records, notesRepoCount);
            });
    }

    bool NotesListModel::onNotesRetrieved(const std::vector<NotesRecord> &records, unsigned int notesRepoCount)
    {
        if (recordsCount != notesRepoCount) {
            recordsCount = notesRepoCount;
            list->rebuildList(::style::listview::RebuildType::Full, 0, true);
            return false;
        }
        return updateRecords(records);
    }
} // namespace app::notes

A module-apps/application-notes/model/NotesListModel.hpp => module-apps/application-notes/model/NotesListModel.hpp +43 -0
@@ 0,0 1,43 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <cstdint>
#include <vector>

#include <module-apps/Application.hpp>
#include <module-apps/DatabaseModel.hpp>

#include <module-db/Interface/NotesRecord.hpp>

#include <module-gui/gui/widgets/ListItemProvider.hpp>

#include "NotesRepository.hpp"

namespace app::notes
{
    class NotesListItemProvider : public app::DatabaseModel<NotesRecord>, public gui::ListItemProvider
    {
      public:
        explicit NotesListItemProvider(app::Application *app);
    };

    class NotesListModel : public NotesListItemProvider
    {
      public:
        NotesListModel(app::Application *app, std::shared_ptr<AbstractNotesRepository> notesRepository);

        [[nodiscard]] unsigned int requestRecordsCount() override;
        [[nodiscard]] bool updateRecords(std::vector<NotesRecord> records) override;
        void requestRecords(std::uint32_t offset, std::uint32_t limit) override;

        [[nodiscard]] gui::ListItem *getItem(gui::Order order) override;
        [[nodiscard]] unsigned int getMinimalItemHeight() const override;

      private:
        bool onNotesRetrieved(const std::vector<NotesRecord> &records, unsigned int notesRepoCount);

        std::shared_ptr<AbstractNotesRepository> notesRepository;
    };
} // namespace app::notes

A module-apps/application-notes/model/NotesRepository.cpp => module-apps/application-notes/model/NotesRepository.cpp +64 -0
@@ 0,0 1,64 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotesRepository.hpp"

#include <module-db/queries/notes/QueryNotesGet.hpp>
#include <module-db/queries/notes/QueryNoteStore.hpp>
#include <module-db/queries/notes/QueryNoteRemove.hpp>

#include <service-db/DBServiceAPI.hpp>

namespace app::notes
{
    NotesDBRepository::NotesDBRepository(Application *application) : application{application}
    {}

    void NotesDBRepository::get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback)
    {
        auto query = std::make_unique<db::query::QueryNotesGet>(offset, limit);
        query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
            auto result = dynamic_cast<db::query::NotesGetResult *>(response);
            if (result == nullptr) {
                return false;
            }
            if (callback) {
                callback(result->getRecords(), result->getCount());
            }
            return true;
        }));
        DBServiceAPI::GetQuery(application, db::Interface::Name::Notes, std::move(query));
    }

    void NotesDBRepository::save(const NotesRecord &note, const OnResultCallback &callback)
    {
        auto query = std::make_unique<db::query::QueryNoteStore>(note);
        query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
            auto result = dynamic_cast<db::query::NoteStoreResult *>(response);
            if (result == nullptr) {
                return false;
            }
            if (callback) {
                callback(result->succeed());
            }
            return true;
        }));
        DBServiceAPI::GetQuery(application, db::Interface::Name::Notes, std::move(query));
    }

    void NotesDBRepository::remove(const NotesRecord &note, const OnResultCallback &callback)
    {
        auto query = std::make_unique<db::query::QueryNoteRemove>(note.ID);
        query->setQueryListener(db::QueryCallback::fromFunction([callback](auto response) {
            auto result = dynamic_cast<db::query::NoteRemoveResult *>(response);
            if (result == nullptr) {
                return false;
            }
            if (callback) {
                callback(result->succeed());
            }
            return true;
        }));
        DBServiceAPI::GetQuery(application, db::Interface::Name::Notes, std::move(query));
    }
} // namespace app::notes

A module-apps/application-notes/model/NotesRepository.hpp => module-apps/application-notes/model/NotesRepository.hpp +39 -0
@@ 0,0 1,39 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <functional>

#include <module-apps/Application.hpp>

#include <module-db/Interface/NotesRecord.hpp>

namespace app::notes
{
    class AbstractNotesRepository
    {
      public:
        using OnGetCallback    = std::function<bool(const std::vector<NotesRecord> &, unsigned int)>;
        using OnResultCallback = std::function<void(bool)>;

        virtual ~AbstractNotesRepository() noexcept = default;

        virtual void get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback) = 0;
        virtual void save(const NotesRecord &note, const OnResultCallback &callback)               = 0;
        virtual void remove(const NotesRecord &note, const OnResultCallback &callback)             = 0;
    };

    class NotesDBRepository : public AbstractNotesRepository
    {
      public:
        explicit NotesDBRepository(Application *application);

        void get(std::uint32_t offset, std::uint32_t limit, const OnGetCallback &callback) override;
        void save(const NotesRecord &note, const OnResultCallback &callback) override;
        void remove(const NotesRecord &note, const OnResultCallback &callback) override;

      private:
        Application *application;
    };
} // namespace app::notes

A module-apps/application-notes/presenter/NoteEditWindowPresenter.cpp => module-apps/application-notes/presenter/NoteEditWindowPresenter.cpp +16 -0
@@ 0,0 1,16 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NoteEditWindowPresenter.hpp"

namespace app::notes
{
    NoteEditWindowPresenter::NoteEditWindowPresenter(std::unique_ptr<AbstractNotesRepository> &&notesRepository)
        : notesRepository{std::move(notesRepository)}
    {}

    void NoteEditWindowPresenter::save(const NotesRecord &note)
    {
        notesRepository->save(note, nullptr);
    }
} // namespace app::notes

A module-apps/application-notes/presenter/NoteEditWindowPresenter.hpp => module-apps/application-notes/presenter/NoteEditWindowPresenter.hpp +39 -0
@@ 0,0 1,39 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-apps/BasePresenter.hpp>

#include <module-apps/application-notes/model/NotesRepository.hpp>

namespace app::notes
{
    class NoteEditWindowContract
    {
      public:
        class View
        {
          public:
            virtual ~View() noexcept = default;
        };
        class Presenter : public BasePresenter<NoteEditWindowContract::View>
        {
          public:
            virtual ~Presenter() noexcept = default;

            virtual void save(const NotesRecord &note) = 0;
        };
    };

    class NoteEditWindowPresenter : public NoteEditWindowContract::Presenter
    {
      public:
        explicit NoteEditWindowPresenter(std::unique_ptr<AbstractNotesRepository> &&notesRepository);

        void save(const NotesRecord &note) override;

      private:
        std::unique_ptr<AbstractNotesRepository> notesRepository;
    };
} // namespace app::notes

A module-apps/application-notes/presenter/NotePreviewWindowPresenter.cpp => module-apps/application-notes/presenter/NotePreviewWindowPresenter.cpp +16 -0
@@ 0,0 1,16 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotePreviewWindowPresenter.hpp"

namespace app::notes
{
    NotePreviewWindowPresenter::NotePreviewWindowPresenter(std::unique_ptr<AbstractNotesRepository> notesRepository)
        : notesRepository{std::move(notesRepository)}
    {}

    AbstractNotesRepository &NotePreviewWindowPresenter::getRepository()
    {
        return *notesRepository;
    }
} // namespace app::notes

A module-apps/application-notes/presenter/NotePreviewWindowPresenter.hpp => module-apps/application-notes/presenter/NotePreviewWindowPresenter.hpp +39 -0
@@ 0,0 1,39 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-apps/BasePresenter.hpp>

#include <module-apps/application-notes/model/NotesRepository.hpp>

namespace app::notes
{
    class NotePreviewWindowContract
    {
      public:
        class View
        {
          public:
            virtual ~View() noexcept = default;
        };
        class Presenter : public BasePresenter<NotePreviewWindowContract::View>
        {
          public:
            virtual ~Presenter() noexcept = default;

            virtual AbstractNotesRepository &getRepository() = 0;
        };
    };

    class NotePreviewWindowPresenter : public NotePreviewWindowContract::Presenter
    {
      public:
        explicit NotePreviewWindowPresenter(std::unique_ptr<AbstractNotesRepository> notesRepository);

        AbstractNotesRepository &getRepository() override;

      private:
        std::unique_ptr<AbstractNotesRepository> notesRepository;
    };
} // namespace app::notes

A module-apps/application-notes/presenter/NotesMainWindowPresenter.cpp => module-apps/application-notes/presenter/NotesMainWindowPresenter.cpp +26 -0
@@ 0,0 1,26 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotesMainWindowPresenter.hpp"

namespace app::notes
{
    NotesMainWindowPresenter::NotesMainWindowPresenter(std::shared_ptr<NotesListItemProvider> notesProvider)
        : notesProvider{std::move(notesProvider)}
    {}

    std::shared_ptr<gui::ListItemProvider> NotesMainWindowPresenter::getNotesItemProvider() const
    {
        return notesProvider;
    }

    bool NotesMainWindowPresenter::isNoteListEmpty()
    {
        return notesProvider->requestRecordsCount() == 0U;
    }

    bool NotesMainWindowPresenter::updateNotes(std::vector<NotesRecord> &&records)
    {
        return notesProvider->updateRecords(std::move(records));
    }
} // namespace app::notes

A module-apps/application-notes/presenter/NotesMainWindowPresenter.hpp => module-apps/application-notes/presenter/NotesMainWindowPresenter.hpp +43 -0
@@ 0,0 1,43 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-apps/BasePresenter.hpp>

#include <module-apps/application-notes/model/NotesListModel.hpp>

namespace app::notes
{
    class NotesMainWindowContract
    {
      public:
        class View
        {
          public:
            virtual ~View() noexcept = default;
        };
        class Presenter : public BasePresenter<NotesMainWindowContract::View>
        {
          public:
            virtual ~Presenter() noexcept = default;

            virtual std::shared_ptr<gui::ListItemProvider> getNotesItemProvider() const = 0;
            virtual bool isNoteListEmpty()                                              = 0;
            virtual bool updateNotes(std::vector<NotesRecord> &&records)                = 0;
        };
    };

    class NotesMainWindowPresenter : public NotesMainWindowContract::Presenter
    {
      public:
        explicit NotesMainWindowPresenter(std::shared_ptr<NotesListItemProvider> notesListItemProvider);

        std::shared_ptr<gui::ListItemProvider> getNotesItemProvider() const override;
        bool isNoteListEmpty() override;
        bool updateNotes(std::vector<NotesRecord> &&records) override;

      private:
        std::shared_ptr<NotesListItemProvider> notesProvider;
    };
} // namespace app::notes

A module-apps/application-notes/style/NoteEditStyle.hpp => module-apps/application-notes/style/NoteEditStyle.hpp +25 -0
@@ 0,0 1,25 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-gui/gui/widgets/Style.hpp>

namespace app::notes::style::edit
{
    constexpr inline auto LeftMargin = ::style::window::default_left_margin;
    constexpr inline auto TopMargin  = ::style::header::height + ::style::margins::very_big - 1;
    constexpr inline auto Width      = ::style::window::default_body_width;
    constexpr inline auto Height     = ::style::window::default_body_height - ::style::margins::very_big;

    namespace counter
    {
        constexpr inline auto Height = 30;
    } // namespace counter

    namespace text
    {
        constexpr inline auto TopMargin = 6;
        constexpr inline auto Height    = ::app::notes::style::edit::Height - counter::Height - TopMargin;
    } // namespace text
} // namespace app::notes::style::edit

A module-apps/application-notes/style/NotePreviewStyle.hpp => module-apps/application-notes/style/NotePreviewStyle.hpp +24 -0
@@ 0,0 1,24 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-gui/gui/widgets/Style.hpp>

namespace app::notes::style::preview
{
    constexpr inline auto LeftMargin = ::style::window::default_left_margin;
    constexpr inline auto TopMargin  = ::style::header::height + ::style::margins::very_big - 1;
    constexpr inline auto Width      = ::style::window::default_body_width;
    constexpr inline auto Height     = ::style::window::default_body_height - ::style::margins::very_big;

    namespace date
    {
        constexpr inline auto Height = 30;
    } // namespace date
    namespace text
    {
        constexpr inline auto TopMargin = 7;
        constexpr inline auto Height    = ::app::notes::style::preview::Height - date::Height - TopMargin;
    } // namespace text
} // namespace app::notes::style::preview

A module-apps/application-notes/style/NotesListStyle.hpp => module-apps/application-notes/style/NotesListStyle.hpp +45 -0
@@ 0,0 1,45 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-gui/gui/widgets/Style.hpp>

namespace app::notes::style::list
{
    constexpr inline auto X      = ::style::window::default_left_margin;
    constexpr inline auto Y      = ::style::header::height + ::style::margins::small - 1;
    constexpr inline auto Width  = ::style::window::default_body_width;
    constexpr inline auto Height = ::style::window::default_body_height - ::style::margins::small;

    constexpr inline auto PenWidth        = 0;
    constexpr inline auto FocusedPenWidth = 0;

    namespace item
    {
        constexpr inline auto Height = 136;
        constexpr inline auto Radius = 0;

        constexpr inline auto VerticalPadding = 16;
        constexpr inline auto LeftPadding     = 10;
        constexpr inline auto RightPadding    = 4;
        namespace title
        {
            constexpr inline auto Width  = 310;
            constexpr inline auto Height = 33;
        } // namespace title

        namespace date
        {
            constexpr inline auto Width  = 90;
            constexpr inline auto Height = 20;
        } // namespace date

        namespace snippet
        {
            constexpr inline auto Width     = 417;
            constexpr inline auto Height    = 66;
            constexpr inline auto TopMargin = 5;
        } // namespace snippet
    }     // namespace item
} // namespace app::notes::style::list

A module-apps/application-notes/style/NotesMainWindowStyle.hpp => module-apps/application-notes/style/NotesMainWindowStyle.hpp +37 -0
@@ 0,0 1,37 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-gui/gui/widgets/Style.hpp>

namespace app::notes::style::main_window
{
    namespace new_note_arrow
    {
        constexpr inline auto X           = 30;
        constexpr inline auto Y           = 62;
        constexpr inline auto ImageSource = "arrow_left";
    } // namespace new_note_arrow

    namespace new_note_image
    {
        constexpr inline auto X           = 48;
        constexpr inline auto Y           = 55;
        constexpr inline auto ImageSource = "cross";
    } // namespace new_note_image

    namespace search_arrow
    {
        constexpr inline auto X           = 480 - 30 - 13;
        constexpr inline auto Y           = 62;
        constexpr inline auto ImageSource = "arrow_right";
    } // namespace search_arrow

    namespace search_image
    {
        constexpr inline auto X           = 480 - 48 - 26;
        constexpr inline auto Y           = 55;
        constexpr inline auto ImageSource = "search";
    } // namespace search_image
} // namespace app::notes::style::main_window

M module-apps/application-notes/widgets/NotesItem.cpp => module-apps/application-notes/widgets/NotesItem.cpp +62 -43
@@ 2,71 2,90 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotesItem.hpp"

#include <Style.hpp>
#include <module-apps/application-notes/style/NotesListStyle.hpp>

#include <module-utils/time/time_conversion.hpp>

namespace gui
{

    NotesItem::NotesItem(NotesModel *model, bool mode24H) : model{model}, mode24H{mode24H}
    NotesItem::NotesItem(std::shared_ptr<NotesRecord> record) : note{std::move(record)}
    {
        setMinimumSize(style::window::default_body_width, 146);
        setMaximumSize(style::window::default_body_width, 146);

        setRadius(8);

        setPenFocusWidth(3);
        setPenWidth(1);
        buildInterface();
    }

        hour = new gui::Label(this, 0, 0, 0, 0);
        hour->setPenFocusWidth(0);
        hour->setPenWidth(0);
        hour->setFont(style::window::font::medium);
        hour->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Top});
    void NotesItem::buildInterface()
    {
        namespace notesItemStyle = app::notes::style::list::item;
        setMinimumSize(style::window::default_body_width, notesItemStyle::Height);
        setMaximumSize(style::window::default_body_width, notesItemStyle::Height);
        setRadius(notesItemStyle::Radius);
        setEdges(RectangleEdge::Bottom | RectangleEdge::Top);
        setPenFocusWidth(style::window::default_border_focus_w);
        setPenWidth(style::window::default_border_no_focus_w);

        title = new gui::Label(this, 0, 0, 0, 0);
        title->setPenFocusWidth(0);
        title->setPenWidth(0);
        title = createEmptyLabel(this);
        title->setFont(style::window::font::bigbold);
        title->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Top});

        snippet = new gui::Label(this, 0, 0, 0, 0);
        snippet->setPenFocusWidth(0);
        snippet->setPenWidth(0);
        date = createEmptyLabel(this);
        date->setFont(style::window::font::medium);
        date->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Top});

        snippet = new gui::TextFixedSize(this, 0, 0, 0, 0);
        snippet->setFont(style::window::font::small);
        snippet->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center});
        snippet->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Left});
        snippet->setEdges(gui::RectangleEdge::None);
        snippet->setPenFocusWidth(::style::window::default_border_focus_w);
        snippet->setPenWidth(::style::window::default_border_rect_no_focus);
        snippet->setEditMode(gui::EditMode::BROWSE);
        snippet->setUnderline(false);

        setSnippet(note->snippet);
        setDateText(note->date);
    }

    NotesItem::~NotesItem()
    gui::Label *NotesItem::createEmptyLabel(Item *parent)
    {
        note = nullptr;
        auto label = new gui::Label(parent, 0, 0, 0, 0);
        label->setPenFocusWidth(0);
        label->setPenWidth(0);
        return label;
    }

    bool NotesItem::onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim)
    void NotesItem::setSnippet(const UTF8 &noteText)
    {
        hour->setPosition(11, 0);
        hour->setSize(newDim.w - 22, 40);

        title->setPosition(11, 0);
        title->setSize(68, 40);

        snippet->setPosition(11, 40);
        snippet->setSize(newDim.w - 22, newDim.h - 40);
        return true;
        title->setText(noteText);
        snippet->setText(noteText);
    }

    // sets copy of alarm's
    void NotesItem::setNote(std::shared_ptr<NotesRecord> &note)
    void NotesItem::setDateText(std::uint32_t timestamp)
    {
        this->note = note;
        // set values of the labels
        title->setText(std::to_string(note->ID));
        snippet->setText(note->path);
        if (auto dt = utils::time::DateTime(timestamp); dt.isYesterday()) {
            date->setText(utils::localize.get("common_yesterday"));
        }
        else {
            date->setText(dt);
        }
    }

    bool NotesItem::onActivated(void *data)
    bool NotesItem::onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim)
    {
        LOG_INFO("ITEM WAS PRESSED");
        namespace notesItemStyle = app::notes::style::list::item;
        title->setPosition(notesItemStyle::LeftPadding, notesItemStyle::VerticalPadding);
        title->setSize(notesItemStyle::title::Width, notesItemStyle::title::Height);

        date->setPosition(notesItemStyle::LeftPadding, notesItemStyle::VerticalPadding);
        date->setSize(newDim.w - (notesItemStyle::LeftPadding + notesItemStyle::RightPadding),
                      notesItemStyle::date::Height);

        snippet->setPosition(notesItemStyle::LeftPadding,
                             notesItemStyle::VerticalPadding + notesItemStyle::title::Height +
                                 notesItemStyle::snippet::TopMargin);
        snippet->setSize(newDim.w - (notesItemStyle::LeftPadding + notesItemStyle::RightPadding),
                         newDim.h - (2 * notesItemStyle::VerticalPadding) - notesItemStyle::title::Height -
                             notesItemStyle::snippet::TopMargin);
        return true;
    }

} /* namespace gui */
} // namespace gui

M module-apps/application-notes/widgets/NotesItem.hpp => module-apps/application-notes/widgets/NotesItem.hpp +20 -32
@@ 1,45 1,33 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#ifndef MODULE_APPS_APPLICATION_NOTES_WIDGETS_NOTESITEM_HPP_
#define MODULE_APPS_APPLICATION_NOTES_WIDGETS_NOTESITEM_HPP_
#pragma once

#include "Label.hpp"
#include "ListItem.hpp"
#include "../NotesModel.hpp"
#include <module-gui/gui/widgets/Label.hpp>
#include <module-gui/gui/widgets/ListItem.hpp>
#include <module-gui/gui/widgets/TextFixedSize.hpp>

#include <module-db/Interface/NotesRecord.hpp>

namespace gui
{

    /*
     * @brief Widget used to display information about note in the notes list view.
     */
    class NotesItem : public ListItem
    {

        NotesModel *model = nullptr;
        // pointer to the notes record
        std::shared_ptr<NotesRecord> note = nullptr;
        // this is hour in the mode defined in settings
        gui::Label *hour    = nullptr;
        gui::Label *title   = nullptr;
        gui::Label *snippet = nullptr;
        // flag that defines if time should be displayed in 24h mode
        bool mode24H = false;

      public:
        NotesItem(NotesModel *model, bool mode24H);
        virtual ~NotesItem();
        // sets copy of alarm's
        void setNote(std::shared_ptr<NotesRecord> &note);

        // returns alarm structure
        std::shared_ptr<NotesRecord> getAlarm();
        // virtual methods from Item
        explicit NotesItem(std::shared_ptr<NotesRecord> record);

        bool onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim) override;
        bool onActivated(void *data) override;
    };

} /* namespace gui */
      private:
        static gui::Label *createEmptyLabel(Item *parent);

#endif /* MODULE_APPS_APPLICATION_NOTES_WIDGETS_NOTESITEM_HPP_ */
        void buildInterface();
        void setSnippet(const UTF8 &noteText);
        void setDateText(std::uint32_t timestamp);

        std::shared_ptr<NotesRecord> note;
        gui::Label *date            = nullptr;
        gui::Label *title           = nullptr;
        gui::TextFixedSize *snippet = nullptr;
    };
} // namespace gui

A module-apps/application-notes/windows/NoteEditWindow.cpp => module-apps/application-notes/windows/NoteEditWindow.cpp +152 -0
@@ 0,0 1,152 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NoteEditWindow.hpp"

#include <sstream>

#include <Style.hpp>

#include <module-apps/application-notes/ApplicationNotes.hpp>
#include <module-apps/application-notes/windows/NotesOptions.hpp>
#include <module-apps/application-notes/data/NoteSwitchData.hpp>
#include <module-apps/application-notes/style/NoteEditStyle.hpp>
#include <module-apps/messages/OptionsWindow.hpp>

#include <module-utils/i18n/i18n.hpp>
#include <module-utils/time/time_conversion.hpp>

#include <module-gui/gui/widgets/BottomBar.hpp>
#include <module-gui/gui/widgets/TopBar.hpp>

namespace app::notes
{
    namespace
    {
        constexpr auto MaxCharactersCount = 4000U;
    } // namespace

    NoteEditWindow::NoteEditWindow(app::Application *app,
                                   std::unique_ptr<NoteEditWindowContract::Presenter> &&windowPresenter)
        : gui::AppWindow(app, gui::name::window::note_edit), presenter{std::move(windowPresenter)}
    {
        presenter->attach(this);
        buildInterface();
    }

    NoteEditWindow::~NoteEditWindow() noexcept
    {
        destroyInterface();
    }

    void NoteEditWindow::rebuild()
    {
        destroyInterface();
        buildInterface();
    }

    void NoteEditWindow::buildInterface()
    {
        AppWindow::buildInterface();

        setTitle(utils::localize.get("app_notes_edit_new_note"));

        namespace editStyle = app::notes::style::edit;
        charactersCounter   = new gui::Label(
            this, editStyle::LeftMargin, editStyle::TopMargin, editStyle::Width, editStyle::counter::Height);
        charactersCounter->setEdges(gui::RectangleEdge::None);
        charactersCounter->setFont(::style::window::font::verysmall);
        charactersCounter->setAlignment(gui::Alignment{gui::Alignment::Horizontal::Right});

        edit = new gui::Text(this,
                             editStyle::LeftMargin,
                             editStyle::TopMargin + editStyle::counter::Height + editStyle::text::TopMargin,
                             editStyle::Width,
                             editStyle::text::Height);
        edit->setAlignment(gui::Alignment{gui::Alignment::Vertical::Top});
        edit->setPenFocusWidth(::style::window::default_border_focus_w);
        edit->setPenWidth(::style::window::default_border_rect_no_focus);
        edit->setEdges(gui::RectangleEdge::None);
        edit->setFont(::style::window::font::medium);
        edit->setInputMode(new InputMode(
            {InputMode::ABC, InputMode::abc, InputMode::digit},
            [=](const UTF8 &text) { bottomBarTemporaryMode(text); },
            [=]() { bottomBarRestoreFromTemporaryMode(); },
            [=]() { selectSpecialCharacter(); }));
        edit->setTextChangedCallback([this](Item &, const UTF8 &text) { setCharactersCount(text.length()); });

        bottomBar->setActive(gui::BottomBar::Side::LEFT, true);
        bottomBar->setText(gui::BottomBar::Side::LEFT, utils::localize.get(::style::strings::common::options));

        bottomBar->setActive(gui::BottomBar::Side::CENTER, true);
        bottomBar->setText(gui::BottomBar::Side::CENTER, utils::localize.get(::style::strings::common::save));

        bottomBar->setActive(gui::BottomBar::Side::RIGHT, true);
        bottomBar->setText(gui::BottomBar::Side::RIGHT, utils::localize.get(::style::strings::common::back));

        topBar->setActive(gui::TopBar::Elements::TIME, true);

        setFocusItem(edit);
    }

    void NoteEditWindow::setCharactersCount(std::uint32_t count)
    {
        std::ostringstream counterText;
        counterText << count << '/' << MaxCharactersCount;
        charactersCounter->setText(counterText.str());
    }

    void NoteEditWindow::destroyInterface()
    {
        erase();
        charactersCounter = nullptr;
        edit              = nullptr;
    }

    void NoteEditWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    {
        auto editData = dynamic_cast<NoteSwitchData *>(data);
        if (editData == nullptr) {
            return;
        }

        notesRecord = std::make_unique<NotesRecord>(editData->getRecord());
        setNoteText(notesRecord->snippet);
    }

    void NoteEditWindow::setNoteText(const UTF8 &text)
    {
        edit->setText(text);
    }

    bool NoteEditWindow::onInput(const gui::InputEvent &inputEvent)
    {
        if (inputEvent.isShortPress()) {
            if (inputEvent.is(gui::KeyCode::KEY_ENTER)) {
                saveNote();
                auto switchData                        = std::make_unique<NoteSwitchData>(*notesRecord);
                switchData->ignoreCurrentWindowOnStack = true;
                application->switchWindow(gui::name::window::note_preview, std::move(switchData));
            }
            if (inputEvent.is(gui::KeyCode::KEY_LF)) {
                application->switchWindow(
                    utils::localize.get("app_phonebook_options_title"),
                    std::make_unique<gui::OptionsWindowOptions>(noteEditOptions(application, *notesRecord, edit)));
            }
        }
        return AppWindow::onInput(inputEvent);
    }

    void NoteEditWindow::saveNote()
    {
        const auto &newSnippetText = edit->getText();
        if (newSnippetText.length() > MaxCharactersCount) {
            LOG_ERROR("Unable to save the note: text is too long.");
            return;
        }

        notesRecord->date    = utils::time::getCurrentTimestamp().getTime();
        notesRecord->snippet = newSnippetText;
        presenter->save(*notesRecord);
    }
} // namespace app::notes

A module-apps/application-notes/windows/NoteEditWindow.hpp => module-apps/application-notes/windows/NoteEditWindow.hpp +43 -0
@@ 0,0 1,43 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <cstdint>

#include "AppWindow.hpp"

#include <module-db/Interface/NotesRecord.hpp>

#include "gui/widgets/Label.hpp"
#include "gui/widgets/Text.hpp"
#include "gui/input/InputEvent.hpp"

#include <module-apps/application-notes/presenter/NoteEditWindowPresenter.hpp>

namespace app::notes
{
    class NoteEditWindow : public gui::AppWindow, public NoteEditWindowContract::View
    {
      public:
        NoteEditWindow(app::Application *app, std::unique_ptr<NoteEditWindowContract::Presenter> &&windowPresenter);
        ~NoteEditWindow() noexcept override;

        void onBeforeShow(gui::ShowMode mode, gui::SwitchData *data) override;
        bool onInput(const gui::InputEvent &inputEvent) override;

        void rebuild() override;
        void buildInterface() override;
        void destroyInterface() override;

      private:
        void setCharactersCount(std::uint32_t count);
        void setNoteText(const UTF8 &text);
        void saveNote();

        std::unique_ptr<NoteEditWindowContract::Presenter> presenter;
        std::unique_ptr<NotesRecord> notesRecord;
        gui::Label *charactersCounter;
        gui::Text *edit;
    };
} // namespace app::notes

A module-apps/application-notes/windows/NoteMainWindow.cpp => module-apps/application-notes/windows/NoteMainWindow.cpp +179 -0
@@ 0,0 1,179 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NoteMainWindow.hpp"

#include <module-apps/application-notes/ApplicationNotes.hpp>
#include <module-apps/application-notes/data/NoteSwitchData.hpp>

#include <InputEvent.hpp>
#include <service-db/DBNotesMessage.hpp>

#include <module-utils/i18n/i18n.hpp>

#include <gui/widgets/Label.hpp>
#include <gui/widgets/BottomBar.hpp>
#include <gui/widgets/TopBar.hpp>

#include <Style.hpp>
#include <module-apps/application-notes/style/NotesListStyle.hpp>
#include <module-apps/application-notes/style/NotesMainWindowStyle.hpp>

namespace app::notes
{
    NoteMainWindow::NoteMainWindow(app::Application *app,
                                   std::unique_ptr<NotesMainWindowContract::Presenter> &&windowPresenter)
        : AppWindow(app, gui::name::window::main_window), presenter{std::move(windowPresenter)}
    {
        presenter->attach(this);
        buildInterface();
    }

    NoteMainWindow::~NoteMainWindow() noexcept
    {
        destroyInterface();
    }

    void NoteMainWindow::rebuild()
    {
        destroyInterface();
        buildInterface();
    }

    void NoteMainWindow::buildInterface()
    {
        AppWindow::buildInterface();

        setTitle(utils::localize.get("app_notes_title_main"));

        bottomBar->setActive(gui::BottomBar::Side::LEFT, true);
        bottomBar->setText(gui::BottomBar::Side::LEFT, utils::localize.get(::style::strings::common::options));
        bottomBar->setActive(gui::BottomBar::Side::CENTER, true);
        bottomBar->setText(gui::BottomBar::Side::CENTER, utils::localize.get(::style::strings::common::open));
        bottomBar->setActive(gui::BottomBar::Side::RIGHT, true);
        bottomBar->setText(gui::BottomBar::Side::RIGHT, utils::localize.get(::style::strings::common::back));
        topBar->setActive(gui::TopBar::Elements::TIME, true);

        namespace windowStyle = app::notes::style::main_window;
        leftArrowImage        = new gui::Image(this,
                                        windowStyle::new_note_arrow::X,
                                        windowStyle::new_note_arrow::Y,
                                        0,
                                        0,
                                        windowStyle::new_note_arrow::ImageSource);
        rightArrowImage       = new gui::Image(this,
                                         windowStyle::search_arrow::X,
                                         windowStyle::search_arrow::Y,
                                         0,
                                         0,
                                         windowStyle::search_arrow::ImageSource);
        newNoteImage          = new gui::Image(this,
                                      windowStyle::new_note_image::X,
                                      windowStyle::new_note_image::Y,
                                      0,
                                      0,
                                      windowStyle::new_note_image::ImageSource);
        searchImage           = new gui::Image(this,
                                     windowStyle::search_image::X,
                                     windowStyle::search_image::Y,
                                     0,
                                     0,
                                     windowStyle::search_image::ImageSource);

        namespace listStyle = app::notes::style::list;
        list                = new gui::ListView(
            this, listStyle::X, listStyle::Y, listStyle::Width, listStyle::Height, presenter->getNotesItemProvider());
        list->setPenWidth(listStyle::PenWidth);
        list->setPenFocusWidth(listStyle::FocusedPenWidth);
        list->focusChangedCallback = [this]([[maybe_unused]] gui::Item &item) {
            onListFilled();
            return true;
        };
        list->setVisible(true);
        list->rebuildList();

        emptyListIcon                       = new gui::Icon(this,
                                      0,
                                      ::style::header::height,
                                      ::style::window_width,
                                      ::style::window_height - ::style::header::height - ::style::footer::height,
                                      "phonebook_empty_grey_circle_W_G",
                                      utils::localize.get("app_notes_no_notes"));
        emptyListIcon->focusChangedCallback = [this]([[maybe_unused]] gui::Item &item) {
            onEmptyList();
            return true;
        };
        emptyListIcon->setVisible(false);

        setFocusItem(list);
    }

    void NoteMainWindow::destroyInterface()
    {
        erase();
        list            = nullptr;
        leftArrowImage  = nullptr;
        rightArrowImage = nullptr;
        newNoteImage    = nullptr;
        searchImage     = nullptr;
        emptyListIcon   = nullptr;
    }

    void NoteMainWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    {
        if (presenter->isNoteListEmpty()) {
            showEmptyIcon();
        }
        else {
            showList();
        }
    }

    void NoteMainWindow::showEmptyIcon()
    {
        list->setVisible(false);
        emptyListIcon->setVisible(true);
        setFocusItem(emptyListIcon);
    }

    void NoteMainWindow::showList()
    {
        list->rebuildList();
        list->setVisible(true);
        emptyListIcon->setVisible(false);
        setFocusItem(list);
    }

    void NoteMainWindow::onEmptyList()
    {
        bottomBar->setActive(gui::BottomBar::Side::LEFT, false);
        bottomBar->setActive(gui::BottomBar::Side::CENTER, false);
        rightArrowImage->setVisible(false);
        searchImage->setVisible(false);
    }

    void NoteMainWindow::onListFilled()
    {
        bottomBar->setActive(gui::BottomBar::Side::LEFT, true);
        bottomBar->setActive(gui::BottomBar::Side::CENTER, true);
        rightArrowImage->setVisible(true);
        searchImage->setVisible(true);
    }

    bool NoteMainWindow::onInput(const gui::InputEvent &inputEvent)
    {
        if (inputEvent.isShortPress()) {
            if (inputEvent.is(gui::KeyCode::KEY_LEFT)) {
                application->switchWindow(gui::name::window::note_edit,
                                          std::make_unique<NoteSwitchData>(NotesRecord{}));
            }
        }
        return AppWindow::onInput(inputEvent);
    }

    bool NoteMainWindow::onDatabaseMessage(sys::Message *msgl)
    {
        auto *msg = static_cast<DBNotesResponseMessage *>(msgl);
        return presenter->updateNotes(std::move(*msg->records));
    }
} // namespace app::notes

A module-apps/application-notes/windows/NoteMainWindow.hpp => module-apps/application-notes/windows/NoteMainWindow.hpp +46 -0
@@ 0,0 1,46 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <memory>

#include <AppWindow.hpp>
#include <module-gui/gui/widgets/Image.hpp>
#include <module-gui/gui/widgets/Icon.hpp>
#include <module-gui/gui/widgets/ListView.hpp>

#include <module-apps/application-notes/presenter/NotesMainWindowPresenter.hpp>

namespace app::notes
{
    class NoteMainWindow : public gui::AppWindow, public NotesMainWindowContract::View
    {
      public:
        NoteMainWindow(app::Application *app, std::unique_ptr<NotesMainWindowContract::Presenter> &&windowPresenter);
        ~NoteMainWindow() noexcept override;

        // virtual methods
        bool onInput(const gui::InputEvent &inputEvent) override;
        void onBeforeShow(gui::ShowMode mode, gui::SwitchData *data) override;

        void rebuild() override;
        void buildInterface() override;
        void destroyInterface() override;
        bool onDatabaseMessage(sys::Message *msg) override;

      private:
        void showEmptyIcon();
        void showList();
        void onEmptyList();
        void onListFilled();

        std::unique_ptr<NotesMainWindowContract::Presenter> presenter;
        gui::ListView *list         = nullptr;
        gui::Image *leftArrowImage  = nullptr;
        gui::Image *rightArrowImage = nullptr;
        gui::Image *newNoteImage    = nullptr;
        gui::Image *searchImage     = nullptr;
        gui::Icon *emptyListIcon    = nullptr;
    };
} // namespace app::notes

A module-apps/application-notes/windows/NotePreviewWindow.cpp => module-apps/application-notes/windows/NotePreviewWindow.cpp +129 -0
@@ 0,0 1,129 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotePreviewWindow.hpp"

#include <module-apps/application-notes/ApplicationNotes.hpp>
#include <module-apps/application-notes/data/NoteSwitchData.hpp>
#include <module-apps/application-notes/style/NotePreviewStyle.hpp>
#include <module-apps/application-notes/windows/NotesOptions.hpp>
#include <module-apps/messages/OptionsWindow.hpp>

#include <module-utils/i18n/i18n.hpp>
#include <module-utils/time/time_conversion.hpp>

#include <Style.hpp>

#include <module-gui/gui/widgets/BottomBar.hpp>
#include <module-gui/gui/widgets/TopBar.hpp>

namespace app::notes
{
    NotePreviewWindow::NotePreviewWindow(app::Application *app,
                                         std::unique_ptr<NotePreviewWindowContract::Presenter> &&windowPresenter)
        : AppWindow(app, gui::name::window::note_preview), presenter(std::move(windowPresenter))
    {
        presenter->attach(this);
        buildInterface();
    }

    NotePreviewWindow::~NotePreviewWindow() noexcept
    {
        destroyInterface();
    }

    void NotePreviewWindow::rebuild()
    {
        destroyInterface();
        buildInterface();
    }

    void NotePreviewWindow::destroyInterface()
    {
        erase();
        date = nullptr;
        note = nullptr;
    }

    void NotePreviewWindow::buildInterface()
    {
        AppWindow::buildInterface();

        setTitle(utils::localize.get("app_notes_title_main"));

        namespace previewStyle = app::notes::style::preview;
        date                   = new gui::Label(
            this, previewStyle::LeftMargin, previewStyle::TopMargin, previewStyle::Width, previewStyle::date::Height);
        date->setEdges(gui::RectangleEdge::None);
        date->setFont(::style::window::font::small);

        note =
            new gui::TextFixedSize(this,
                                   previewStyle::LeftMargin,
                                   previewStyle::TopMargin + previewStyle::date::Height + previewStyle::text::TopMargin,
                                   previewStyle::Width,
                                   previewStyle::text::Height);
        note->setEdges(gui::RectangleEdge::None);
        note->setFont(::style::window::font::medium);
        note->setAlignment(gui::Alignment{gui::Alignment::Vertical::Top});
        note->setPenFocusWidth(::style::window::default_border_focus_w);
        note->setPenWidth(::style::window::default_border_rect_no_focus);
        note->setEditMode(gui::EditMode::BROWSE);
        note->setUnderline(false);

        bottomBar->setActive(gui::BottomBar::Side::LEFT, true);
        bottomBar->setText(gui::BottomBar::Side::LEFT, utils::localize.get(::style::strings::common::options));

        bottomBar->setActive(gui::BottomBar::Side::CENTER, true);
        bottomBar->setText(gui::BottomBar::Side::CENTER, utils::localize.get("app_notes_edit"));

        bottomBar->setActive(gui::BottomBar::Side::RIGHT, true);
        bottomBar->setText(gui::BottomBar::Side::RIGHT, utils::localize.get(::style::strings::common::back));

        topBar->setActive(gui::TopBar::Elements::TIME, true);

        setFocusItem(note);
    }

    void NotePreviewWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    {
        auto previewData = dynamic_cast<NoteSwitchData *>(data);
        if (previewData == nullptr) {
            return;
        }

        notesRecord = std::make_unique<NotesRecord>(previewData->getRecord());
        setEditDateText(notesRecord->date);
        note->setText(notesRecord->snippet);
    }

    void NotePreviewWindow::setEditDateText(std::uint32_t timestamp)
    {
        utils::time::DateTime dt{timestamp};
        std::ostringstream dateText;
        dateText << utils::localize.get("app_notes_edited") << ": ";
        if (dt.isToday()) {
            dateText << utils::localize.get("common_today") << ", ";
        }
        else if (dt.isYesterday()) {
            dateText << utils::localize.get("common_yesterday") << ", ";
        }
        dateText << dt;
        date->setText(dateText.str());
    }

    bool NotePreviewWindow::onInput(const gui::InputEvent &inputEvent)
    {
        if (inputEvent.isShortPress()) {
            if (inputEvent.is(gui::KeyCode::KEY_ENTER)) {
                application->switchWindow(gui::name::window::note_edit, std::make_unique<NoteSwitchData>(*notesRecord));
            }
            else if (inputEvent.is(gui::KeyCode::KEY_LF)) {
                application->switchWindow(utils::localize.get("app_phonebook_options_title"),
                                          std::make_unique<gui::OptionsWindowOptions>(notePreviewOptions(
                                              application, *notesRecord, presenter->getRepository(), note)));
            }
        }
        return AppWindow::onInput(inputEvent);
    }
} // namespace app::notes

A module-apps/application-notes/windows/NotePreviewWindow.hpp => module-apps/application-notes/windows/NotePreviewWindow.hpp +39 -0
@@ 0,0 1,39 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "AppWindow.hpp"

#include <module-db/Interface/NotesRecord.hpp>
#include <module-apps/application-notes/presenter/NotePreviewWindowPresenter.hpp>

#include <module-gui/gui/widgets/Label.hpp>
#include <module-gui/gui/widgets/TextFixedSize.hpp>
#include <module-gui/gui/input/InputEvent.hpp>

namespace app::notes
{
    class NotePreviewWindow : public gui::AppWindow, public NotePreviewWindowContract::View
    {
      public:
        NotePreviewWindow(app::Application *app,
                          std::unique_ptr<NotePreviewWindowContract::Presenter> &&windowPresenter);
        ~NotePreviewWindow() noexcept override;

        void onBeforeShow(gui::ShowMode mode, gui::SwitchData *data) override;
        bool onInput(const gui::InputEvent &inputEvent) override;

        void rebuild() override;
        void buildInterface() override;
        void destroyInterface() override;

      private:
        void setEditDateText(std::uint32_t editTimestamp);

        std::unique_ptr<NotePreviewWindowContract::Presenter> presenter;
        std::unique_ptr<NotesRecord> notesRecord;
        gui::TextFixedSize *note = nullptr;
        gui::Label *date         = nullptr;
    };
} // namespace app::notes

D module-apps/application-notes/windows/NotesEditWindow.cpp => module-apps/application-notes/windows/NotesEditWindow.cpp +0 -88
@@ 1,88 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <memory>
#include <functional>

#include <service-appmgr/model/ApplicationManager.hpp>

#include "bsp/rtc/rtc.hpp"

#include "../ApplicationNotes.hpp"

#include <service-db/DBMessage.hpp>
#include "module-utils/i18n/i18n.hpp"

#include "Label.hpp"
#include "Margins.hpp"
#include "Text.hpp"
#include "NotesEditWindow.hpp"
#include <Style.hpp>

UTF8 textString = "Very long test line ABCDEFGHIJKLMNOPQRST123456789\n"
                  "abcdefghijklmnopqrs 123456789 ABCDEFGHIJKLMONPQRSTUW 12345\n    test\nnew line\n\n\n12345";

namespace gui
{

    NotesEditWindow::NotesEditWindow(app::Application *app) : AppWindow(app, "EditWindow")
    {
        buildInterface();
    }

    void NotesEditWindow::rebuild()
    {
        destroyInterface();
        buildInterface();
    }
    void NotesEditWindow::buildInterface()
    {
        AppWindow::buildInterface();

        bottomBar->setActive(BottomBar::Side::CENTER, true);
        bottomBar->setActive(BottomBar::Side::RIGHT, true);
        bottomBar->setText(BottomBar::Side::CENTER, utils::localize.get("app_notes_save"));
        bottomBar->setText(BottomBar::Side::RIGHT, utils::localize.get("app_notes_back"));

        setTitle(utils::localize.get("app_notes_new_note"));

        text = new gui::Text(this, 11, 105, 480 - 22, 600 - 105 - 50);
        text->setFont(style::window::font::medium);
        text->setRadius(5);
        text->setMargins(gui::Margins(10, 5, 10, 5));
        text->activatedCallback = [=](gui::Item &item) {
            UTF8 getstr = text->getText();
            time_t timestamp;
            bsp::rtc_GetCurrentTimestamp(&timestamp);

            UTF8 filename = "sys/data/applications/notes/" + std::to_string(timestamp) + ".txt";

            auto file = vfs.fopen(filename.c_str(), "wb");
            vfs.fwrite(getstr.c_str(), getstr.used() - 1, 1, file);
            vfs.fclose(file);
            return true;
        };
        text->setTextType(TextType::MULTI_LINE);
        text->setEditMode(EditMode::BROWSE);
        text->setInputMode(new InputMode({InputMode::ABC, InputMode::abc}));

        topBar->setActive(TopBar::Elements::TIME, true);
    }

    void NotesEditWindow::destroyInterface()
    {
        erase();
    }

    NotesEditWindow::~NotesEditWindow()
    {
        destroyInterface();
    }

    void NotesEditWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        text->setText(textString);
        setFocusItem(text);
    }

} /* namespace gui */

D module-apps/application-notes/windows/NotesEditWindow.hpp => module-apps/application-notes/windows/NotesEditWindow.hpp +0 -41
@@ 1,41 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#ifndef MODULE_APPS_APPLICATION_NOTES_WINDOWS_NOTESEDITWINDOW_HPP_
#define MODULE_APPS_APPLICATION_NOTES_WINDOWS_NOTESEDITWINDOW_HPP_

#include <string>
#include <functional>

#include "AppWindow.hpp"
#include "gui/widgets/Text.hpp"
#include "gui/widgets/Label.hpp"
#include "gui/widgets/Image.hpp"
#include "gui/widgets/Window.hpp"
#include "gui/widgets/BottomBar.hpp"
#include "gui/widgets/TopBar.hpp"
#include "gui/widgets/ListView.hpp"

namespace gui
{

    class NotesEditWindow : public AppWindow
    {
        Text *text   = nullptr;
        Label *title = nullptr;

      public:
        NotesEditWindow(app::Application *app);
        virtual ~NotesEditWindow();

        // virtual methods
        void onBeforeShow(ShowMode mode, SwitchData *data) override;

        void rebuild() override;
        void buildInterface() override;
        void destroyInterface() override;
    };

} /* namespace gui */

#endif /* MODULE_APPS_APPLICATION_NOTES_WINDOWS_NOTESEDITWINDOW_HPP_ */

D module-apps/application-notes/windows/NotesMainWindow.cpp => module-apps/application-notes/windows/NotesMainWindow.cpp +0 -86
@@ 1,86 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <memory>
#include <functional>

#include "InputEvent.hpp"
#include <service-appmgr/model/ApplicationManager.hpp>

#include "../ApplicationNotes.hpp"

#include <service-db/DBNotesMessage.hpp>
#include "module-utils/i18n/i18n.hpp"

#include "Label.hpp"
#include "Margins.hpp"
#include "NotesMainWindow.hpp"
#include <Style.hpp>

namespace gui
{

    NotesMainWindow::NotesMainWindow(app::Application *app)
        : AppWindow(app, gui::name::window::main_window), notesModel{std::make_shared<NotesModel>(app)}
    {
        buildInterface();
    }

    void NotesMainWindow::rebuild()
    {
        destroyInterface();
        buildInterface();
    }

    void NotesMainWindow::buildInterface()
    {
        AppWindow::buildInterface();

        list = new gui::ListView(this, 16, 105, 480 - 32, 440, notesModel);
        list->setPenFocusWidth(0);
        list->setPenWidth(0);

        setFocusItem(list);

        setTitle(utils::localize.get("app_notes_title_main"));

        bottomBar->setActive(BottomBar::Side::CENTER, true);
        bottomBar->setActive(BottomBar::Side::RIGHT, true);
        bottomBar->setText(BottomBar::Side::CENTER, utils::localize.get(style::strings::common::open));
        bottomBar->setText(BottomBar::Side::RIGHT, utils::localize.get(style::strings::common::back));

        topBar->setActive(TopBar::Elements::TIME, true);
    }

    void NotesMainWindow::destroyInterface()
    {
        erase();
    }

    void NotesMainWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        list->rebuildList();
    }

    bool NotesMainWindow::onInput(const InputEvent &inputEvent)
    {

        // process only if key is released
        if ((inputEvent.state != InputEvent::State::keyReleasedShort) ||
            (inputEvent.state != InputEvent::State::keyReleasedLong)) {
            if (inputEvent.keyCode == KeyCode::KEY_LEFT) {
                application->switchWindow("EditWindow");
                return true;
            }
        }

        return AppWindow::onInput(inputEvent);
    }

    bool NotesMainWindow::onDatabaseMessage(sys::Message *msgl)
    {
        DBNotesResponseMessage *msg = reinterpret_cast<DBNotesResponseMessage *>(msgl);
        return notesModel->updateRecords(std::move(*msg->records));
    }

} /* namespace gui */

D module-apps/application-notes/windows/NotesMainWindow.hpp => module-apps/application-notes/windows/NotesMainWindow.hpp +0 -44
@@ 1,44 0,0 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#ifndef MODULE_APPS_APPLICATION_NOTES_WINDOWS_NOTESMAINWINDOW_HPP_
#define MODULE_APPS_APPLICATION_NOTES_WINDOWS_NOTESMAINWINDOW_HPP_

#include <string>
#include <functional>

#include "AppWindow.hpp"
#include "gui/widgets/Label.hpp"
#include "gui/widgets/Image.hpp"
#include "gui/widgets/Window.hpp"
#include "gui/widgets/BottomBar.hpp"
#include "gui/widgets/TopBar.hpp"
#include "gui/widgets/ListView.hpp"

#include "../NotesModel.hpp"

namespace gui
{

    class NotesMainWindow : public AppWindow
    {
        std::shared_ptr<NotesModel> notesModel = nullptr;
        gui::ListView *list    = nullptr;
        Label *title           = nullptr;

      public:
        NotesMainWindow(app::Application *app);

        // virtual methods
        bool onInput(const InputEvent &inputEvent) override;
        void onBeforeShow(ShowMode mode, SwitchData *data) override;

        void rebuild() override;
        void buildInterface() override;
        void destroyInterface() override;
        bool onDatabaseMessage(sys::Message *msg) override;
    };

} /* namespace gui */

#endif /* MODULE_APPS_APPLICATION_NOTES_WINDOWS_NOTESMAINWINDOW_HPP_ */

A module-apps/application-notes/windows/NotesOptions.cpp => module-apps/application-notes/windows/NotesOptions.cpp +108 -0
@@ 0,0 1,108 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotesOptions.hpp"

#include <Options.hpp>
#include <common_data/Clipboard.hpp>

#include <module-apps/windows/DialogMetadata.hpp>
#include <module-apps/messages/DialogMetadataMessage.hpp>

#include <module-utils/i18n/i18n.hpp>

#include <module-gui/gui/widgets/Text.hpp>

namespace app::notes
{
    namespace
    {
        void addOption(const std::string &translationId,
                       std::function<bool(gui::Item &)> onClickCallback,
                       std::list<gui::Option> &options)
        {
            options.emplace_back(utils::localize.get(translationId), onClickCallback);
        }

        void removeNote(const NotesRecord &record, Application *application, AbstractNotesRepository &notesRepository)
        {
            gui::DialogMetadata meta;
            meta.action = [record, application, &notesRepository] {
                notesRepository.remove(
                    record, [application](bool) { application->switchWindow(gui::name::window::main_window); });
                return true;
            };
            meta.text = utils::localize.get("app_notes_note_delete_confirmation");
            meta.icon = "phonebook_contact_delete_trashcan";
            application->switchWindow(gui::name::window::note_confirm_dialog,
                                      std::make_unique<gui::DialogMetadataMessage>(meta));
        }
    } // namespace

    std::list<gui::Option> noteListOptions(Application *application,
                                           const NotesRecord &record,
                                           AbstractNotesRepository &notesRepository)
    {
        std::list<gui::Option> options;
        addOption(
            {"app_notes_delete_note"},
            [application, record, &notesRepository](gui::Item &item) {
                removeNote(record, application, notesRepository);
                return true;
            },
            options);
        return options;
    }

    std::list<gui::Option> notePreviewOptions(Application *application,
                                              const NotesRecord &record,
                                              AbstractNotesRepository &notesRepository,
                                              gui::Text *textWidget)
    {
        std::list<gui::Option> options;
        addOption(
            {"app_notes_copy_text"},
            [application, textWidget](gui::Item &item) {
                if (textWidget != nullptr) {
                    Clipboard::getInstance().copy(textWidget->getText());
                }
                application->returnToPreviousWindow();
                return true;
            },
            options);
        addOption(
            {"app_notes_delete_note"},
            [application, record, &notesRepository](gui::Item &item) {
                removeNote(record, application, notesRepository);
                return true;
            },
            options);
        return options;
    }

    std::list<gui::Option> noteEditOptions(Application *application, const NotesRecord &record, gui::Text *textWidget)
    {
        std::list<gui::Option> options;
        addOption(
            {"app_notes_copy_text"},
            [application, textWidget](gui::Item &item) {
                if (textWidget != nullptr) {
                    Clipboard::getInstance().copy(textWidget->getText());
                }
                application->returnToPreviousWindow();
                return true;
            },
            options);
        addOption(
            {"app_notes_copy_paste"},
            [application, textWidget](gui::Item &item) {
                if (textWidget != nullptr) {
                    textWidget->addText(Clipboard::getInstance().paste());
                }
                application->returnToPreviousWindow();
                return true;
            },
            options);
        return options;
    }
} // namespace app::notes

A module-apps/application-notes/windows/NotesOptions.hpp => module-apps/application-notes/windows/NotesOptions.hpp +25 -0
@@ 0,0 1,25 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <module-apps/application-notes/ApplicationNotes.hpp>
#include <module-db/Interface/NotesRecord.hpp>

namespace gui
{
    class Option; // Forward declaration.
    class Text;   // Forward declaration.
} // namespace gui

namespace app::notes
{
    std::list<gui::Option> noteListOptions(Application *application,
                                           const NotesRecord &record,
                                           AbstractNotesRepository &notesRepository);
    std::list<gui::Option> notePreviewOptions(Application *application,
                                              const NotesRecord &record,
                                              AbstractNotesRepository &notesRepository,
                                              gui::Text *textWidget);
    std::list<gui::Option> noteEditOptions(Application *application, const NotesRecord &record, gui::Text *textWidget);
} // namespace app::notes

M module-db/CMakeLists.txt => module-db/CMakeLists.txt +3 -0
@@ 87,6 87,9 @@ set(SOURCES
        queries/messages/threads/QueryThreadGetByContactID.cpp
        queries/messages/threads/QueryThreadRemove.cpp
        queries/messages/threads/QueryThreadMarkAsRead.cpp
        queries/notes/QueryNotesGet.cpp
        queries/notes/QueryNoteStore.cpp
        queries/notes/QueryNoteRemove.cpp
        queries/calllog/QueryCalllogSetAllRead.cpp
        queries/calllog/QueryCalllogGet.cpp
        queries/calllog/QueryCalllogGetCount.cpp

M module-db/Interface/NotesRecord.cpp => module-db/Interface/NotesRecord.cpp +104 -51
@@ 2,100 2,153 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotesRecord.hpp"
#include "../Tables/NotesTable.hpp"

NotesRecordInterface::NotesRecordInterface(NotesDB *notesDb) : notesDB(notesDb)
{}
#include "queries/notes/QueryNotesGet.hpp"
#include "queries/notes/QueryNoteStore.hpp"
#include "queries/notes/QueryNoteRemove.hpp"

NotesRecordInterface::~NotesRecordInterface()
{}
#include <cassert>

bool NotesRecordInterface::Add(const NotesRecord &rec)
namespace
{
    // Create SMS
    notesDB->notes.add(NotesTableRow{.date = rec.date, .snippet = rec.snippet, .path = rec.path});
    NotesTableFields toNotesTableFields(NotesRecordField field)
    {
        switch (field) {
        case NotesRecordField::Date:
            return NotesTableFields::Date;
        case NotesRecordField::Snippet:
            return NotesTableFields::Snippet;
        }
        throw std::invalid_argument{"Invalid notes record field passed."};
    }
} // namespace

    // TODO: error check
NotesRecordInterface::NotesRecordInterface(NotesDB *notesDb) : notesDB(notesDb)
{}

    return true;
bool NotesRecordInterface::Add(const NotesRecord &rec)
{
    return notesDB->notes.add(NotesTableRow{.date = rec.date, .snippet = rec.snippet});
}

uint32_t NotesRecordInterface::GetCount()
std::uint32_t NotesRecordInterface::GetCount()
{
    return notesDB->notes.count();
}

std::unique_ptr<std::vector<NotesRecord>> NotesRecordInterface::GetLimitOffsetByField(uint32_t offset,
                                                                                      uint32_t limit,
std::unique_ptr<std::vector<NotesRecord>> NotesRecordInterface::GetLimitOffsetByField(std::uint32_t offset,
                                                                                      std::uint32_t limit,
                                                                                      NotesRecordField field,
                                                                                      const char *str)
{
    return GetLimitOffset(offset, limit);
}

std::unique_ptr<std::vector<NotesRecord>> NotesRecordInterface::GetLimitOffset(uint32_t offset, uint32_t limit)
{
    auto notes = notesDB->notes.getLimitOffset(offset, limit);

    auto records = std::make_unique<std::vector<NotesRecord>>();
    //
    NotesRecordInterface notesInterface(notesDB);
    auto records     = std::make_unique<std::vector<NotesRecord>>();
    const auto notes = notesDB->notes.getLimitOffsetByField(offset, limit, toNotesTableFields(field), str);
    for (const auto &w : notes) {

        records->push_back({
            .ID      = w.ID,
            .date    = w.date,
            .snippet = w.snippet,
            .path    = w.path,
        });
        NotesRecord record{w.ID, w.date, w.snippet};
        records->push_back(std::move(record));
    }

    return records;
}

bool NotesRecordInterface::Update(const NotesRecord &rec)
std::unique_ptr<std::vector<NotesRecord>> NotesRecordInterface::GetLimitOffset(std::uint32_t offset,
                                                                               std::uint32_t limit)
{
    return std::make_unique<std::vector<NotesRecord>>(getNotes(offset, limit));
}

bool NotesRecordInterface::Update(const NotesRecord &rec)
{
    auto note = notesDB->notes.getById(rec.ID);
    if (note.ID == 0) {
    if (note.ID == DB_ID_NONE) {
        return false;
    }

    notesDB->notes.update(NotesTableRow{.ID = rec.ID, .date = rec.date, .snippet = rec.snippet, .path = rec.path});

    notesDB->notes.update(NotesTableRow{.ID = rec.ID, .date = rec.date, .snippet = rec.snippet});
    return true;
}

bool NotesRecordInterface::RemoveByID(uint32_t id)
bool NotesRecordInterface::RemoveAll()
{
    return notesDB->notes.removeAll();
}

    auto note = notesDB->notes.getById(id);
    if (note.ID == 0) {
        return false;
    }

    // Remove SMS
    if (notesDB->notes.removeById(id) == false) {
        return false;
    }

    return true;
bool NotesRecordInterface::RemoveByID(std::uint32_t id)
{
    return notesDB->notes.removeById(id);
}

bool NotesRecordInterface::RemoveByField(NotesRecordField field, const char *str)
{

    switch (field) {
    case NotesRecordField::Data:
    case NotesRecordField::Date:
        return notesDB->notes.removeByField(NotesTableFields::Date, str);
    default:
        return false;
    }
}

NotesRecord NotesRecordInterface::GetByID(uint32_t id)
NotesRecord NotesRecordInterface::GetByID(std::uint32_t id)
{
    auto note = notesDB->notes.getById(id);
    return NotesRecord{note.ID, note.date, note.snippet};
}

std::vector<NotesRecord> NotesRecordInterface::getNotes(std::uint32_t offset, std::uint32_t limit) const
{
    std::vector<NotesRecord> records;
    const auto notes = notesDB->notes.getLimitOffset(offset, limit);
    for (const auto &w : notes) {
        NotesRecord record{w.ID, w.date, w.snippet};
        records.push_back(std::move(record));
    }
    return records;
}

    return NotesRecord{.ID = note.ID, .date = note.date, .snippet = note.snippet, .path = note.path};
std::unique_ptr<db::QueryResult> NotesRecordInterface::runQuery(std::shared_ptr<db::Query> query)
{
    if (typeid(*query) == typeid(db::query::QueryNotesGet)) {
        return getQuery(query);
    }
    if (typeid(*query) == typeid(db::query::QueryNoteStore)) {
        return storeQuery(query);
    }
    if (typeid(*query) == typeid(db::query::QueryNoteRemove)) {
        return removeQuery(query);
    }
    return nullptr;
}

std::unique_ptr<db::QueryResult> NotesRecordInterface::getQuery(const std::shared_ptr<db::Query> &query)
{
    const auto localQuery = static_cast<db::query::QueryNotesGet *>(query.get());
    const auto &records   = getNotes(localQuery->getOffset(), localQuery->getLimit());
    auto response         = std::make_unique<db::query::NotesGetResult>(records, GetCount());
    response->setRequestQuery(query);
    return response;
}

std::unique_ptr<db::QueryResult> NotesRecordInterface::storeQuery(const std::shared_ptr<db::Query> &query)
{
    const auto localQuery = static_cast<db::query::QueryNoteStore *>(query.get());
    const auto &record    = localQuery->getRecord();
    bool isSuccess        = false;
    if (const auto exists = notesDB->notes.getById(record.ID).ID != DB_ID_NONE; exists) {
        isSuccess = Update(record);
    }
    else {
        isSuccess = Add(record);
    }

    auto response = std::make_unique<db::query::NoteStoreResult>(isSuccess);
    response->setRequestQuery(query);
    return response;
}

std::unique_ptr<db::QueryResult> NotesRecordInterface::removeQuery(const std::shared_ptr<db::Query> &query)
{
    const auto localQuery = static_cast<db::query::QueryNoteRemove *>(query.get());
    const auto isSuccess  = RemoveByID(localQuery->getRecordId());
    auto response         = std::make_unique<db::query::NoteRemoveResult>(isSuccess);
    response->setRequestQuery(query);
    return response;
}

M module-db/Interface/NotesRecord.hpp => module-db/Interface/NotesRecord.hpp +30 -28
@@ 4,51 4,53 @@
#pragma once

#include "Record.hpp"
#include <stdint.h>
#include "../Databases/NotesDB.hpp"
#include "utf8/UTF8.hpp"

#include <cstdint>
#include <filesystem>

#include <utf8/UTF8.hpp>

#include "module-db/Databases/NotesDB.hpp"
#include "../Common/Common.hpp"

struct NotesRecord
struct NotesRecord : public Record
{
    uint32_t ID;
    uint32_t date;
    std::uint32_t date;
    UTF8 snippet;
    UTF8 path;
};

enum class NotesRecordField
{
    Data,
    Date,
    Snippet,
    Path
};

/*
 *
 */
class NotesRecordInterface : public RecordInterface<NotesRecord, NotesRecordField>
{
  public:
    NotesRecordInterface(NotesDB *notesDb);
    virtual ~NotesRecordInterface();

    bool Add(const NotesRecord &rec) override final;
    bool RemoveByID(uint32_t id) override final;
    bool RemoveByField(NotesRecordField field, const char *str) override final;
    bool Update(const NotesRecord &rec) override final;
    NotesRecord GetByID(uint32_t id) override final;
    explicit NotesRecordInterface(NotesDB *notesDb);

    bool Add(const NotesRecord &rec) override;
    bool RemoveAll() override;
    bool RemoveByID(std::uint32_t id) override;
    bool RemoveByField(NotesRecordField field, const char *str) override;
    bool Update(const NotesRecord &rec) override;
    NotesRecord GetByID(std::uint32_t id) override;
    std::uint32_t GetCount() override;
    std::unique_ptr<std::vector<NotesRecord>> GetLimitOffset(std::uint32_t offset, std::uint32_t limit) override;
    std::unique_ptr<std::vector<NotesRecord>> GetLimitOffsetByField(std::uint32_t offset,
                                                                    std::uint32_t limit,
                                                                    NotesRecordField field,
                                                                    const char *str) override;

    uint32_t GetCount() override final;
    std::unique_ptr<db::QueryResult> runQuery(std::shared_ptr<db::Query> query) override;

    std::unique_ptr<std::vector<NotesRecord>> GetLimitOffset(uint32_t offset, uint32_t limit) override final;
  private:
    std::vector<NotesRecord> getNotes(std::uint32_t offset, std::uint32_t limit) const;

    std::unique_ptr<std::vector<NotesRecord>> GetLimitOffsetByField(uint32_t offset,
                                                                    uint32_t limit,
                                                                    NotesRecordField field,
                                                                    const char *str) override final;
    std::unique_ptr<db::QueryResult> getQuery(const std::shared_ptr<db::Query> &query);
    std::unique_ptr<db::QueryResult> storeQuery(const std::shared_ptr<db::Query> &query);
    std::unique_ptr<db::QueryResult> removeQuery(const std::shared_ptr<db::Query> &query);

  private:
    const uint32_t snippetLength = 60;
    NotesDB *notesDB;
};

M module-db/Interface/Record.hpp => module-db/Interface/Record.hpp +5 -0
@@ 23,6 23,11 @@ template <typename T, typename F> class RecordInterface : public db::Interface
        return true;
    };

    virtual bool RemoveAll()
    {
        return true;
    }

    virtual bool RemoveByID(uint32_t id)
    {
        return true;

M module-db/Tables/NotesTable.cpp => module-db/Tables/NotesTable.cpp +30 -56
@@ 6,9 6,6 @@
NotesTable::NotesTable(Database *db) : Table(db)
{}

NotesTable::~NotesTable()
{}

bool NotesTable::create()
{
    return true;


@@ 16,13 13,16 @@ bool NotesTable::create()

bool NotesTable::add(NotesTableRow entry)
{
    return db->execute("INSERT or ignore INTO notes ( date, snippet, path ) VALUES (%lu,'%q','%q');",
                       entry.date,
                       entry.snippet.c_str(),
                       entry.path.c_str());
    return db->execute(
        "INSERT or ignore INTO notes ( date, snippet ) VALUES ( %lu, '%q' );", entry.date, entry.snippet.c_str());
}

bool NotesTable::removeAll()
{
    return db->execute("DELETE FROM notes;");
}

bool NotesTable::removeById(uint32_t id)
bool NotesTable::removeById(std::uint32_t id)
{
    return db->execute("DELETE FROM notes where _id = %lu;", id);
}


@@ 30,79 30,61 @@ bool NotesTable::removeById(uint32_t id)
bool NotesTable::removeByField(NotesTableFields field, const char *str)
{
    std::string fieldName;

    switch (field) {
    case NotesTableFields::Date:
        fieldName = "date";
        break;

    case NotesTableFields::Path:
        fieldName = "path";
        break;

    case NotesTableFields::Snippet:
        fieldName = "snippet";
        break;
    default:
        return false;
    }

    return db->execute("DELETE FROM note where %q = '%q';", fieldName.c_str(), str);
}

bool NotesTable::update(NotesTableRow entry)
{
    return db->execute("UPDATE notes SET date = %lu, snippet = '%q', , snippet = '%q' WHERE _id=%lu;",
                       entry.date,
                       entry.snippet.c_str(),
                       entry.path.c_str(),
                       entry.ID);
    return db->execute(
        "UPDATE notes SET date = %lu, snippet = '%q' WHERE _id = %lu;", entry.date, entry.snippet.c_str(), entry.ID);
}

NotesTableRow NotesTable::getById(uint32_t id)
NotesTableRow NotesTable::getById(std::uint32_t id)
{
    auto retQuery = db->query("SELECT * FROM notes WHERE _id= %u;", id);

    auto retQuery = db->query("SELECT * FROM notes WHERE _id = %lu;", id);
    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
        return NotesTableRow();
    }

    return NotesTableRow{
        (*retQuery)[0].getUInt32(), // ID
        (*retQuery)[1].getUInt32(), // date
        (*retQuery)[2].getString(), // snippet
        (*retQuery)[3].getString(), // path
        (*retQuery)[2].getString()  // snippet
    };
}

std::vector<NotesTableRow> NotesTable::getLimitOffset(uint32_t offset, uint32_t limit)
std::vector<NotesTableRow> NotesTable::getLimitOffset(std::uint32_t offset, std::uint32_t limit)
{
    auto retQuery = db->query("SELECT * from notes ORDER BY date LIMIT %lu OFFSET %lu;", limit, offset);

    auto retQuery = db->query("SELECT * FROM notes ORDER BY date DESC LIMIT %lu OFFSET %lu;", limit, offset);
    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
        return std::vector<NotesTableRow>();
    }

    std::vector<NotesTableRow> ret;

    do {
        ret.push_back(NotesTableRow{
            (*retQuery)[0].getUInt32(), // ID
            (*retQuery)[1].getUInt32(), // date
            (*retQuery)[2].getString(), // snippet
            (*retQuery)[3].getString(), // path
            (*retQuery)[2].getString()  // snippet
        });
    } while (retQuery->nextRow());

    return ret;
}

std::vector<NotesTableRow> NotesTable::getLimitOffsetByField(uint32_t offset,
                                                             uint32_t limit,
std::vector<NotesTableRow> NotesTable::getLimitOffsetByField(std::uint32_t offset,
                                                             std::uint32_t limit,
                                                             NotesTableFields field,
                                                             const char *str)
{

    std::string fieldName;
    switch (field) {
    case NotesTableFields::Date:


@@ 111,52 93,44 @@ std::vector<NotesTableRow> NotesTable::getLimitOffsetByField(uint32_t offset,
    case NotesTableFields ::Snippet:
        fieldName = "snippet";
        break;
    case NotesTableFields ::Path:
        fieldName = "path";
        break;
    default:
        return std::vector<NotesTableRow>();
    }

    auto retQuery = db->query(
        "SELECT * from notes WHERE %q='%q' ORDER BY date LIMIT %lu OFFSET %lu;", fieldName.c_str(), str, limit, offset);

    auto retQuery = db->query("SELECT * FROM notes WHERE %q='%q' ORDER BY date DESC LIMIT %lu OFFSET %lu;",
                              fieldName.c_str(),
                              str,
                              limit,
                              offset);
    if ((retQuery == nullptr) || (retQuery->getRowCount() == 0)) {
        return std::vector<NotesTableRow>();
    }

    std::vector<NotesTableRow> ret;

    do {
        ret.push_back(NotesTableRow{
            (*retQuery)[0].getUInt32(), // ID
            (*retQuery)[1].getUInt32(), // date
            (*retQuery)[2].getString(), // snippet
            (*retQuery)[3].getString(), // path
            (*retQuery)[2].getString()  // snippet
        });
    } while (retQuery->nextRow());

    return ret;
}

uint32_t NotesTable::count()
std::uint32_t NotesTable::count()
{
    auto queryRet = db->query("SELECT COUNT(*) FROM NOTES;");

    auto queryRet = db->query("SELECT COUNT(*) FROM notes;");
    if (queryRet->getRowCount() == 0) {
        return 0;
    }

    return uint32_t{(*queryRet)[0].getUInt32()};
    return (*queryRet)[0].getUInt32();
}

uint32_t NotesTable::countByFieldId(const char *field, uint32_t id)
std::uint32_t NotesTable::countByFieldId(const char *field, std::uint32_t id)
{
    auto queryRet = db->query("SELECT COUNT(*) FROM notes WHERE %q=%lu;", field, id);

    auto queryRet = db->query("SELECT COUNT(*) FROM notes WHERE %q = %lu;", field, id);
    if ((queryRet == nullptr) || (queryRet->getRowCount() == 0)) {
        return 0;
    }

    return uint32_t{(*queryRet)[0].getUInt32()};
    return (*queryRet)[0].getUInt32();
}

M module-db/Tables/NotesTable.hpp => module-db/Tables/NotesTable.hpp +20 -23
@@ 3,6 3,8 @@

#pragma once

#include <cstdint>

#include "Table.hpp"
#include "Record.hpp"
#include "Database/Database.hpp"


@@ 11,40 13,35 @@

struct NotesTableRow
{
    uint32_t ID = DB_ID_NONE;
    uint32_t date;
    UTF8 snippet; // up to 60 characters
    UTF8 path;
    std::uint32_t ID = DB_ID_NONE;
    std::uint32_t date;
    UTF8 snippet;
};

enum class NotesTableFields
{
    Date,
    Snippet,
    Path
};

/*
 *
 */
class NotesTable : public Table<NotesTableRow, NotesTableFields>
{
  public:
    NotesTable(Database *db);
    virtual ~NotesTable();

    bool create() override final;
    bool add(NotesTableRow entry) override final;
    bool removeById(uint32_t id) override final;
    bool removeByField(NotesTableFields field, const char *str) override final;
    bool update(NotesTableRow entry) override final;
    NotesTableRow getById(uint32_t id) override final;
    std::vector<NotesTableRow> getLimitOffset(uint32_t offset, uint32_t limit) override final;
    std::vector<NotesTableRow> getLimitOffsetByField(uint32_t offset,
                                                     uint32_t limit,
    explicit NotesTable(Database *db);

    bool create() override;
    bool add(NotesTableRow entry) override;
    bool removeAll() override;
    bool removeById(std::uint32_t id) override;
    bool removeByField(NotesTableFields field, const char *str) override;
    bool update(NotesTableRow entry) override;
    NotesTableRow getById(std::uint32_t id) override;
    std::vector<NotesTableRow> getLimitOffset(std::uint32_t offset, std::uint32_t limit) override;
    std::vector<NotesTableRow> getLimitOffsetByField(std::uint32_t offset,
                                                     std::uint32_t limit,
                                                     NotesTableFields field,
                                                     const char *str) override final;
                                                     const char *str) override;

    uint32_t count() override final;
    uint32_t countByFieldId(const char *field, uint32_t id) override final;
    std::uint32_t count() override;
    std::uint32_t countByFieldId(const char *field, std::uint32_t id) override;
};

M module-db/Tables/Table.hpp => module-db/Tables/Table.hpp +6 -2
@@ 17,8 17,12 @@ template <typename T, typename F> class Table
    virtual ~Table()
    {}

    virtual bool create()                = 0;
    virtual bool add(T entry)            = 0;
    virtual bool create()     = 0;
    virtual bool add(T entry) = 0;
    virtual bool removeAll()
    {
        return false;
    }
    virtual bool removeById(uint32_t id) = 0;
    virtual bool removeByField(F field, const char *str)
    {

A module-db/queries/notes/QueryNoteRemove.cpp => module-db/queries/notes/QueryNoteRemove.cpp +33 -0
@@ 0,0 1,33 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryNoteRemove.hpp"

namespace db::query
{
    QueryNoteRemove::QueryNoteRemove(unsigned int recordId) : Query(Query::Type::Delete), recordId{recordId}
    {}

    unsigned int QueryNoteRemove::getRecordId() const noexcept
    {
        return recordId;
    }

    std::string QueryNoteRemove::debugInfo() const
    {
        return {"QueryNoteRemove"};
    }

    NoteRemoveResult::NoteRemoveResult(bool isSuccess) : isSuccess{isSuccess}
    {}

    bool NoteRemoveResult::succeed() const noexcept
    {
        return isSuccess;
    }

    auto NoteRemoveResult::debugInfo() const -> std::string
    {
        return {"NoteRemoveResult"};
    }
} // namespace db::query

A module-db/queries/notes/QueryNoteRemove.hpp => module-db/queries/notes/QueryNoteRemove.hpp +35 -0
@@ 0,0 1,35 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <string>

#include <Common/Query.hpp>
#include "Interface/NotesRecord.hpp"

namespace db::query
{
    class QueryNoteRemove : public Query
    {
      public:
        explicit QueryNoteRemove(unsigned int recordId);
        [[nodiscard]] unsigned int getRecordId() const noexcept;
        [[nodiscard]] std::string debugInfo() const override;

      private:
        unsigned int recordId;
    };

    class NoteRemoveResult : public QueryResult
    {
      public:
        explicit NoteRemoveResult(bool isSuccess);

        [[nodiscard]] bool succeed() const noexcept;
        [[nodiscard]] std::string debugInfo() const override;

      private:
        bool isSuccess;
    };
} // namespace db::query

A module-db/queries/notes/QueryNoteStore.cpp => module-db/queries/notes/QueryNoteStore.cpp +33 -0
@@ 0,0 1,33 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryNoteStore.hpp"

namespace db::query
{
    QueryNoteStore::QueryNoteStore(NotesRecord record) : Query(Query::Type::Create), record{std::move(record)}
    {}

    const NotesRecord &QueryNoteStore::getRecord() const noexcept
    {
        return record;
    }

    std::string QueryNoteStore::debugInfo() const
    {
        return {"QueryNoteStore"};
    }

    NoteStoreResult::NoteStoreResult(bool isSuccess) : isSuccess{isSuccess}
    {}

    bool NoteStoreResult::succeed() const noexcept
    {
        return isSuccess;
    }

    auto NoteStoreResult::debugInfo() const -> std::string
    {
        return {"NoteStoreResult"};
    }
} // namespace db::query

A module-db/queries/notes/QueryNoteStore.hpp => module-db/queries/notes/QueryNoteStore.hpp +36 -0
@@ 0,0 1,36 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <string>

#include <Common/Query.hpp>
#include "Interface/NotesRecord.hpp"

namespace db::query
{
    class QueryNoteStore : public Query
    {
      public:
        explicit QueryNoteStore(NotesRecord record);

        [[nodiscard]] const NotesRecord &getRecord() const noexcept;
        [[nodiscard]] std::string debugInfo() const override;

      private:
        NotesRecord record;
    };

    class NoteStoreResult : public QueryResult
    {
      public:
        explicit NoteStoreResult(bool isSuccess);

        [[nodiscard]] bool succeed() const noexcept;
        [[nodiscard]] std::string debugInfo() const override;

      private:
        bool isSuccess;
    };
} // namespace db::query

A module-db/queries/notes/QueryNotesGet.cpp => module-db/queries/notes/QueryNotesGet.cpp +45 -0
@@ 0,0 1,45 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryNotesGet.hpp"

namespace db::query
{
    QueryNotesGet::QueryNotesGet(unsigned int offset, unsigned int limit)
        : Query(Query::Type::Read), offset{offset}, limit{limit}
    {}

    unsigned int QueryNotesGet::getOffset() const noexcept
    {
        return offset;
    }

    unsigned int QueryNotesGet::getLimit() const noexcept
    {
        return limit;
    }

    std::string QueryNotesGet::debugInfo() const
    {
        return {"QueryNotesGet"};
    }

    NotesGetResult::NotesGetResult(std::vector<NotesRecord> notes, unsigned int dbRecordsCount)
        : records{std::move(notes)}, dbRecordsCount{dbRecordsCount}
    {}

    const std::vector<NotesRecord> &NotesGetResult::getRecords() const noexcept
    {
        return records;
    }

    unsigned int NotesGetResult::getCount() const noexcept
    {
        return dbRecordsCount;
    }

    auto NotesGetResult::debugInfo() const -> std::string
    {
        return {"NotesGetResult"};
    }
} // namespace db::query

A module-db/queries/notes/QueryNotesGet.hpp => module-db/queries/notes/QueryNotesGet.hpp +40 -0
@@ 0,0 1,40 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <string>

#include <Common/Query.hpp>
#include "Interface/NotesRecord.hpp"

namespace db::query
{
    class QueryNotesGet : public Query
    {
      public:
        explicit QueryNotesGet(unsigned int offset = 0U, unsigned int limit = 0U);

        [[nodiscard]] unsigned int getOffset() const noexcept;
        [[nodiscard]] unsigned int getLimit() const noexcept;
        [[nodiscard]] std::string debugInfo() const override;

      private:
        unsigned int offset;
        unsigned int limit;
    };

    class NotesGetResult : public QueryResult
    {
      public:
        NotesGetResult(std::vector<NotesRecord> notes, unsigned int dbRecordsCount);

        [[nodiscard]] const std::vector<NotesRecord> &getRecords() const noexcept;
        [[nodiscard]] unsigned int getCount() const noexcept;
        [[nodiscard]] std::string debugInfo() const override;

      private:
        std::vector<NotesRecord> records;
        unsigned int dbRecordsCount;
    };
} // namespace db::query

M module-db/tests/CMakeLists.txt => module-db/tests/CMakeLists.txt +2 -1
@@ 22,7 22,8 @@ add_catch2_executable(
        "${CMAKE_CURRENT_SOURCE_DIR}/CalllogTable_tests.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/NotificationsTable_tests.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/EventsTable_tests.cpp"

        "${CMAKE_CURRENT_SOURCE_DIR}/NotesRecord_tests.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/NotesTable_tests.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/CalllogRecord_tests.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/ContactsRecord_tests.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/SMSRecord_tests.cpp"

A module-db/tests/NotesRecord_tests.cpp => module-db/tests/NotesRecord_tests.cpp +67 -0
@@ 0,0 1,67 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include <Interface/NotesRecord.hpp>
#include <queries/notes/QueryNotesGet.hpp>
#include <queries/notes/QueryNoteRemove.hpp>
#include <queries/notes/QueryNoteStore.hpp>

#include "Database/Database.hpp"
#include "Databases/NotesDB.hpp"

TEST_CASE("Notes Record tests")
{
    Database::initialize();

    auto notesDb = std::make_unique<NotesDB>();
    REQUIRE(notesDb->isInitialized());

    NotesRecordInterface notesRecordInterface{notesDb.get()};
    notesRecordInterface.RemoveAll(); // Empty the notes database.

    constexpr auto testSnippet = "TEST SNIPPET";

    NotesRecord recordIn;
    recordIn.snippet = testSnippet;

    notesRecordInterface.Add(recordIn);
    REQUIRE(notesRecordInterface.GetCount() == 1);

    SECTION("Get notes query")
    {
        auto query     = std::make_unique<db::query::QueryNotesGet>(0, notesRecordInterface.GetCount());
        auto response  = notesRecordInterface.runQuery(std::move(query));
        auto getResult = static_cast<db::query::NotesGetResult *>(response.get());

        REQUIRE(getResult->getRecords().size() == notesRecordInterface.GetCount());
        REQUIRE(getResult->getCount() == notesRecordInterface.GetCount());
        REQUIRE(getResult->getRecords()[0].snippet == testSnippet);
    }

    SECTION("Add a note")
    {
        NotesRecord record;
        record.snippet = testSnippet;

        auto query     = std::make_unique<db::query::QueryNoteStore>(record);
        auto response  = notesRecordInterface.runQuery(std::move(query));
        auto addResult = static_cast<db::query::NoteStoreResult *>(response.get());

        REQUIRE(addResult->succeed());
        REQUIRE(notesRecordInterface.GetCount() == 2);
    }

    SECTION("Remove a note")
    {
        auto query        = std::make_unique<db::query::QueryNoteRemove>(1);
        auto response     = notesRecordInterface.runQuery(std::move(query));
        auto removeResult = static_cast<db::query::NoteRemoveResult *>(response.get());

        REQUIRE(removeResult->succeed());
        REQUIRE(notesRecordInterface.GetCount() == 0);
    }

    Database::deinitialize();
};

A module-db/tests/NotesTable_tests.cpp => module-db/tests/NotesTable_tests.cpp +65 -0
@@ 0,0 1,65 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include <catch2/catch.hpp>

#include <Tables/NotesTable.hpp>
#include "Database/Database.hpp"
#include "Databases/NotesDB.hpp"

TEST_CASE("Notes Table tests")
{
    Database::initialize();

    auto notesDb = std::make_unique<NotesDB>();
    REQUIRE(notesDb->isInitialized());

    NotesTable table{notesDb.get()};
    table.removeAll();
    REQUIRE(table.count() == 0);

    constexpr auto testSnippet = "TEST SNIPPET";

    NotesTableRow rowIn;
    rowIn.snippet = testSnippet;

    table.add(rowIn);
    REQUIRE(table.count() == 1);

    SECTION("Get notes")
    {
        const auto &records = table.getLimitOffset(0, table.count());
        REQUIRE(records.size() == table.count());
        REQUIRE(records[0].snippet == testSnippet);
    }

    SECTION("Add a note")
    {
        NotesTableRow row;
        row.snippet = testSnippet;
        table.add(row);
        REQUIRE(table.count() == 2);
    }

    SECTION("Update a note")
    {
        constexpr auto testId             = 1;
        constexpr auto testSnippetUpdated = "UPDATED TEST SNIPPET";
        NotesTableRow row;
        row.ID      = testId;
        row.snippet = testSnippetUpdated;
        table.update(row);
        REQUIRE(table.count() == 1);

        const auto &record = table.getById(testId);
        REQUIRE(record.snippet == testSnippetUpdated);
    }

    SECTION("Remove a note")
    {
        table.removeById(1);
        REQUIRE(table.count() == 0);
    }

    Database::deinitialize();
}
\ No newline at end of file