~aleteoryx/muditaos

10f27328e82c84f3e7aaaae6075112ead7225f11 — Przemyslaw Brudny 4 years ago 6213041
[EGD-7215] Connected Sim contact import with backends

Connected Sim contact import with cellular contact read
and DB contacts write.
27 files changed, 605 insertions(+), 57 deletions(-)

M image/assets/lang/English.json
M module-apps/application-settings/ApplicationSettings.cpp
M module-apps/application-settings/CMakeLists.txt
M module-apps/application-settings/models/network/SimContactsImportModel.cpp
M module-apps/application-settings/models/network/SimContactsImportModel.hpp
A module-apps/application-settings/models/network/SimContactsRepository.cpp
A module-apps/application-settings/models/network/SimContactsRepository.hpp
M module-apps/application-settings/presenter/network/SimContactsImportWindowPresenter.cpp
M module-apps/application-settings/presenter/network/SimContactsImportWindowPresenter.hpp
M module-apps/application-settings/widgets/network/SimContactImportSelectWidget.cpp
M module-apps/application-settings/widgets/network/SimContactImportSelectWidget.hpp
M module-apps/application-settings/windows/network/SimCardsWindow.cpp
M module-apps/application-settings/windows/network/SimContactsImportWindow.cpp
M module-apps/application-settings/windows/network/SimContactsImportWindow.hpp
M module-db/Interface/ContactRecord.cpp
M module-db/Interface/ContactRecord.hpp
M module-db/queries/phonebook/QueryCheckContactsListDuplicates.cpp
M module-db/queries/phonebook/QueryCheckContactsListDuplicates.hpp
M module-db/tests/ContactsRecord_tests.cpp
M module-gui/gui/widgets/CheckBox.cpp
M module-gui/gui/widgets/CheckBox.hpp
M module-gui/gui/widgets/ListItem.hpp
M module-gui/gui/widgets/ListView.cpp
M module-gui/gui/widgets/ListView.hpp
M module-gui/gui/widgets/ListViewEngine.hpp
M module-gui/gui/widgets/Style.hpp
M module-utils/log/debug.hpp
M image/assets/lang/English.json => image/assets/lang/English.json +9 -0
@@ 3,6 3,7 @@
  "common_open": "OPEN",
  "common_call": "CALL",
  "common_save": "SAVE",
  "common_import": "IMPORT",
  "common_send": "SEND",
  "common_confirm": "CONFIRM",
  "common_select": "SELECT",


@@ 11,6 12,7 @@
  "common_back": "BACK",
  "common_skip": "SKIP",
  "common_set": "SET",
  "common_show": "SHOW",
  "common_yes": "Yes",
  "common_no": "No",
  "common_switch": "SWITCH",


@@ 25,6 27,7 @@
  "common_resume": "RESUME",
  "common_pause": "PAUSE",
  "common_retry": "TRY AGAIN",
  "common_replace": "REPLACE",
  "common_abort": "ABORT",
  "common_connect": "CONNECT",
  "common_disconnect": "DISCONNECT",


@@ 399,7 402,13 @@
  "app_settings_network_pin_settings": "PIN settings",
  "app_settings_network_pin": "PIN",
  "app_settings_network_pin_change_code": "Change PIN code",
  "app_settings_network_import_contacts": "Import contacts",
  "app_settings_network_import_contacts_duplicates": "Duplicates from SIM",
  "app_settings_network_import_contacts_from_sim_card": "Import contacts from SIM card",
  "app_settings_network_import_contacts_from_sim_card_reading": "<text>Importing in progress...<br></br>Please wait a moment.</text>",
  "app_settings_network_import_contacts_from_sim_card_no_contacts": "<text>There are no contacts<br></br>on this SIM card.</text>",
  "app_settings_network_import_contacts_from_sim_card_duplicates": "<text>We found <token>$DUPLICATES</token> duplicates. Do you want<br></br>to import duplicated contacts and<br></br>replace existing ones.</text>",
  "app_settings_network_import_contacts_from_sim_card_success": "Contacts imported successfully.",
  "app_settings_network_sim1": "SIM1",
  "app_settings_network_sim2": "SIM2",
  "app_settings_network_sim_none": "No SIM",

M module-apps/application-settings/ApplicationSettings.cpp => module-apps/application-settings/ApplicationSettings.cpp +8 -3
@@ 90,11 90,14 @@ namespace app
        constexpr inline auto operators_on = "operators_on";
    } // namespace settings

    static constexpr auto settingStackDepth = 1024 * 6; // 6Kb stack size

    ApplicationSettings::ApplicationSettings(std::string name,
                                             std::string parent,
                                             sys::phone_modes::PhoneMode mode,
                                             StartInBackground startInBackground)
        : Application(std::move(name), std::move(parent), mode, startInBackground), AsyncCallbackReceiver{this}
        : Application(std::move(name), std::move(parent), mode, startInBackground, settingStackDepth),
          AsyncCallbackReceiver{this}
    {
        CellularServiceAPI::SubscribeForOwnNumber(this, [&](const std::string &number) {
            selectedSimNumber = number;


@@ 132,6 135,7 @@ namespace app
            if (auto command = callbackStorage->getCallback(resp); command->execute()) {
                refreshWindow(gui::RefreshModes::GUI_REFRESH_FAST);
            }
            return sys::msgHandled();
        }

        return sys::MessageNone{};


@@ 381,8 385,9 @@ namespace app
            return std::make_unique<gui::SimPINSettingsWindow>(app);
        });
        windowsFactory.attach(gui::window::name::import_contacts, [&](Application *app, const std::string &name) {
            auto model     = std::make_unique<SimContactsImportModel>(this);
            auto presenter = std::make_unique<SimContactsImportWindowPresenter>(std::move(model));
            auto repository = std::make_unique<SimContactsRepository>(this);
            auto model      = std::make_unique<SimContactsImportModel>(this, std::move(repository));
            auto presenter  = std::make_unique<SimContactsImportWindowPresenter>(this, std::move(model));
            return std::make_unique<gui::SimContactsImportWindow>(app, std::move(presenter));
        });


