~aleteoryx/muditaos

46c57aaa889c92a47a4ac2d82aace687a9dae3dd — Michał Kamoń 4 years ago a3aa441
[EGD-6599] Add single number call notification

This PR adds proper notification on call notifications coming from
single number. To that end following changes have been introduced:
 * extension of Notification DB record with contact_id filed
 * use of `ContactRecordInterface` in `NotificationsRecordInterface`
 * extension of `Increment` query to require `PhoneNumber::View`
 * multiple minor changes on path from creating/handling
 `NotificationsRecord` to displaying respective notification.
33 files changed, 378 insertions(+), 187 deletions(-)

M enabled_unittests
M image/user/db/notifications_001.sql
M image/user/db/notifications_002.sql
M module-apps/application-desktop/models/ActiveNotificationsModel.cpp
M module-apps/application-desktop/windows/DesktopMainWindow.cpp
M module-apps/application-desktop/windows/DesktopMainWindow.hpp
M module-apps/notifications/NotificationData.cpp
M module-apps/notifications/NotificationData.hpp
M module-apps/notifications/NotificationListItem.cpp
M module-apps/notifications/NotificationProvider.cpp
M module-apps/notifications/NotificationProvider.hpp
M module-apps/notifications/NotificationsModel.cpp
M module-db/Databases/NotificationsDB.hpp
M module-db/Interface/NotificationsRecord.cpp
M module-db/Interface/NotificationsRecord.hpp
M module-db/Tables/NotificationsTable.cpp
M module-db/Tables/NotificationsTable.hpp
M module-db/queries/calllog/QueryCalllogSetAllRead.hpp
M module-db/queries/notifications/QueryNotificationsClear.cpp
M module-db/queries/notifications/QueryNotificationsClear.hpp
M module-db/queries/notifications/QueryNotificationsGet.cpp
M module-db/queries/notifications/QueryNotificationsGet.hpp
M module-db/queries/notifications/QueryNotificationsGetAll.hpp
M module-db/queries/notifications/QueryNotificationsIncrement.cpp
M module-db/queries/notifications/QueryNotificationsIncrement.hpp
M module-db/tests/CMakeLists.txt
M module-db/tests/NotificationsRecord_tests.cpp
M module-db/tests/NotificationsTable_tests.cpp
M module-gui/gui/widgets/Style.hpp
M module-services/service-cellular/ServiceCellular.cpp
M module-services/service-cellular/service-cellular/ServiceCellular.hpp
M module-services/service-db/ServiceDB.cpp
M module-services/service-db/ServiceDB.hpp
M enabled_unittests => enabled_unittests +2 -0
@@ 130,6 130,8 @@ TESTS_LIST["catch2-db"]="
    SMS Templates Table tests;
    Thread Record tests;
    Threads Table tests;
    Notifications Table tests;
    Notifications Record tests;
"
#---------
TESTS_LIST["catch2-db-initializer"]="

M image/user/db/notifications_001.sql => image/user/db/notifications_001.sql +2 -1
@@ 4,5 4,6 @@
CREATE TABLE IF NOT EXISTS notifications(
    _id INTEGER PRIMARY KEY,
    key INTEGER UNIQUE DEFAULT 0,
    value INTEGER DEFAULT 0
    value INTEGER DEFAULT 0,
    contact_id INTEGER DEFAULT 0
);

M image/user/db/notifications_002.sql => image/user/db/notifications_002.sql +3 -4
@@ 1,7 1,6 @@
-- Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
-- For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

INSERT OR IGNORE INTO notifications (key, value) VALUES
    ('1', '0'),
    ('2', '0');
 
INSERT OR IGNORE INTO notifications (key, value, contact_id) VALUES
    ('1', '0', '0'),
    ('2', '0', '0');