M module-apps/application-settings/CMakeLists.txt => module-apps/application-settings/CMakeLists.txt +1 -0
@@ 20,6 20,7 @@ target_sources( ${PROJECT_NAME}
        models/network/ApnSettingsModel.cpp
        models/network/NewApnModel.cpp
        models/network/SimContactsImportModel.cpp
        models/network/SimContactsRepository.cpp
        models/display-keypad/QuotesModel.cpp
        models/display-keypad/CategoriesModel.cpp
        models/apps/AudioSettingsModel.cpp

M module-apps/application-settings/models/network/SimContactsImportModel.cpp => module-apps/application-settings/models/network/SimContactsImportModel.cpp +60 -6
@@ 5,12 5,11 @@

#include <application-settings/widgets/network/SimContactImportSelectWidget.hpp>
#include <ListView.hpp>
#include <i18n/i18n.hpp>

SimContactsImportModel::SimContactsImportModel(app::Application *app) : application(app)
{
    createData();
}
SimContactsImportModel::SimContactsImportModel(app::Application *app,
                                               std::unique_ptr<AbstractSimContactsRepository> contactsRepository)
    : application(app), contactsRepository(std::move(contactsRepository))
{}

auto SimContactsImportModel::requestRecordsCount() -> unsigned int
{


@@ 33,8 32,32 @@ auto SimContactsImportModel::getItem(gui::Order order) -> gui::ListItem *
    return getRecord(order);
}

void SimContactsImportModel::createData()
void SimContactsImportModel::createSimImported()
{
    createData(contactsRepository->getImportedRecords());
}

void SimContactsImportModel::createDuplicates()
{
    createData(contactsRepository->getDuplicatedRecords());
}

unsigned int SimContactsImportModel::getDuplicatesCount()
{
    return contactsRepository->getDuplicatedRecords().size();
}

void SimContactsImportModel::createData(const std::vector<ContactRecord> &importedRecords)
{
    auto app = application;

    for (const auto &record : importedRecords) {
        internalData.push_back(new gui::SimContactImportSelectWidget(
            record.primaryName + " " + record.alternativeName,
            [app](const UTF8 &text) { app->getCurrentWindow()->bottomBarTemporaryMode(text, false); },
            [app]() { app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode(); }));
    }

    for (auto item : internalData) {
        item->deleteByList = false;
    }


@@ 45,3 68,34 @@ void SimContactsImportModel::clearData()
    list->reset();
    eraseInternalData();
}

void SimContactsImportModel::eraseData()
{
    clearData();
    contactsRepository->clear();
}

std::vector<bool> SimContactsImportModel::getSelectedContacts()
{
    std::vector<bool> selectedContacts;
    for (const auto &item : internalData) {
        selectedContacts.push_back(item->isChecked());
    }
    return selectedContacts;
}

void SimContactsImportModel::findDuplicates(std::function<void(bool)> onDuplicatesCheckCallback)
{
    contactsRepository->findDuplicates(getSelectedContacts(), std::move(onDuplicatesCheckCallback));
}

void SimContactsImportModel::saveData(std::function<void()> onSaveCallback)
{
    auto duplicatesFound = getDuplicatesCount() != 0;
    contactsRepository->save(getSelectedContacts(), duplicatesFound, std::move(onSaveCallback));
}

void SimContactsImportModel::requestSimContacts(std::function<void()> onSimContactsReadCallback)
{
    contactsRepository->read(std::move(onSimContactsReadCallback));
}

M module-apps/application-settings/models/network/SimContactsImportModel.hpp => module-apps/application-settings/models/network/SimContactsImportModel.hpp +16 -4
@@ 3,22 3,34 @@

#pragma once

#include "SimContactsRepository.hpp"
#include <application-settings/widgets/network/SimContactImportSelectWidget.hpp>

#include <InternalModel.hpp>
#include <ListItemProvider.hpp>
#include <ContactRecord.hpp>
#include <Application.hpp>

class SimContactsImportModel : public app::InternalModel<gui::ListItem *>, public gui::ListItemProvider
class SimContactsImportModel : public app::InternalModel<gui::SimContactImportSelectWidget *>,
                               public gui::ListItemProvider
{
  private:
    app::Application *application = nullptr;
    std::vector<ContactRecord> importedRecords;
    std::shared_ptr<AbstractSimContactsRepository> contactsRepository;
    std::vector<bool> getSelectedContacts();

  public:
    explicit SimContactsImportModel(app::Application *app);
    SimContactsImportModel(app::Application *app, std::unique_ptr<AbstractSimContactsRepository> contactsRepository);

    void createData();
    void createSimImported();
    void createDuplicates();
    unsigned int getDuplicatesCount();
    void createData(const std::vector<ContactRecord> &importedRecords);
    void clearData();
    void eraseData();
    void requestSimContacts(std::function<void()> onSimContactsReadCallback);
    void saveData(std::function<void()> onSaveCallback);
    void findDuplicates(std::function<void(bool)> onDuplicatesCheckCallback);

    [[nodiscard]] auto requestRecordsCount() -> unsigned int override;
    [[nodiscard]] auto getMinimalItemSpaceRequired() const -> unsigned int override;

A module-apps/application-settings/models/network/SimContactsRepository.cpp => module-apps/application-settings/models/network/SimContactsRepository.cpp +155 -0
@@ 0,0 1,155 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "SimContactsRepository.hpp"

#include <queries/phonebook/QueryMergeContactsList.hpp>
#include <queries/phonebook/QueryCheckContactsListDuplicates.hpp>
#include <service-cellular/ServiceCellular.hpp>

#include <application-settings/ApplicationSettings.hpp>

SimContactsRepository::SimContactsRepository(app::Application *application)
    : app::AsyncCallbackReceiver{application}, application{application}
{}

const std::vector<ContactRecord> &SimContactsRepository::getImportedRecords()
{
    return importedRecords;
}

const std::vector<ContactRecord> &SimContactsRepository::getDuplicatedRecords()
{
    return duplicatedRecords;
}

void SimContactsRepository::clear()
{
    importedRecords.clear();
    uniqueRecords.clear();
    duplicatedRecords.clear();
}

void SimContactsRepository::findDuplicates(const std::vector<bool> &selectedContacts,
                                           AbstractSimContactsRepository::OnDupplicatesCheckCallback callback)
{
    std::vector<ContactRecord> recordsToSave;
    for (unsigned int i = 0; i < selectedContacts.size(); i++) {
        if (selectedContacts[i]) {
            recordsToSave.push_back(importedRecords[i]);
        }
    }

#if DEBUG_SIM_IMPORT_DATA == 1
    printRecordsData("Sim import selected", recordsToSave);
#endif

    auto query = std::make_unique<db::query::CheckContactsListDuplicates>(recordsToSave);
    auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Contact);
    task->setCallback([&, callback](auto response) {
        auto result = dynamic_cast<db::query::CheckContactsListDuplicatesResult *>(response);
        if (result == nullptr) {
            return false;
        }

        auto duplicatesFound = !result->getDuplicates().empty();

        if (callback) {
            if (duplicatesFound) {
                uniqueRecords     = std::move(result->getUnique());
                duplicatedRecords = std::move(result->getDuplicates());

#if DEBUG_SIM_IMPORT_DATA == 1
                printRecordsData("Sim import uniques", uniqueRecords);
                printRecordsData("Sim import duplicates", duplicatedRecords);
#endif
            }
            callback(duplicatesFound);
        }
        return true;
    });
    task->execute(application, this);
}

void SimContactsRepository::save(const std::vector<bool> &selectedContacts,
                                 bool duplicatesFound,
                                 OnSaveCallback callback)
{
    std::vector<ContactRecord> recordsToSave = uniqueRecords;
    for (unsigned int i = 0; i < selectedContacts.size(); i++) {
        if (selectedContacts[i]) {
            recordsToSave.push_back(duplicatesFound ? duplicatedRecords[i] : importedRecords[i]);
        }
    }

#if DEBUG_SIM_IMPORT_DATA == 1
    printRecordsData("Sim import to save data", recordsToSave);
#endif

    auto query = std::make_unique<db::query::MergeContactsList>(recordsToSave);
    auto task  = app::AsyncQuery::createFromQuery(std::move(query), db::Interface::Name::Contact);
    task->setCallback([&, callback](auto response) {
        auto result = dynamic_cast<db::query::MergeContactsListResult *>(response);
        if (result == nullptr) {
            return false;
        }
        if (callback) {
            callback();
        }
        return true;
    });
    task->execute(application, this);
}

void SimContactsRepository::read(AbstractSimContactsRepository::OnReadCallback readDoneCallback)
{
    std::function<void(const std::vector<cellular::SimContact> &simData)> callback =
        [&](const std::vector<cellular::SimContact> &simData) { updateImportedRecords(simData); };

    auto msg  = std::make_unique<cellular::GetSimContactsRequest>();
    auto task = app::AsyncRequest::createFromMessage(std::move(msg), cellular::service::name);
    auto cb   = [callback, readDoneCallback](auto response) {
        auto result = dynamic_cast<cellular::GetSimContactsResponse *>(response);
        if (result != nullptr && result->retCode == sys::ReturnCodes::Success) {
            callback(*result->getContacts());
        }
        readDoneCallback();
        return true;
    };
    task->execute(this->application, this, cb);
}

void SimContactsRepository::updateImportedRecords(const std::vector<cellular::SimContact> &simData)
{
    for (const auto &simRecord : simData) {
        ContactRecord rec = ContactRecord();

        auto description = simRecord.name;
        auto splitPos    = simRecord.name.find(' ');

        rec.primaryName = description.substr(0, splitPos);
        rec.alternativeName =
            splitPos != std::string::npos ? description.substr(rec.primaryName.length() + 1, description.length()) : "";
        rec.numbers.push_back(
            ContactRecord::Number(utils::PhoneNumber(simRecord.number, utils::country::Id::UNKNOWN).getView()));

        importedRecords.push_back(rec);
    }

#if DEBUG_SIM_IMPORT_DATA == 1
    printRecordsData("Imported records from sim", importedRecords);
#endif
}

#if DEBUG_SIM_IMPORT_DATA == 1
void SimContactsRepository::printRecordsData(const std::string &name, const std::vector<ContactRecord> &data)
{
    for (auto record : data) {
        LOG_SENSITIVE("%s: %s %s, Number: %s",
                      name.c_str(),
                      record.primaryName.c_str(),
                      record.alternativeName.c_str(),
                      record.numbers.front().number.getFormatted().c_str());
    }
}
#endif

A module-apps/application-settings/models/network/SimContactsRepository.hpp => module-apps/application-settings/models/network/SimContactsRepository.hpp +49 -0
@@ 0,0 1,49 @@
// 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 <apps-common/Application.hpp>
#include <module-db/Interface/ContactRecord.hpp>
#include <service-cellular/CellularMessage.hpp>

class AbstractSimContactsRepository
{
  public:
    using OnReadCallback             = std::function<void()>;
    using OnDupplicatesCheckCallback = std::function<void(bool duplicatesFound)>;
    using OnSaveCallback             = std::function<void()>;

    virtual ~AbstractSimContactsRepository() noexcept = default;

    virtual const std::vector<ContactRecord> &getImportedRecords()                                              = 0;
    virtual const std::vector<ContactRecord> &getDuplicatedRecords()                                            = 0;
    virtual void read(OnReadCallback callback)                                                                  = 0;
    virtual void clear()                                                                                        = 0;
    virtual void save(const std::vector<bool> &selectedContacts, bool duplicatesFound, OnSaveCallback callback) = 0;
    virtual void findDuplicates(const std::vector<bool> &selectedContacts, OnDupplicatesCheckCallback callback) = 0;
};

class SimContactsRepository : public AbstractSimContactsRepository, public app::AsyncCallbackReceiver
{
  public:
    explicit SimContactsRepository(app::Application *application);

    const std::vector<ContactRecord> &getImportedRecords() override;
    const std::vector<ContactRecord> &getDuplicatedRecords() override;
    void read(OnReadCallback callback) override;
    void clear() override;
    void save(const std::vector<bool> &selectedContacts, bool duplicatesFound, OnSaveCallback callback) override;
    void findDuplicates(const std::vector<bool> &selectedContacts, OnDupplicatesCheckCallback callback) override;
    void updateImportedRecords(const std::vector<cellular::SimContact> &simData);

  private:
    std::vector<ContactRecord> importedRecords;
    std::vector<ContactRecord> uniqueRecords;
    std::vector<ContactRecord> duplicatedRecords;
    app::Application *application;

#if DEBUG_SIM_IMPORT_DATA == 1
    void printRecordsData(const std::string &name, const std::vector<ContactRecord> &data);
#endif
};

M module-apps/application-settings/presenter/network/SimContactsImportWindowPresenter.cpp => module-apps/application-settings/presenter/network/SimContactsImportWindowPresenter.cpp +60 -5
@@ 4,16 4,71 @@
#include "SimContactsImportWindowPresenter.hpp"

SimContactsImportWindowPresenter::SimContactsImportWindowPresenter(
    std::shared_ptr<SimContactsImportModel> simContactsProvider)
    : simContactsProvider{std::move(simContactsProvider)}
{}
    app::Application *application, std::shared_ptr<SimContactsImportModel> simContactsProvider)
    : application(application), simContactsProvider{std::move(simContactsProvider)}
{
    onSave = [&]() {
        this->simContactsProvider->clearData();
        getView()->contactsImported();
        this->application->refreshWindow(gui::RefreshModes::GUI_REFRESH_DEEP);
        requestCompleted = true;
    };

    onDuplicatesCheck = [&](bool duplicatesFound) {
        if (duplicatesFound) {
            duplicatesChecked = true;
            this->simContactsProvider->clearData();
            getView()->displayDuplicatesCount(this->simContactsProvider->getDuplicatesCount());
            this->application->refreshWindow(gui::RefreshModes::GUI_REFRESH_DEEP);
        }
        else {
            this->simContactsProvider->saveData(onSave);
        }
        requestCompleted = true;
    };

    onSimContactsReady = [&]() {
        this->simContactsProvider->createSimImported();
        getView()->contactsReady();
        this->application->refreshWindow(gui::RefreshModes::GUI_REFRESH_DEEP);
    };
}

std::shared_ptr<gui::ListItemProvider> SimContactsImportWindowPresenter::getSimContactsProvider() const
{
    return simContactsProvider;
}

void SimContactsImportWindowPresenter::clearProviderData() const
void SimContactsImportWindowPresenter::eraseProviderData() const
{
    simContactsProvider->eraseData();
}

void SimContactsImportWindowPresenter::saveImportedContacts()
{
    this->application->refreshWindow(gui::RefreshModes::GUI_REFRESH_DEEP);
    requestCompleted = false;
    if (!duplicatesChecked) {
        simContactsProvider->findDuplicates(onDuplicatesCheck);
    }
    else {
        simContactsProvider->saveData(onSave);
    }
}

bool SimContactsImportWindowPresenter::isRequestCompleted()
{
    return requestCompleted;
}

void SimContactsImportWindowPresenter::requestSimContacts()
{
    simContactsProvider->requestSimContacts(onSimContactsReady);
}

void SimContactsImportWindowPresenter::requestDuplicates()
{
    simContactsProvider->clearData();
    this->simContactsProvider->createDuplicates();
    getView()->displayDuplicates();
    this->application->refreshWindow(gui::RefreshModes::GUI_REFRESH_DEEP);
}

M module-apps/application-settings/presenter/network/SimContactsImportWindowPresenter.hpp => module-apps/application-settings/presenter/network/SimContactsImportWindowPresenter.hpp +23 -4
@@ 12,7 12,11 @@ class SimContactsImportWindowContract
    class View
    {
      public:
        virtual ~View() noexcept = default;
        virtual ~View() noexcept                                                   = default;
        virtual void displayDuplicatesCount(unsigned int duplicatesCount) noexcept = 0;
        virtual void displayDuplicates() noexcept                                  = 0;
        virtual void contactsImported() noexcept                                   = 0;
        virtual void contactsReady() noexcept                                      = 0;
    };
    class Presenter : public app::BasePresenter<SimContactsImportWindowContract::View>
    {


@@ 20,18 24,33 @@ class SimContactsImportWindowContract
        virtual ~Presenter() noexcept = default;

        virtual std::shared_ptr<gui::ListItemProvider> getSimContactsProvider() const = 0;
        virtual void clearProviderData() const                                        = 0;
        virtual void eraseProviderData() const                                        = 0;
        virtual void saveImportedContacts()                                           = 0;
        virtual void requestDuplicates()                                              = 0;
        virtual void requestSimContacts()                                             = 0;
        virtual bool isRequestCompleted()                                             = 0;
    };
};