M module-apps/application-desktop/models/ActiveNotificationsModel.cpp => module-apps/application-desktop/models/ActiveNotificationsModel.cpp +2 -2
@@ 63,8 63,8 @@ auto ActiveNotificationsModel::create(const notifications::NotSeenCallNotificati
    std::function<void()> onKeyLeftFunctionalCallback = nullptr;

    if (notification->hasRecord()) {
        if (const auto &record = notification->getRecord(); !record->numbers.empty()) {
            onKeyLeftFunctionalCallback = [this, number = record->numbers[0].number]() {
        if (const auto &record = notification->getRecord(); !record.numbers.empty()) {
            onKeyLeftFunctionalCallback = [this, number = record.numbers[0].number]() {
                app::manager::Controller::sendAction(parent->getApplication(),
                                                     app::manager::actions::Dial,
                                                     std::make_unique<app::ExecuteCallData>(number));

M module-apps/application-desktop/windows/DesktopMainWindow.cpp => module-apps/application-desktop/windows/DesktopMainWindow.cpp +1 -1
@@ 12,6 12,7 @@
#include <service-appmgr/Controller.hpp>
#include <service-time/ServiceTime.hpp>
#include <service-time/TimeMessage.hpp>
#include <notifications/NotificationsModel.hpp>

#include <log/log.hpp>



@@ 136,7 137,6 @@ namespace gui
            return app::manager::Controller::sendAction(
                application, app::manager::actions::Dial, std::make_unique<app::EnterNumberData>("+"));
        }

        if (inputEvent.is(KeyCode::KEY_RF)) {
            application->switchWindow(popup::window::power_off_window);
            return true;

M module-apps/application-desktop/windows/DesktopMainWindow.hpp => module-apps/application-desktop/windows/DesktopMainWindow.hpp +2 -0
@@ 17,6 17,8 @@ namespace app

namespace gui
{
    class NotificationsModel;

    class DesktopMainWindow : public AppWindow
    {
      protected:

M module-apps/notifications/NotificationData.cpp => module-apps/notifications/NotificationData.cpp +19 -17
@@ 2,7 2,7 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotificationData.hpp"

#include <gsl_assert>
uint32_t notifications::Notification::priorityPool = 0;

using namespace notifications;


@@ 35,33 35,35 @@ auto Notification::getPriority() const noexcept -> uint32_t
    return priority;
}

NotSeenSMSNotification::NotSeenSMSNotification(unsigned value)
    : Notification(NotificationType::NotSeenSms), value{value}
NotificationWithContact::NotificationWithContact(NotificationType type,
                                                 unsigned value,
                                                 std::optional<ContactRecord> record)
    : Notification(type), value{value}, record{std::move(record)}
{}

auto NotSeenSMSNotification::getValue() const noexcept -> unsigned
auto NotificationWithContact::hasRecord() const noexcept -> bool
{
    return value;
    return record.has_value();
}

NotSeenCallNotification::NotSeenCallNotification(unsigned value, std::unique_ptr<ContactRecord> record)
    : Notification(NotificationType::NotSeenCall), value{value}, record{std::move(record)}
{}

bool NotSeenCallNotification::hasRecord() const noexcept
auto NotificationWithContact::getRecord() const noexcept -> const ContactRecord &
{
    return record != nullptr;
    Expects(hasRecord());
    return record.value();
}

auto NotSeenCallNotification::getRecord() const noexcept -> const std::unique_ptr<ContactRecord> &
{
    return record;
}

auto NotSeenCallNotification::getValue() const noexcept -> unsigned
auto NotificationWithContact::getValue() const noexcept -> unsigned
{
    return value;
}

NotSeenSMSNotification::NotSeenSMSNotification(unsigned value, std::optional<ContactRecord> record)
    : NotificationWithContact(NotificationType::NotSeenSms, value, std::move(record))
{}

NotSeenCallNotification::NotSeenCallNotification(unsigned value, std::optional<ContactRecord> record)
    : NotificationWithContact(NotificationType::NotSeenCall, value, std::move(record))
{}

TetheringNotification::TetheringNotification() : Notification(NotificationType::Tethering)
{}

M module-apps/notifications/NotificationData.hpp => module-apps/notifications/NotificationData.hpp +14 -10
@@ 44,26 44,30 @@ namespace notifications
        virtual ~Notification() = default;
    };

    class NotSeenSMSNotification : public Notification
    class NotificationWithContact : public Notification
    {
        unsigned value = 0;
        std::optional<ContactRecord> record;

      protected:
        NotificationWithContact(NotificationType type, unsigned value, std::optional<ContactRecord> record);

      public:
        explicit NotSeenSMSNotification(unsigned value);
        [[nodiscard]] auto hasRecord() const noexcept -> bool;
        [[nodiscard]] auto getRecord() const noexcept -> const ContactRecord &;
        [[nodiscard]] auto getValue() const noexcept -> unsigned;
    };

    class NotSeenCallNotification : public Notification
    class NotSeenSMSNotification : public NotificationWithContact
    {
        unsigned value = 0;
        std::unique_ptr<ContactRecord> record;

      public:
        explicit NotSeenCallNotification(unsigned value, std::unique_ptr<ContactRecord> record = nullptr);
        NotSeenSMSNotification(unsigned value, std::optional<ContactRecord> record);
    };

        [[nodiscard]] bool hasRecord() const noexcept;
        [[nodiscard]] auto getRecord() const noexcept -> const std::unique_ptr<ContactRecord> &;
        [[nodiscard]] auto getValue() const noexcept -> unsigned;
    class NotSeenCallNotification : public NotificationWithContact
    {
      public:
        NotSeenCallNotification(unsigned value, std::optional<ContactRecord> record);
    };

    class TetheringNotification : public Notification

M module-apps/notifications/NotificationListItem.cpp => module-apps/notifications/NotificationListItem.cpp +4 -3
@@ 12,7 12,6 @@
#include <map>

using namespace gui;
// using namespace style::desktop;

namespace
{


@@ 36,14 35,15 @@ namespace
    auto buildNotificationNameLabel(uint32_t width) -> gui::TextFixedSize *
    {
        auto text =
            new gui::TextFixedSize(nullptr, 0, 0, style::notifications::textMaxWidth, style::notifications::itemHeight);
        text->setMaximumSize(width, Axis::X);
            new gui::TextFixedSize(nullptr, 0, 0, style::notifications::textMinWidth, style::notifications::itemHeight);

        text->setMaximumSize(style::notifications::textMaxWidth, Axis::X);
        text->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        text->setPenWidth(style::window::default_border_no_focus_w);
        text->setUnderline(false);
        text->setFont(style::window::font::medium);
        text->activeItem = false;
        text->setTextLimitType(TextLimitType::MaxLines, 1);
        return text;
    }



@@ 135,6 135,7 @@ NotificationWithEventCounter::NotificationWithEventCounter(notifications::Notifi
{
    box->addWidget(buildImageInactive("dot_12px_hard_alpha_W_G"));
    box->addWidget(buildNotificationCountText(indicator));
    text->setMaximumSize(text->getSize(Axis::X), Axis::X);
}

NotificationWithOnOffButton::NotificationWithOnOffButton(notifications::NotificationType type, gui::ButtonState state)

M module-apps/notifications/NotificationProvider.cpp => module-apps/notifications/NotificationProvider.cpp +9 -6
@@ 7,26 7,29 @@
#include <service-appmgr/Controller.hpp>
#include <service-appmgr/data/NotificationsChangedActionsParams.hpp>
#include <service-db/DBNotificationMessage.hpp>
#include <NotificationsRecord.hpp>

using namespace notifications;

NotificationProvider::NotificationProvider(sys::Service *ownerService) : ownerService{ownerService}
{}

template <NotificationType type, typename T> bool NotificationProvider::handleNotSeenWithCounter(unsigned int value)
template <NotificationType type, typename T>
bool NotificationProvider::handleNotSeenWithCounter(NotificationsRecord &&record)
{
    auto value = record.value;
    if (notifications.count(type) > 0) {
        if (value == 0) {
            notifications.erase(type);
            return true;
        }
        if (auto notification = static_cast<T *>(notifications[type].get()); value > notification->getValue()) {
            notifications[type] = std::make_shared<T>(value);
            notifications[type] = std::make_shared<T>(value, std::move(record.contactRecord));
            return true;
        }
    }
    else if (value > 0) {
        notifications[type] = std::make_shared<T>(value);
        notifications[type] = std::make_shared<T>(value, std::move(record.contactRecord));
        return true;
    }
    return false;


@@ 38,15 41,15 @@ void NotificationProvider::handle(db::query::notifications::GetAllResult *msg)
    auto records = *msg->getResult();

    bool notificationsChanged = false;
    for (auto record : records) {
    for (auto &&record : records) {
        switch (record.key) {
        case NotificationsRecord::Key::Calls:
            notificationsChanged |=
                handleNotSeenWithCounter<NotificationType::NotSeenCall, NotSeenCallNotification>(record.value);
                handleNotSeenWithCounter<NotificationType::NotSeenCall, NotSeenCallNotification>(std::move(record));
            break;
        case NotificationsRecord::Key::Sms:
            notificationsChanged |=
                handleNotSeenWithCounter<NotificationType::NotSeenSms, NotSeenSMSNotification>(record.value);
                handleNotSeenWithCounter<NotificationType::NotSeenSms, NotSeenSMSNotification>(std::move(record));
            break;
        default:
            break;

M module-apps/notifications/NotificationProvider.hpp => module-apps/notifications/NotificationProvider.hpp +3 -1
@@ 11,6 11,8 @@ namespace sys
    class Service;
}

class NotificationsRecord;

namespace db
{
    class NotificationMessage;


@@ 25,7 27,7 @@ namespace notifications

    class NotificationProvider
    {
        template <NotificationType type, typename T> bool handleNotSeenWithCounter(unsigned int value);
        template <NotificationType type, typename T> bool handleNotSeenWithCounter(NotificationsRecord &&record);

      public:
        explicit NotificationProvider(sys::Service *ownerService);

M module-apps/notifications/NotificationsModel.cpp => module-apps/notifications/NotificationsModel.cpp +1 -1
@@ 65,7 65,7 @@ auto NotificationsModel::create(const notifications::NotSeenCallNotification *no
                                                 utils::to_string(notification->getValue()));
    if (notification->hasRecord()) {
        const auto &record = notification->getRecord();
        item->setName(record->getFormattedName());
        item->setName(record.getFormattedName());
    }
    else {
        item->setName(utils::translate("app_desktop_missed_calls"), true);

M module-db/Databases/NotificationsDB.hpp => module-db/Databases/NotificationsDB.hpp +2 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once


@@ 9,7 9,7 @@
class NotificationsDB : public Database
{
  public:
    NotificationsDB(const char *name);
    explicit NotificationsDB(const char *name);
    virtual ~NotificationsDB() = default;

    NotificationsTable notifications;

M module-db/Interface/NotificationsRecord.cpp => module-db/Interface/NotificationsRecord.cpp +51 -32
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotificationsRecord.hpp"


@@ 6,15 6,17 @@
#include "module-db/queries/notifications/QueryNotificationsIncrement.hpp"
#include "module-db/queries/notifications/QueryNotificationsClear.hpp"
#include "module-db/queries/notifications/QueryNotificationsGetAll.hpp"
#include "Databases/NotificationsDB.hpp"

#include <log/log.hpp>
#include <Utils.hpp>

#include <cassert>
#include <vector>
#include <gsl_assert>

NotificationsRecord::NotificationsRecord(const NotificationsTableRow &tableRow)
    : Record{tableRow.ID}, value{tableRow.value}
NotificationsRecord::NotificationsRecord(const NotificationsTableRow &tableRow, std::optional<ContactRecord> record)
    : Record{tableRow.ID}, value{tableRow.value}, contactRecord{std::move(record)}
{
    if (tableRow.key > static_cast<uint32_t>(Key::NotValidKey) &&
        tableRow.key < static_cast<uint32_t>(Key::NumberOfKeys)) {


@@ 26,16 28,6 @@ NotificationsRecord::NotificationsRecord(const NotificationsTableRow &tableRow)
    ID  = DB_ID_NONE;
}

bool NotificationsRecord::isValidRecord() const
{
    return isValid() && gotValidKey();
}

bool NotificationsRecord::gotValidKey() const
{
    return isValidKey(key);
}

bool NotificationsRecord::isValidKey(Key key)
{
    return key != Key::NotValidKey && key != Key::NumberOfKeys;


@@ 44,13 36,18 @@ bool NotificationsRecord::isValidKey(Key key)
std::ostream &operator<<(std::ostream &out, const NotificationsRecord &rec)
{
    out << " <id> " << rec.ID << " <key> " << static_cast<int>(rec.key) << " <value> " << rec.value;

    if (rec.contactRecord.has_value()) {
        out << " <contact_id> " << rec.contactRecord.value().ID;
    }
    return out;
}

NotificationsRecordInterface::NotificationsRecordInterface(NotificationsDB *notificationsDb)
    : notificationsDb(notificationsDb)
{}
NotificationsRecordInterface::NotificationsRecordInterface(NotificationsDB *notificationsDb,
                                                           ContactRecordInterface *contactsDb)
    : notificationsDb(notificationsDb), contactsDb(contactsDb)
{
    Expects(contactsDb != nullptr);
}

bool NotificationsRecordInterface::Add(const NotificationsRecord &rec)
{


@@ 79,7 76,7 @@ std::unique_ptr<std::vector<NotificationsRecord>> NotificationsRecordInterface::
    auto records = std::make_unique<std::vector<NotificationsRecord>>();

    for (auto &r : rows) {
        records->push_back(NotificationsRecord{r});
        records->push_back(NotificationsRecord{r, getContactRecord(r.contactID)});
    }

    return records;


@@ 92,8 89,10 @@ bool NotificationsRecordInterface::Update(const NotificationsRecord &rec)
        return false;
    }

    return notificationsDb->notifications.update(
        NotificationsTableRow{{.ID = rec.ID}, .key = static_cast<uint32_t>(rec.key), .value = rec.value});
    uint32_t contactId = rec.contactRecord.has_value() ? rec.contactRecord.value().ID : DB_ID_NONE;

    return notificationsDb->notifications.update(NotificationsTableRow{
        {.ID = rec.ID}, .key = static_cast<uint32_t>(rec.key), .value = rec.value, .contactID = contactId});
}

bool NotificationsRecordInterface::RemoveByID(uint32_t id)


@@ 109,10 108,20 @@ bool NotificationsRecordInterface::RemoveByField(NotificationsRecordField field,

    return false;
}
std::optional<ContactRecord> NotificationsRecordInterface::getContactRecord(uint32_t id) const
{
    if (id != DB_ID_NONE) {
        if (auto contactRecord = contactsDb->GetByIdWithTemporary(id); contactRecord.isValid()) {
            return std::make_optional(std::move(contactRecord));
        }
    }
    return std::nullopt;
}

NotificationsRecord NotificationsRecordInterface::GetByID(uint32_t id)
{
    return NotificationsRecord{notificationsDb->notifications.getById(id)};
    auto tableRow = notificationsDb->notifications.getById(id);
    return NotificationsRecord{tableRow, getContactRecord(tableRow.contactID)};
}

uint32_t NotificationsRecordInterface::GetCount()


@@ 126,8 135,8 @@ NotificationsRecord NotificationsRecordInterface::GetByKey(NotificationsRecord::
        return NotificationsRecord();
    }

    NotificationsTableRow notificationsTableRow = notificationsDb->notifications.GetByKey(static_cast<uint32_t>(key));
    return NotificationsRecord{notificationsTableRow};
    auto tableRow = notificationsDb->notifications.getByKey(static_cast<uint32_t>(key));
    return NotificationsRecord{tableRow, getContactRecord(tableRow.contactID)};
}

std::unique_ptr<db::QueryResult> NotificationsRecordInterface::runQuery(std::shared_ptr<db::Query> query)


@@ 150,17 159,28 @@ std::unique_ptr<db::QueryResult> NotificationsRecordInterface::runQuery(std::sha
std::unique_ptr<db::query::notifications::GetResult> NotificationsRecordInterface::runQueryImpl(
    const db::query::notifications::Get *query)
{
    auto value = GetByKey(query->key);
    return std::make_unique<db::query::notifications::GetResult>(value);
    auto value = GetByKey(query->getKey());
    return std::make_unique<db::query::notifications::GetResult>(std::move(value));
}

std::unique_ptr<db::query::notifications::IncrementResult> NotificationsRecordInterface::runQueryImpl(
    const db::query::notifications::Increment *query)
{
    auto ret = false;

    auto record = GetByKey(query->key);
    if (record.isValid() && record.key == query->key) {
    if (auto record = GetByKey(query->getKey()); record.isValid()) {
        auto &currentContactRecord = record.contactRecord;
        if (auto numberMatch = contactsDb->MatchByNumber(query->getNumber()); numberMatch.has_value()) {
            if (record.value == 0) {
                currentContactRecord = std::move(numberMatch.value().contact);
            }
            else if (currentContactRecord.has_value() &&
                     numberMatch.value().contactId != currentContactRecord.value().ID) {
                currentContactRecord.reset();
            }
        }
        else {
            currentContactRecord.reset();
        }
        record.value++;
        ret = Update(record);
    }


@@ 171,11 191,10 @@ std::unique_ptr<db::query::notifications::ClearResult> NotificationsRecordInterf
    const db::query::notifications::Clear *query)
{
    auto ret = false;

    auto record = GetByKey(query->key);
    if (record.isValid() && record.key == query->key) {
    if (auto record = GetByKey(query->getKey()); record.isValid()) {
        record.value = 0;
        ret          = Update(record);
        record.contactRecord.reset();
        ret = Update(record);
    }
    return std::make_unique<db::query::notifications::ClearResult>(ret);
}

M module-db/Interface/NotificationsRecord.hpp => module-db/Interface/NotificationsRecord.hpp +12 -10
@@ 1,18 1,21 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Common/Common.hpp"
#include "Databases/NotificationsDB.hpp"
#include "module-db/Tables/NotificationsTable.hpp"
#include "Record.hpp"
#include "Interface/ContactRecord.hpp"

#include <utf8/UTF8.hpp>

#include <cstdint>
#include <vector>

// fw declarations
class ContactRecordInterface;
class NotificationsDB;

namespace db::query::notifications
{
    class Get;


@@ 37,15 40,13 @@ struct NotificationsRecord : public Record

    Key key        = Key::NotValidKey;
    uint32_t value = 0;
    std::optional<ContactRecord> contactRecord;

    friend std::ostream &operator<<(std::ostream &out, const NotificationsRecord &point);

    NotificationsRecord()  = default;
    ~NotificationsRecord() = default;
    explicit NotificationsRecord(const NotificationsTableRow &tableRow);

    bool isValidRecord() const;
    bool gotValidKey() const;
    explicit NotificationsRecord(const NotificationsTableRow &tableRow,
                                 std::optional<ContactRecord> record = std::nullopt);

    static bool isValidKey(Key key);
};


@@ 58,8 59,7 @@ enum class NotificationsRecordField
class NotificationsRecordInterface : public RecordInterface<NotificationsRecord, NotificationsRecordField>
{
  public:
    explicit NotificationsRecordInterface(NotificationsDB *notificationsDb);
    virtual ~NotificationsRecordInterface() = default;
    NotificationsRecordInterface(NotificationsDB *notificationsDb, ContactRecordInterface *contactsDb);

    bool Add(const NotificationsRecord &rec) override final;
    bool RemoveByID(uint32_t id) override final;


@@ 79,7 79,9 @@ class NotificationsRecordInterface : public RecordInterface<NotificationsRecord,

  private:
    NotificationsDB *notificationsDb = nullptr;
    ContactRecordInterface *contactsDb = nullptr;

    std::optional<ContactRecord> getContactRecord(uint32_t id) const;
    std::unique_ptr<db::query::notifications::GetResult> runQueryImpl(const db::query::notifications::Get *query);
    std::unique_ptr<db::query::notifications::IncrementResult> runQueryImpl(
        const db::query::notifications::Increment *query);

M module-db/Tables/NotificationsTable.cpp => module-db/Tables/NotificationsTable.cpp +17 -10
@@ 1,12 1,11 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "NotificationsTable.hpp"
#include "module-db/Interface/NotificationsRecord.hpp"
#include "Database/Database.hpp"

#include <log/log.hpp>
#include <Utils.hpp>

#include <cassert>

NotificationsTable::NotificationsTable(Database *db) : Table(db)


@@ 19,7 18,10 @@ bool NotificationsTable::create()

bool NotificationsTable::add(NotificationsTableRow entry)
{
    return db->execute("INSERT or IGNORE INTO notifications (key, value) VALUES (%lu, %lu);", entry.key, entry.value);
    return db->execute("INSERT or IGNORE INTO notifications (key, value, contact_id) VALUES (%lu, %lu, %lu);",
                       entry.key,
                       entry.value,
                       entry.contactID);
}

bool NotificationsTable::removeById(uint32_t id)


@@ 42,8 44,11 @@ bool NotificationsTable::removeByField(NotificationsTableFields field, const cha

bool NotificationsTable::update(NotificationsTableRow entry)
{
    return db->execute(
        "UPDATE notifications SET key = %lu, value = %lu WHERE _id = %lu;", entry.key, entry.value, entry.ID);
    return db->execute("UPDATE notifications SET key = %lu, value = %lu, contact_id = %lu WHERE _id = %lu;",
                       entry.key,
                       entry.value,
                       entry.contactID,
                       entry.ID);
}

NotificationsTableRow NotificationsTable::getById(uint32_t id)


@@ 59,12 64,12 @@ NotificationsTableRow NotificationsTable::getById(uint32_t id)
    return NotificationsTableRow{
        (*retQuery)[0].getUInt32(), // ID
        (*retQuery)[1].getUInt32(), // key
        (*retQuery)[2].getUInt32()  // value

        (*retQuery)[2].getUInt32(), // value
        (*retQuery)[3].getUInt32()  // contactID
    };
}

NotificationsTableRow NotificationsTable::GetByKey(uint32_t key)
NotificationsTableRow NotificationsTable::getByKey(uint32_t key)
{
    auto retQuery = db->query("SELECT * FROM notifications WHERE key= %u;", key);



@@ 77,7 82,8 @@ NotificationsTableRow NotificationsTable::GetByKey(uint32_t key)
    return NotificationsTableRow{
        (*retQuery)[0].getUInt32(), // ID
        (*retQuery)[1].getUInt32(), // key
        (*retQuery)[2].getUInt32()  // value
        (*retQuery)[2].getUInt32(), // value
        (*retQuery)[3].getUInt32()  // contactID
    };
}



@@ 96,6 102,7 @@ std::vector<NotificationsTableRow> NotificationsTable::getLimitOffset(uint32_t o
            (*retQuery)[0].getUInt32(), // ID
            (*retQuery)[1].getUInt32(), // key
            (*retQuery)[2].getUInt32(), // value
            (*retQuery)[3].getUInt32()  // contactID
        });
    } while (retQuery->nextRow());


M module-db/Tables/NotificationsTable.hpp => module-db/Tables/NotificationsTable.hpp +5 -3
@@ 1,18 1,20 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Table.hpp"
#include "Record.hpp"
#include "Database/Database.hpp"
#include "utf8/UTF8.hpp"
#include "Common/Common.hpp"

class Database;

struct NotificationsTableRow : public Record
{
    uint32_t key   = 0;
    uint32_t value = 0;
    uint32_t contactID = DB_ID_NONE;
};

enum class NotificationsTableFields


@@ 32,7 34,7 @@ class NotificationsTable : public Table<NotificationsTableRow, NotificationsTabl
    bool removeByField(NotificationsTableFields field, const char *str) override final;
    bool update(NotificationsTableRow entry) override final;
    NotificationsTableRow getById(uint32_t id) override final;
    NotificationsTableRow GetByKey(uint32_t key);
    NotificationsTableRow getByKey(uint32_t key);
    uint32_t count() override final;
    uint32_t countByFieldId(const char *field, uint32_t id) override final;
    std::vector<NotificationsTableRow> getLimitOffset(uint32_t offset, uint32_t limit) override final;

M module-db/queries/calllog/QueryCalllogSetAllRead.hpp => module-db/queries/calllog/QueryCalllogSetAllRead.hpp +2 -3
@@ 1,9 1,8 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "Interface/NotificationsRecord.hpp"
#include <Common/Query.hpp>
#include <string>



@@ 20,7 19,7 @@ namespace db::query::calllog
    class SetAllReadResult : public QueryResult
    {
      public:
        SetAllReadResult(bool ret);
        explicit SetAllReadResult(bool ret);
        [[nodiscard]] auto debugInfo() const -> std::string override;

        const bool ret = true;

M module-db/queries/notifications/QueryNotificationsClear.cpp => module-db/queries/notifications/QueryNotificationsClear.cpp +6 -1
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryNotificationsClear.hpp"


@@ 13,6 13,11 @@ namespace db::query::notifications
        return "Clear";
    }

    auto Clear::getKey() const noexcept -> NotificationsRecord::Key
    {
        return key;
    }

    ClearResult::ClearResult(bool ret) : ret(ret)
    {}


M module-db/queries/notifications/QueryNotificationsClear.hpp => module-db/queries/notifications/QueryNotificationsClear.hpp +7 -5
@@ 1,9 1,9 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "module-db/Interface/NotificationsRecord.hpp"
#include <Interface/NotificationsRecord.hpp>
#include <Common/Query.hpp>
#include <string>



@@ 11,10 11,12 @@ namespace db::query::notifications
{
    class Clear : public Query
    {
      public:
        const NotificationsRecord::Key key;
        Clear(NotificationsRecord::Key key);

      public:
        explicit Clear(NotificationsRecord::Key key);

        [[nodiscard]] auto getKey() const noexcept -> NotificationsRecord::Key;
        [[nodiscard]] auto debugInfo() const -> std::string override;
    };



@@ 23,7 25,7 @@ namespace db::query::notifications
        bool ret;

      public:
        ClearResult(bool ret);
        explicit ClearResult(bool ret);
        [[nodiscard]] auto getResult() const -> bool;

        [[nodiscard]] auto debugInfo() const -> std::string override;

M module-db/queries/notifications/QueryNotificationsGet.cpp => module-db/queries/notifications/QueryNotificationsGet.cpp +7 -2
@@ 1,4 1,4 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryNotificationsGet.hpp"


@@ 13,7 13,12 @@ namespace db::query::notifications
        return "Get";
    }

    GetResult::GetResult(NotificationsRecord record) : record(record)
    auto Get::getKey() const noexcept -> NotificationsRecord::Key
    {
        return key;
    }

    GetResult::GetResult(NotificationsRecord record) : record(std::move(record))
    {}

    auto GetResult::getResult() const -> NotificationsRecord

M module-db/queries/notifications/QueryNotificationsGet.hpp => module-db/queries/notifications/QueryNotificationsGet.hpp +7 -5
@@ 1,9 1,9 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "module-db/Interface/NotificationsRecord.hpp"
#include <Interface/NotificationsRecord.hpp>
#include <Common/Query.hpp>
#include <string>



@@ 11,10 11,12 @@ namespace db::query::notifications
{
    class Get : public Query
    {
      public:
        const NotificationsRecord::Key key;
        Get(NotificationsRecord::Key key);

      public:
        explicit Get(NotificationsRecord::Key key);

        [[nodiscard]] auto getKey() const noexcept -> NotificationsRecord::Key;
        [[nodiscard]] auto debugInfo() const -> std::string override;
    };



@@ 23,7 25,7 @@ namespace db::query::notifications
        NotificationsRecord record;

      public:
        GetResult(NotificationsRecord record);
        explicit GetResult(NotificationsRecord record);
        [[nodiscard]] auto getResult() const -> NotificationsRecord;

        [[nodiscard]] auto debugInfo() const -> std::string override;

M module-db/queries/notifications/QueryNotificationsGetAll.hpp => module-db/queries/notifications/QueryNotificationsGetAll.hpp +3 -3
@@ 1,9 1,9 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "module-db/Interface/NotificationsRecord.hpp"
#include <Interface/NotificationsRecord.hpp>
#include <Common/Query.hpp>
#include <string>



@@ 22,7 22,7 @@ namespace db::query::notifications
        std::unique_ptr<std::vector<NotificationsRecord>> records;

      public:
        GetAllResult(std::unique_ptr<std::vector<NotificationsRecord>> records);
        explicit GetAllResult(std::unique_ptr<std::vector<NotificationsRecord>> records);
        [[nodiscard]] auto getResult() -> std::unique_ptr<std::vector<NotificationsRecord>>;

        [[nodiscard]] auto debugInfo() const -> std::string override;

M module-db/queries/notifications/QueryNotificationsIncrement.cpp => module-db/queries/notifications/QueryNotificationsIncrement.cpp +13 -3
@@ 1,13 1,23 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "QueryNotificationsIncrement.hpp"

namespace db::query::notifications
{
    Increment::Increment(NotificationsRecord::Key key) : Query(Query::Type::Update), key(key)
    Increment::Increment(NotificationsRecord::Key key, const utils::PhoneNumber::View &number)
        : Query(Query::Type::Update), key(key), number(number)
    {}

    auto Increment::getKey() const noexcept -> NotificationsRecord::Key
    {
        return key;
    }
    auto Increment::getNumber() const noexcept -> const utils::PhoneNumber::View &
    {
        return number;
    }

    auto Increment::debugInfo() const -> std::string
    {
        return "Increment";


@@ 16,7 26,7 @@ namespace db::query::notifications
    IncrementResult::IncrementResult(bool ret) : ret(ret)
    {}

    auto IncrementResult::getResult() const -> bool
    auto IncrementResult::getResult() const noexcept -> bool
    {
        return ret;
    }

M module-db/queries/notifications/QueryNotificationsIncrement.hpp => module-db/queries/notifications/QueryNotificationsIncrement.hpp +12 -6
@@ 1,19 1,25 @@
// Copyright (c) 2017-2020, Mudita Sp. z.o.o. All rights reserved.
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include "module-db/Interface/NotificationsRecord.hpp"
#include <Interface/NotificationsRecord.hpp>
#include <Common/Query.hpp>
#include <string>
#include <PhoneNumber.hpp>

namespace db::query::notifications
{
    class Increment : public Query
    {
      public:
        const NotificationsRecord::Key key;
        Increment(NotificationsRecord::Key key);
        const utils::PhoneNumber::View number;

      public:
        Increment(NotificationsRecord::Key key, const utils::PhoneNumber::View &number);

        [[nodiscard]] auto getKey() const noexcept -> NotificationsRecord::Key;
        [[nodiscard]] auto getNumber() const noexcept -> const utils::PhoneNumber::View &;

        [[nodiscard]] auto debugInfo() const -> std::string override;
    };


@@ 23,8 29,8 @@ namespace db::query::notifications
        bool ret;

      public:
        IncrementResult(bool ret);
        [[nodiscard]] auto getResult() const -> bool;
        explicit IncrementResult(bool ret);
        [[nodiscard]] auto getResult() const noexcept -> bool;

        [[nodiscard]] auto debugInfo() const -> std::string override;
    };

M module-db/tests/CMakeLists.txt => module-db/tests/CMakeLists.txt +2 -2
@@ 22,8 22,8 @@ add_catch2_executable(
        #EventsTable_tests.cpp
        NotesRecord_tests.cpp
        NotesTable_tests.cpp
        #NotificationsRecord_tests.cpp
        #NotificationsTable_tests.cpp
        NotificationsRecord_tests.cpp
        NotificationsTable_tests.cpp
        QueryInterface.cpp
        SMSRecord_tests.cpp
        SMSTable_tests.cpp

M module-db/tests/NotificationsRecord_tests.cpp => module-db/tests/NotificationsRecord_tests.cpp +132 -23
@@ 1,15 1,18 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"
#include <catch2/catch.hpp>

#include "Interface/NotificationsRecord.hpp"
#include "Database/Database.hpp"
#include "Databases/NotificationsDB.hpp"
#include "module-db/queries/notifications/QueryNotificationsGet.hpp"
#include "module-db/queries/notifications/QueryNotificationsIncrement.hpp"
#include "module-db/queries/notifications/QueryNotificationsClear.hpp"
#include "module-db/queries/notifications/QueryNotificationsGetAll.hpp"
#include <Interface/NotificationsRecord.hpp>
#include <Interface/ContactRecord.hpp>
#include <Database/Database.hpp>
#include <Databases/NotificationsDB.hpp>
#include <Databases/ContactsDB.hpp>
#include <queries/notifications/QueryNotificationsGet.hpp>
#include <queries/notifications/QueryNotificationsIncrement.hpp>
#include <queries/notifications/QueryNotificationsClear.hpp>
#include <queries/notifications/QueryNotificationsGetAll.hpp>

#include <filesystem>



@@ 21,16 24,6 @@

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

    const auto notificationsPath = (std::filesystem::path{"sys/user"} / "notifications.db");
    if (std::filesystem::exists(notificationsPath)) {
        REQUIRE(std::filesystem::remove(notificationsPath));
    }

    NotificationsDB notificationsDb{notificationsPath.c_str()};

    REQUIRE(notificationsDb.isInitialized());

    SECTION("Default Constructor")
    {


@@ 39,27 32,43 @@ TEST_CASE("Notifications Record tests")
        REQUIRE(testRec.ID == DB_ID_NONE);
        REQUIRE(testRec.key == NotificationsRecord::Key::NotValidKey);
        REQUIRE(testRec.value == 0);
        REQUIRE_FALSE(testRec.contactRecord.has_value());
    }

    SECTION("Constructor from NotificationsTableRow")
    {
        NotificationsTableRow tableRow{
            {.ID = 10}, .key = static_cast<uint32_t>(NotificationsRecord::Key::Calls), .value = 2};

        NotificationsRecord testRec(tableRow);
        REQUIRE(testRec.isValid());
        REQUIRE(testRec.ID == 10);
        REQUIRE(testRec.key == NotificationsRecord::Key::Calls);
        REQUIRE(testRec.value == 2);
        REQUIRE_FALSE(testRec.contactRecord.has_value());
    }

    Database::initialize();
    const auto notificationsPath = (std::filesystem::path{"sys/user"} / "notifications.db");
    const auto contactsPath      = (std::filesystem::path{"sys/user"} / "contacts.db");
    RemoveDbFiles(notificationsPath.stem());
    RemoveDbFiles(contactsPath.stem());

    NotificationsDB notificationsDb{notificationsPath.c_str()};
    ContactsDB contactsDb{contactsPath.c_str()};
    REQUIRE(notificationsDb.isInitialized());
    REQUIRE(contactsDb.isInitialized());

    const auto notificationsCount = notificationsDb.notifications.count() + 1;
    // clear notifications table
    for (std::size_t id = 1; id <= notificationsCount; id++) {
        REQUIRE(notificationsDb.notifications.removeById(id));
    }
    NotificationsRecordInterface notificationsRecordInterface(&notificationsDb);

    ContactRecordInterface contactRecordInterface(&contactsDb);
    NotificationsRecordInterface notificationsRecordInterface(&notificationsDb, &contactRecordInterface);
    REQUIRE(contactRecordInterface.GetCount() == 0);
    REQUIRE(notificationsRecordInterface.GetCount() == 0);

    NotificationsTableRow callsRow{
        {.ID = DB_ID_NONE}, .key = static_cast<uint32_t>(NotificationsRecord::Key::Calls), .value = 0};



@@ 80,12 89,14 @@ TEST_CASE("Notifications Record tests")
        REQUIRE(callsNotifications.ID == 1);
        REQUIRE(callsNotifications.key == NotificationsRecord::Key::Calls);
        REQUIRE(callsNotifications.value == 0);
        REQUIRE_FALSE(callsNotifications.contactRecord.has_value());

        auto smsNotifications = notificationsRecordInterface.GetByID(2);
        REQUIRE(smsNotifications.isValid());
        REQUIRE(smsNotifications.ID == 2);
        REQUIRE(smsNotifications.key == NotificationsRecord::Key::Sms);
        REQUIRE(smsNotifications.value == 0);
        REQUIRE_FALSE(smsNotifications.contactRecord.has_value());
    }

    SECTION("Get entry by key")


@@ 180,7 191,9 @@ TEST_CASE("Notifications Record tests")
        REQUIRE(entryPost.value == entryPre.value);
    }

    auto getByKey = [&](NotificationsRecord::Key key, uint32_t val) {
    auto getByKey = [&](NotificationsRecord::Key key,
                        uint32_t expectedValue,
                        const std::optional<ContactRecord> &expectedContactRecord = std::nullopt) {
        auto query  = std::make_shared<db::query::notifications::Get>(key);
        auto ret    = notificationsRecordInterface.runQuery(query);
        auto result = dynamic_cast<db::query::notifications::GetResult *>(ret.get());


@@ 188,11 201,16 @@ TEST_CASE("Notifications Record tests")
        auto record = result->getResult();
        REQUIRE(record.isValid());
        REQUIRE(record.key == key);
        REQUIRE(record.value == val);
        REQUIRE(record.value == expectedValue);
        REQUIRE(record.contactRecord.has_value() == expectedContactRecord.has_value());
        if (expectedContactRecord.has_value()) {
            REQUIRE(record.contactRecord.value().ID == expectedContactRecord.value().ID);
        }
    };

    auto incrementByKey = [&](NotificationsRecord::Key key) {
        auto query  = std::make_shared<db::query::notifications::Increment>(key);
    auto incrementByKey = [&](NotificationsRecord::Key key, const std::string &phoneNumber = "+48500500500") {
        utils::PhoneNumber number(phoneNumber);
        auto query  = std::make_shared<db::query::notifications::Increment>(key, number.getView());
        auto ret    = notificationsRecordInterface.runQuery(query);
        auto result = dynamic_cast<db::query::notifications::IncrementResult *>(ret.get());
        REQUIRE(result != nullptr);


@@ 253,5 271,96 @@ TEST_CASE("Notifications Record tests")
        REQUIRE(records->size() == numberOfNotifcations);
    }

    SECTION("Notification DB and Contact DB connection tests")
    {
        const std::string primaryNameTest_1 = "PrimaryTestNameOne";
        const std::string numberUserTest_1  = "600123456";
        ContactRecord testContactRecord_1;
        testContactRecord_1.primaryName = primaryNameTest_1;
        testContactRecord_1.numbers =
            std::vector<ContactRecord::Number>({ContactRecord::Number(utils::PhoneNumber(numberUserTest_1).getView())});

        const std::string primaryNameTest_2 = "PrimaryTestNameTwo";
        const std::string numberUserTest_2  = "600600600";
        ContactRecord testContactRecord_2;
        testContactRecord_2.primaryName = primaryNameTest_2;
        testContactRecord_2.numbers =
            std::vector<ContactRecord::Number>({ContactRecord::Number(utils::PhoneNumber(numberUserTest_2).getView())});

        REQUIRE(contactRecordInterface.Add(testContactRecord_1));
        REQUIRE(contactRecordInterface.Add(testContactRecord_2));

        const auto someUnknownNumber      = "500200300";
        const auto noNotificationExpected = 0;
        const auto contactNotExpected     = std::nullopt;

        SECTION("Tests preconditions")
        {
            getByKey(NotificationsRecord::Key::Calls, noNotificationExpected);
        }

        SECTION("Single call notification from unknown number")
        {
            const auto expectedNotificationValue = 1;
            incrementByKey(NotificationsRecord::Key::Calls, someUnknownNumber);
            getByKey(NotificationsRecord::Key::Calls, expectedNotificationValue, contactNotExpected);
        }

        SECTION("Single call notification from known number")
        {
            const auto expectedNotificationValue = 1;
            incrementByKey(NotificationsRecord::Key::Calls, numberUserTest_1);
            getByKey(NotificationsRecord::Key::Calls, expectedNotificationValue, testContactRecord_1);
            clearByKey(NotificationsRecord::Key::Calls);
            getByKey(NotificationsRecord::Key::Calls, noNotificationExpected, contactNotExpected);
        }

        SECTION("Multiple call notifications from single known number")
        {
            const auto expectedNotificationValue = 3;
            for (int i = 0; i < expectedNotificationValue; i++)
                incrementByKey(NotificationsRecord::Key::Calls, numberUserTest_1);
            getByKey(NotificationsRecord::Key::Calls, expectedNotificationValue, testContactRecord_1);
            clearByKey(NotificationsRecord::Key::Calls);
            getByKey(NotificationsRecord::Key::Calls, noNotificationExpected, contactNotExpected);
        }

        SECTION("Multiple call notifications from multiple known numbers")
        {
            const auto expectedNotificationValue = 2 * 3;
            for (int i = 0; i < expectedNotificationValue / 2; i++) {
                incrementByKey(NotificationsRecord::Key::Calls, numberUserTest_1);
                incrementByKey(NotificationsRecord::Key::Calls, numberUserTest_2);
            }
            getByKey(NotificationsRecord::Key::Calls, expectedNotificationValue, contactNotExpected);
            clearByKey(NotificationsRecord::Key::Calls);
            getByKey(NotificationsRecord::Key::Calls, noNotificationExpected, contactNotExpected);
        }

        SECTION("Multiple call notifications in sequence of known-unknown-known numbers")
        {
            incrementByKey(NotificationsRecord::Key::Calls, numberUserTest_1);
            getByKey(NotificationsRecord::Key::Calls, 1, testContactRecord_1);

            incrementByKey(NotificationsRecord::Key::Calls, someUnknownNumber);
            getByKey(NotificationsRecord::Key::Calls, 2, contactNotExpected);

            incrementByKey(NotificationsRecord::Key::Calls, numberUserTest_1);
            getByKey(NotificationsRecord::Key::Calls, 3, contactNotExpected);
        }

        SECTION("Multiple call notifications in sequence of unknown-known-unknown numbers")
        {
            incrementByKey(NotificationsRecord::Key::Calls, someUnknownNumber);
            getByKey(NotificationsRecord::Key::Calls, 1, contactNotExpected);

            incrementByKey(NotificationsRecord::Key::Calls, numberUserTest_1);
            getByKey(NotificationsRecord::Key::Calls, 2, contactNotExpected);

            incrementByKey(NotificationsRecord::Key::Calls, someUnknownNumber);
            getByKey(NotificationsRecord::Key::Calls, 3, contactNotExpected);
        }
    }

    Database::deinitialize();
}

M module-db/tests/NotificationsTable_tests.cpp => module-db/tests/NotificationsTable_tests.cpp +15 -11
@@ 1,6 1,7 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "common.hpp"
#include <catch2/catch.hpp>

#include "Database/Database.hpp"


@@ 17,12 18,8 @@
TEST_CASE("Notifications Table tests")
{
    Database::initialize();

    const auto notificationsPath = (std::filesystem::path{"sys/user"} / "notifications.db");
    if (std::filesystem::exists(notificationsPath)) {
        REQUIRE(std::filesystem::remove(notificationsPath));
    }

    RemoveDbFiles(notificationsPath.stem());
    NotificationsDB notificationsDb{notificationsPath.c_str()};
    REQUIRE(notificationsDb.isInitialized());



@@ 50,11 47,12 @@ TEST_CASE("Notifications Table tests")
        REQUIRE(testRow.ID == DB_ID_NONE);
        REQUIRE(testRow.key == 0);
        REQUIRE(testRow.value == 0);
        REQUIRE(testRow.contactID == DB_ID_NONE);
        REQUIRE_FALSE(testRow.isValid());
    }

    REQUIRE(notificationsTbl.add({{.ID = 0}, .key = 3, .value = 8}));
    REQUIRE(notificationsTbl.add({{.ID = 0}, .key = 4, .value = 16}));
    REQUIRE(notificationsTbl.add({{.ID = 0}, .key = 4, .value = 16, .contactID = 100}));

    REQUIRE(notificationsTbl.count() == 4);



@@ 64,6 62,7 @@ TEST_CASE("Notifications Table tests")
        REQUIRE(entry.ID == 4);
        REQUIRE(entry.key == 4);
        REQUIRE(entry.value == 16);
        REQUIRE(entry.contactID == 100);
        REQUIRE(entry.isValid());
    }



@@ 73,6 72,7 @@ TEST_CASE("Notifications Table tests")
        REQUIRE(entry.ID == 3);
        REQUIRE(entry.key == 3);
        REQUIRE(entry.value == 8);
        REQUIRE(entry.contactID == DB_ID_NONE);
        REQUIRE(entry.isValid());
    }



@@ 93,11 93,12 @@ TEST_CASE("Notifications Table tests")

    SECTION("Entry update")
    {
        REQUIRE(notificationsTbl.update({{.ID = 3}, .key = 100, .value = 200}));
        REQUIRE(notificationsTbl.update({{.ID = 3}, .key = 100, .value = 200, .contactID = 300}));
        auto entry = notificationsTbl.getById(3);
        REQUIRE(entry.ID == 3);
        REQUIRE(entry.key == 100);
        REQUIRE(entry.value == 200);
        REQUIRE(entry.contactID == 300);
    }

    SECTION("Get entry - invalid ID")


@@ 107,14 108,16 @@ TEST_CASE("Notifications Table tests")
        REQUIRE(entry.ID == DB_ID_NONE);
        REQUIRE(entry.key == 0);
        REQUIRE(entry.value == 0);
        REQUIRE(entry.ID == DB_ID_NONE);
    }

    SECTION("Get by invalid key")
    {
        auto entry = notificationsTbl.getById(100);
        auto entry = notificationsTbl.getByKey(100);
        REQUIRE(entry.ID == DB_ID_NONE);
        REQUIRE(entry.key == 0);
        REQUIRE(entry.value == 0);
        REQUIRE(entry.ID == DB_ID_NONE);
        REQUIRE_FALSE(entry.isValid());
    }



@@ 158,13 161,14 @@ TEST_CASE("Notifications Table tests")

    SECTION("Check uniqueness")
    {
        REQUIRE(notificationsTbl.add({{.ID = 0}, .key = 3, .value = 100}));
        REQUIRE(notificationsTbl.add({{.ID = 0}, .key = 3, .value = 100, .contactID = 200}));
        REQUIRE(notificationsTbl.count() == 4);
        auto entry = notificationsTbl.getById(3);
        auto entry = notificationsTbl.getByKey(3);
        REQUIRE(entry.isValid());
        REQUIRE(entry.ID == 3);
        REQUIRE(entry.key == 3);
        REQUIRE(entry.value == 8);
        REQUIRE(entry.isValid());
        REQUIRE(entry.contactID == DB_ID_NONE);
    }

    Database::deinitialize();

M module-gui/gui/widgets/Style.hpp => module-gui/gui/widgets/Style.hpp +2 -1
@@ 249,7 249,8 @@ namespace style
        inline constexpr auto spanSize     = 8;
        inline constexpr auto digitSize    = 16;
        inline constexpr auto iconWidth    = 35;
        inline constexpr auto textMaxWidth = 250;
        inline constexpr auto textMinWidth = 250;
        inline constexpr auto textMaxWidth = 350;
        inline constexpr auto itemHeight   = 55;

        namespace model

M module-services/service-cellular/ServiceCellular.cpp => module-services/service-cellular/ServiceCellular.cpp +18 -16
@@ 214,10 214,10 @@ ServiceCellular::ServiceCellular()

    ongoingCall.setEndCallAction([=](const CalllogRecord &rec) {
        if (DBServiceAPI::CalllogUpdate(this, rec) && rec.type == CallType::CT_MISSED) {
            DBServiceAPI::GetQuery(
                this,
                db::Interface::Name::Notifications,
                std::make_unique<db::query::notifications::Increment>(NotificationsRecord::Key::Calls));
            DBServiceAPI::GetQuery(this,
                                   db::Interface::Name::Notifications,
                                   std::make_unique<db::query::notifications::Increment>(
                                       NotificationsRecord::Key::Calls, rec.phoneNumber));
        }
        return true;
    });


@@ 1841,21 1841,23 @@ SMSRecord ServiceCellular::createSMSRecord(const UTF8 &decodedMessage,

bool ServiceCellular::dbAddSMSRecord(const SMSRecord &record)
{
    return DBServiceAPI::AddSMS(this, record, db::QueryCallback::fromFunction([this](auto response) {
                                    auto result = dynamic_cast<db::query::SMSAddResult *>(response);
                                    if (result == nullptr || !result->result) {
                                        return false;
                                    }
                                    onSMSReceived();
                                    return true;
                                }));
    return DBServiceAPI::AddSMS(
        this, record, db::QueryCallback::fromFunction([this, number = record.number](auto response) {
            auto result = dynamic_cast<db::query::SMSAddResult *>(response);
            if (result == nullptr || !result->result) {
                return false;
            }
            onSMSReceived(number);
            return true;
        }));
}

void ServiceCellular::onSMSReceived()
void ServiceCellular::onSMSReceived(const utils::PhoneNumber::View &number)
{
    DBServiceAPI::GetQuery(this,
                           db::Interface::Name::Notifications,
                           std::make_unique<db::query::notifications::Increment>(NotificationsRecord::Key::Sms));
    DBServiceAPI::GetQuery(
        this,
        db::Interface::Name::Notifications,
        std::make_unique<db::query::notifications::Increment>(NotificationsRecord::Key::Sms, number));

    const auto guard = [&]() { return !phoneModeObserver->isInMode(sys::phone_modes::PhoneMode::DoNotDisturb); };
    auto filePath    = AudioServiceAPI::GetSound(this, audio::PlaybackType::TextMessageRingtone);

M module-services/service-cellular/service-cellular/ServiceCellular.hpp => module-services/service-cellular/service-cellular/ServiceCellular.hpp +1 -1
@@ 279,7 279,7 @@ class ServiceCellular : public sys::Service
                                            const time_t messageDate,
                                            const SMSType &smsType = SMSType::INBOX) const noexcept;
    bool dbAddSMSRecord(const SMSRecord &record);
    void onSMSReceived();
    void onSMSReceived(const utils::PhoneNumber::View &number);
    [[nodiscard]] bool receiveAllMessages();
    /// @}


M module-services/service-db/ServiceDB.cpp => module-services/service-db/ServiceDB.cpp +2 -1
@@ 263,7 263,8 @@ sys::ReturnCodes ServiceDB::InitHandler()
    notesRecordInterface         = std::make_unique<NotesRecordInterface>(notesDB.get());
    calllogRecordInterface       = std::make_unique<CalllogRecordInterface>(calllogDB.get(), contactsDB.get());
    countryCodeRecordInterface   = std::make_unique<CountryCodeRecordInterface>(countryCodesDB.get());
    notificationsRecordInterface = std::make_unique<NotificationsRecordInterface>(notificationsDB.get());
    notificationsRecordInterface =
        std::make_unique<NotificationsRecordInterface>(notificationsDB.get(), contactRecordInterface.get());
    eventsRecordInterface        = std::make_unique<EventsRecordInterface>(eventsDB.get());
    quotesRecordInterface        = std::make_unique<Quotes::QuotesAgent>(quotesDB.get());


M module-services/service-db/ServiceDB.hpp => module-services/service-db/ServiceDB.hpp +0 -1
@@ 19,7 19,6 @@
#include <Interface/CountryCodeRecord.hpp>
#include <Interface/EventsRecord.hpp>
#include <Interface/NotesRecord.hpp>
#include <Interface/NotificationsRecord.hpp>
#include <Interface/SMSRecord.hpp>
#include <Interface/SMSTemplateRecord.hpp>
#include <Interface/ThreadRecord.hpp>