class SimContactsImportWindowPresenter : public SimContactsImportWindowContract::Presenter
{
  public:
    explicit SimContactsImportWindowPresenter(std::shared_ptr<SimContactsImportModel> simContactsProvider);
    explicit SimContactsImportWindowPresenter(app::Application *application,
                                              std::shared_ptr<SimContactsImportModel> simContactsProvider);

    std::shared_ptr<gui::ListItemProvider> getSimContactsProvider() const override;
    void clearProviderData() const override;
    void eraseProviderData() const override;
    void saveImportedContacts() override;
    void requestDuplicates() override;
    void requestSimContacts() override;
    bool isRequestCompleted() override;

  private:
    app::Application *application               = nullptr;
    bool requestCompleted                       = true;
    bool duplicatesChecked                      = false;
    std::function<void()> onSave                = nullptr;
    std::function<void(bool)> onDuplicatesCheck = nullptr;
    std::function<void()> onSimContactsReady    = nullptr;
    std::shared_ptr<SimContactsImportModel> simContactsProvider;
};

M module-apps/application-settings/widgets/network/SimContactImportSelectWidget.cpp => module-apps/application-settings/widgets/network/SimContactImportSelectWidget.cpp +6 -1
@@ 6,7 6,7 @@
namespace gui
{
    SimContactImportSelectWidget::SimContactImportSelectWidget(
        std::string contactName,
        const std::string &contactName,
        const std::function<void(const UTF8 &text)> &bottomBarTemporaryMode,
        const std::function<void()> &bottomBarRestoreFromTemporaryMode)
    {


@@ 38,4 38,9 @@ namespace gui
            return true;
        };
    }

    bool SimContactImportSelectWidget::isChecked()
    {
        return checkBoxWithLabel->isChecked();
    }
} /* namespace gui */

M module-apps/application-settings/widgets/network/SimContactImportSelectWidget.hpp => module-apps/application-settings/widgets/network/SimContactImportSelectWidget.hpp +2 -1
@@ 14,9 14,10 @@ namespace gui
        gui::CheckBoxWithLabel *checkBoxWithLabel = nullptr;

      public:
        SimContactImportSelectWidget(std::string contactName,
        SimContactImportSelectWidget(const std::string &contactName,
                                     const std::function<void(const UTF8 &text)> &bottomBarTemporaryMode = nullptr,
                                     const std::function<void()> &bottomBarRestoreFromTemporaryMode      = nullptr);
        bool isChecked();
    };

} /* namespace gui */

M module-apps/application-settings/windows/network/SimCardsWindow.cpp => module-apps/application-settings/windows/network/SimCardsWindow.cpp +1 -1
@@ 85,7 85,7 @@ namespace gui
        optList.emplace_back(std::make_unique<gui::option::OptionSettings>(
            utils::translate("app_settings_network_import_contacts_from_sim_card"),
            [=](gui::Item &item) {
                this->application->switchWindow(gui::window::name::import_contacts, nullptr);
                application->switchWindow(gui::window::name::import_contacts);
                return true;
            },
            nullptr,

M module-apps/application-settings/windows/network/SimContactsImportWindow.cpp => module-apps/application-settings/windows/network/SimContactsImportWindow.cpp +149 -14
@@ 4,6 4,8 @@
#include "SimContactsImportWindow.hpp"

#include <application-settings/windows/WindowNames.hpp>
#include <InputEvent.hpp>
#include <service-appmgr/Controller.hpp>

namespace gui
{


@@ 12,29 14,18 @@ namespace gui
        : AppWindow(app, gui::window::name::import_contacts), presenter(std::move(simImportPresenter))
    {
        presenter->attach(this);
        preventsAutoLock = true;
        buildInterface();
    }

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

    void SimContactsImportWindow::onClose(CloseReason reason)
    {
        if (reason != CloseReason::PhoneLock) {
            presenter->clearProviderData();
        }
    }

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

        setTitle(utils::translate("app_settings_network_import_contacts_from_sim_card"));
        setTitle(utils::translate("app_settings_network_import_contacts"));

        bottomBar->setActive(gui::BottomBar::Side::RIGHT, true);
        bottomBar->setText(gui::BottomBar::Side::RIGHT, utils::translate(::style::strings::common::back));
        bottomBar->setText(gui::BottomBar::Side::CENTER, utils::translate(::style::strings::common::import));

        list = new ListView(this,
                            style::window::default_left_margin,


@@ 44,6 35,150 @@ namespace gui
                            presenter->getSimContactsProvider(),
                            listview::ScrollBarType::Fixed);

        emptyListIcon = new gui::Icon(this,
                                      ::style::window::default_left_margin,
                                      ::style::window::default_vertical_pos,
                                      ::style::window::default_body_width,
                                      ::style::window::default_body_height,
                                      "info_icon_W_G",
                                      utils::translate("app_settings_network_import_contacts_from_sim_card_reading"));
        emptyListIcon->setAlignment(Alignment::Horizontal::Center);

        list->emptyListCallback    = [this]() { emptyListIcon->setVisible(true); };
        list->notEmptyListCallback = [this]() { emptyListIcon->setVisible(false); };

        setFocusItem(list);
    }

    void SimContactsImportWindow::onBeforeShow(ShowMode mode, SwitchData *data)
    {
        emptyListIcon->text->setRichText(
            utils::translate("app_settings_network_import_contacts_from_sim_card_reading"));
        emptyListIcon->image->set("update_icon_W_G");
        bottomBar->setActive(BottomBar::Side::RIGHT, true);
        bottomBar->setActive(BottomBar::Side::CENTER, false);
        bottomBar->setActive(BottomBar::Side::LEFT, false);

        presenter->requestSimContacts();
    }

    void SimContactsImportWindow::onClose(CloseReason reason)
    {
        if (reason != CloseReason::PhoneLock) {
            presenter->eraseProviderData();
        }
    }

    bool SimContactsImportWindow::onInput(const InputEvent &inputEvent)
    {
        if (inputEvent.isKeyRelease(gui::KeyCode::KEY_LF) && !bottomBar->isActive(BottomBar::Side::LEFT)) {
            return false;
        }

        if (inputEvent.isKeyRelease(gui::KeyCode::KEY_ENTER) && !bottomBar->isActive(BottomBar::Side::CENTER)) {
            return false;
        }

        if (inputEvent.isKeyRelease(gui::KeyCode::KEY_RF) && !bottomBar->isActive(BottomBar::Side::RIGHT)) {
            return false;
        }

        if (inputEvent.isKeyRelease(gui::KeyCode::KEY_LF) && presenter->isRequestCompleted() && onLFInputCallback) {
            onLFInputCallback();
            return true;
        }

        if (inputEvent.isKeyRelease(gui::KeyCode::KEY_ENTER) && presenter->isRequestCompleted() &&
            onEnterInputCallback) {
            onEnterInputCallback();
            return true;
        }

        return AppWindow::onInput(inputEvent);
    }

    void SimContactsImportWindow::contactsReady() noexcept
    {
        bottomBar->setActive(BottomBar::Side::CENTER, true);
        list->rebuildList();

        if (list->isEmpty()) {
            bottomBar->setActive(BottomBar::Side::CENTER, false);
            emptyListIcon->text->setRichText(
                utils::translate("app_settings_network_import_contacts_from_sim_card_no_contacts"));
            emptyListIcon->image->set("info_icon_W_G");
        }
        else {
            setTitle(utils::translate("app_settings_network_import_contacts_from_sim_card"));
            onEnterInputCallback = [&]() {
                displayProgressInfo();
                presenter->saveImportedContacts();
            };
        }
    }

    void SimContactsImportWindow::displayDuplicatesCount(unsigned int duplicatesCount) noexcept
    {
        list->rebuildList();

        setTitle(utils::translate("app_settings_network_import_contacts"));
        emptyListIcon->text->setRichText(
            utils::translate("app_settings_network_import_contacts_from_sim_card_duplicates"),
            {{"$DUPLICATES", std::to_string(duplicatesCount)}});
        emptyListIcon->image->set("info_icon_W_G");
        bottomBar->setActive(BottomBar::Side::RIGHT, true);
        bottomBar->setText(gui::BottomBar::Side::CENTER, utils::translate(::style::strings::common::show));
        bottomBar->setText(gui::BottomBar::Side::LEFT, utils::translate(::style::strings::common::skip));

        onLFInputCallback = [&]() {
            displayProgressInfo();
            presenter->saveImportedContacts();
        };
        onEnterInputCallback = [&]() { presenter->requestDuplicates(); };
    }

    void SimContactsImportWindow::displayDuplicates() noexcept
    {
        bottomBar->setText(gui::BottomBar::Side::CENTER, utils::translate(::style::strings::common::replace));
        list->rebuildList();

        setTitle(utils::translate("app_settings_network_import_contacts_duplicates"));
        bottomBar->setActive(BottomBar::Side::RIGHT, true);

        onLFInputCallback    = nullptr;
        onEnterInputCallback = [&]() {
            displayProgressInfo();
            presenter->saveImportedContacts();
        };
    }

    void SimContactsImportWindow::contactsImported() noexcept
    {
        list->rebuildList();

        setTitle(utils::translate("app_settings_network_import_contacts_from_sim_card"));
        emptyListIcon->text->setRichText(
            utils::translate("app_settings_network_import_contacts_from_sim_card_success"));
        emptyListIcon->image->set("success_icon_W_G");
        bottomBar->setActive(BottomBar::Side::RIGHT, false);
        bottomBar->setText(gui::BottomBar::Side::CENTER, utils::translate(::style::strings::common::ok));
        bottomBar->setText(gui::BottomBar::Side::LEFT, utils::translate("app_desktop_menu_contacts"));

        onLFInputCallback = [&]() {
            app::manager::Controller::sendAction(application, app::manager::actions::ShowContacts);
        };
        onEnterInputCallback = [&]() { application->switchWindow(gui::window::name::sim_cards); };
    }

    void SimContactsImportWindow::displayProgressInfo()
    {
        list->clear();
        list->emptyListCallback();
        emptyListIcon->text->setRichText(
            utils::translate("app_settings_network_import_contacts_from_sim_card_reading"));
        emptyListIcon->image->set("update_icon_W_G");
        bottomBar->setActive(BottomBar::Side::RIGHT, false);
        bottomBar->setActive(BottomBar::Side::CENTER, false);
        bottomBar->setActive(BottomBar::Side::LEFT, false);
    }
} // namespace gui

M module-apps/application-settings/windows/network/SimContactsImportWindow.hpp => module-apps/application-settings/windows/network/SimContactsImportWindow.hpp +12 -2
@@ 6,22 6,32 @@
#include <application-settings/windows/BaseSettingsWindow.hpp>
#include <application-settings/presenter/network/SimContactsImportWindowPresenter.hpp>
#include <application-settings/models/network/SimContactsImportModel.hpp>
#include <Icon.hpp>

namespace gui
{
    class SimContactsImportWindow : public AppWindow, public SimContactsImportWindowContract::View
    {

      public:
        SimContactsImportWindow(app::Application *app,
                                std::unique_ptr<SimContactsImportWindowContract::Presenter> presenter);

      private:
        ListView *list = nullptr;
        ListView *list                             = nullptr;
        Icon *emptyListIcon                        = nullptr;
        std::function<void()> onEnterInputCallback = nullptr;
        std::function<void()> onLFInputCallback    = nullptr;

        void buildInterface() override;
        void onBeforeShow(ShowMode mode, SwitchData *data) override;
        void onClose(CloseReason reason) override;
        bool onInput(const InputEvent &inputEvent) override;

        void displayDuplicatesCount(unsigned int duplicatesCount) noexcept override;
        void displayDuplicates() noexcept override;
        void contactsImported() noexcept override;
        void contactsReady() noexcept override;
        void displayProgressInfo();

        std::shared_ptr<SimContactsImportWindowPresenter::Presenter> presenter;
    };

M module-db/Interface/ContactRecord.cpp => module-db/Interface/ContactRecord.cpp +6 -2
@@ 1231,8 1231,9 @@ auto ContactRecordInterface::MergeContactsList(std::vector<ContactRecord> &conta
}

auto ContactRecordInterface::CheckContactsListDuplicates(std::vector<ContactRecord> &contacts)
    -> std::vector<ContactRecord>
    -> std::pair<std::vector<ContactRecord>, std::vector<ContactRecord>>
{
    std::vector<ContactRecord> unique;
    std::vector<ContactRecord> duplicates;
    std::vector<ContactNumberHolder> contactNumberHolders;
    auto numberMatcher = buildNumberMatcher(contactNumberHolders);


@@ 1246,6 1247,9 @@ auto ContactRecordInterface::CheckContactsListDuplicates(std::vector<ContactReco
        if (matchedNumber != numberMatcher.END) {
            duplicates.push_back(contact);
        }
        else {
            unique.push_back(contact);
        }
    }
    return duplicates;
    return {unique, duplicates};
}

M module-db/Interface/ContactRecord.hpp => module-db/Interface/ContactRecord.hpp +3 -2
@@ 232,9 232,10 @@ class ContactRecordInterface : public RecordInterface<ContactRecord, ContactReco
     * @brief Check which contacts in vector are duplicating contacts in DB
     *
     * @param contacts vector of contacts with single number
     * @return vector of contacts with numbers appearing in contacts DB
     * @return first vector of contacts with unique numbers and second with duplicates appearing in contacts DB
     */
    auto CheckContactsListDuplicates(std::vector<ContactRecord> &contacts) -> std::vector<ContactRecord>;
    auto CheckContactsListDuplicates(std::vector<ContactRecord> &contacts)
        -> std::pair<std::vector<ContactRecord>, std::vector<ContactRecord>>;

  private:
    ContactsDB *contactDB;

M module-db/queries/phonebook/QueryCheckContactsListDuplicates.cpp => module-db/queries/phonebook/QueryCheckContactsListDuplicates.cpp +8 -2
@@ 20,10 20,16 @@ std::vector<ContactRecord> &CheckContactsListDuplicates::getContactsList()
    return "CheckContactsListDuplicates";
}

CheckContactsListDuplicatesResult::CheckContactsListDuplicatesResult(std::vector<ContactRecord> duplicates)
    : duplicates(std::move(duplicates))
CheckContactsListDuplicatesResult::CheckContactsListDuplicatesResult(
    std::pair<std::vector<ContactRecord>, std::vector<ContactRecord>> result)
    : unique(std::move(result.first)), duplicates(std::move(result.second))
{}

std::vector<ContactRecord> &CheckContactsListDuplicatesResult::getUnique()
{
    return unique;
}

std::vector<ContactRecord> &CheckContactsListDuplicatesResult::getDuplicates()
{
    return duplicates;

M module-db/queries/phonebook/QueryCheckContactsListDuplicates.hpp => module-db/queries/phonebook/QueryCheckContactsListDuplicates.hpp +4 -1
@@ 28,12 28,15 @@ namespace db::query
    class CheckContactsListDuplicatesResult : public QueryResult
    {
      public:
        CheckContactsListDuplicatesResult(std::vector<ContactRecord> duplicates);
        explicit CheckContactsListDuplicatesResult(
            std::pair<std::vector<ContactRecord>, std::vector<ContactRecord>> result);
        std::vector<ContactRecord> &getUnique();
        std::vector<ContactRecord> &getDuplicates();

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

      private:
        std::vector<ContactRecord> unique;
        std::vector<ContactRecord> duplicates;
    };


M module-db/tests/ContactsRecord_tests.cpp => module-db/tests/ContactsRecord_tests.cpp +11 -2
@@ 559,8 559,10 @@ TEST_CASE("Contacts list duplicates search")
    }

    // Prepare contacts list to compare with DB
    std::pair<std::string, std::string> uniqueContact                     = {"600100500", "test5"};
    std::array<std::pair<std::string, std::string>, 3> rawContactsToCheck = {
        {rawContactsInitial[2], {"600100500", "test5"}, rawContactsInitial[0]}};
        {rawContactsInitial[2], uniqueContact, rawContactsInitial[0]}};
    constexpr auto numOfUniqueContacts     = 1;
    constexpr auto numOfDuplicatedContacts = 2;

    std::vector<ContactRecord> contacts;


@@ 570,7 572,14 @@ TEST_CASE("Contacts list duplicates search")
        record.numbers = std::vector<ContactRecord::Number>({ContactRecord::Number(rawContact.first, std::string(""))});
        contacts.push_back(record);
    }
    auto duplicates = records.CheckContactsListDuplicates(contacts);
    auto results    = records.CheckContactsListDuplicates(contacts);
    auto unique     = results.first;
    auto duplicates = results.second;

    REQUIRE(unique.size() == numOfUniqueContacts);

    REQUIRE(unique[0].numbers[0].number.getEntered() == uniqueContact.first);
    REQUIRE(unique[0].primaryName == uniqueContact.second);

    REQUIRE(duplicates.size() == numOfDuplicatedContacts);


M module-gui/gui/widgets/CheckBox.cpp => module-gui/gui/widgets/CheckBox.cpp +2 -1
@@ 80,13 80,14 @@ namespace gui

    void CheckBox::setCheck(bool state)
    {
        checkState = state;
        image->setVisible(state);
        resizeItems();
    }

    bool CheckBox::isChecked()
    {
        return image->visible;
        return checkState;
    }

} /* namespace gui */

M module-gui/gui/widgets/CheckBox.hpp => module-gui/gui/widgets/CheckBox.hpp +1 -0
@@ 11,6 11,7 @@ namespace gui
{
    class CheckBox : public HBox
    {
        bool checkState                                              = false;
        Image *image                                                 = nullptr;
        std::function<void(const UTF8 &text)> bottomBarTemporaryMode = nullptr;
        std::function<void()> bottomBarRestoreFromTemporaryMode      = nullptr;

M module-gui/gui/widgets/ListItem.hpp => module-gui/gui/widgets/ListItem.hpp +5 -5
@@ 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


@@ 21,9 21,9 @@ namespace gui
    template <class T> class ListItemWithCallbacks : public ListItem
    {
      public:
        std::function<bool()> onEmptyCallback                          = nullptr;
        std::function<bool()> onContentChangedCallback                 = nullptr;
        std::function<void(std::shared_ptr<T> record)> onSaveCallback  = nullptr;
        std::function<void(std::shared_ptr<T> record)> onLoadCallback  = nullptr;
        std::function<bool()> onEmptyCallback                         = nullptr;
        std::function<bool()> onContentChangedCallback                = nullptr;
        std::function<void(std::shared_ptr<T> record)> onSaveCallback = nullptr;
        std::function<void(std::shared_ptr<T> record)> onLoadCallback = nullptr;
    };
} /* namespace gui */

M module-gui/gui/widgets/ListView.cpp => module-gui/gui/widgets/ListView.cpp +8 -0
@@ 207,6 207,14 @@ namespace gui
        };
    }

    void ListView::clear()
    {
        if (scroll) {
            scroll->setVisible(false);
        }
        ListViewEngine::clear();
    }

    void ListView::setFocus()
    {
        if (!focus) {

M module-gui/gui/widgets/ListView.hpp => module-gui/gui/widgets/ListView.hpp +1 -0
@@ 51,6 51,7 @@ namespace gui
                 listview::ScrollBarType scrollType = listview::ScrollBarType::Proportional);

        void setScrollTopMargin(int value);
        void clear() override;
        void setAlignment(const Alignment &value) override;

        // virtual methods from Item

M module-gui/gui/widgets/ListViewEngine.hpp => module-gui/gui/widgets/ListViewEngine.hpp +1 -1
@@ 177,7 177,7 @@ namespace gui
        std::function<void()> prepareRebuildCallback;

        void reset();
        void clear();
        virtual void clear();
        void onClose();

        std::shared_ptr<ListItemProvider> getProvider();

M module-gui/gui/widgets/Style.hpp => module-gui/gui/widgets/Style.hpp +3 -0
@@ 162,6 162,7 @@ namespace style
            inline constexpr auto call           = "common_call";
            inline constexpr auto send           = "common_send";
            inline constexpr auto save           = "common_save";
            inline constexpr auto import         = "common_import";
            inline constexpr auto confirm        = "common_confirm";
            inline constexpr auto select         = "common_select";
            inline constexpr auto use            = "common_use";


@@ 169,6 170,7 @@ namespace style
            inline constexpr auto back           = "common_back";
            inline constexpr auto skip           = "common_skip";
            inline constexpr auto set            = "common_set";
            inline constexpr auto show           = "common_show";
            inline constexpr auto yes            = "common_yes";
            inline constexpr auto no             = "common_no";
            inline constexpr auto check          = "common_check";


@@ 185,6 187,7 @@ namespace style
            inline constexpr auto pause          = "common_pause";
            inline constexpr auto accept         = "common_accept";
            inline constexpr auto retry          = "common_retry";
            inline constexpr auto replace        = "common_replace";
            inline constexpr auto abort          = "common_abort";
            inline constexpr auto adjust         = "common_adjust";
            // days

M module-utils/log/debug.hpp => module-utils/log/debug.hpp +1 -0
@@ 10,6 10,7 @@
#define DEBUG_BLUETOOTH_HCI_BYTES    0 /// show communication with BT module - all the HCI bytes
#define DEBUG_SERVICE_MESSAGES       0 /// show messages prior to handling in service
#define DEBUG_DB_MODEL_DATA          0 /// show messages prior to handling in service
#define DEBUG_SIM_IMPORT_DATA        0 /// show messages connected to sim data imports
#define DEBUG_FONT                   0 /// show Font debug messages
#define DEBUG_GUI_TEXT               0 /// show basic debug messages for gui::Text - warning this can be hard on cpu
#define DEBUG_GUI_TEXT_LINES         0 /// show extended debug messages for gui::Text - lines building