~aleteoryx/muditaos

e49512bf4fd32f5871c5d2c146dce5167bedf7b5 — mkamonMdt 5 years ago 8bbb577
[EGD-5619] Add unified implementation of active icons

This PR provides unified and reusable implementation of active
icons used in windows system-wide.
M image/assets/lang/English.json => image/assets/lang/English.json +1 -0
@@ 308,6 308,7 @@
  "app_messages_offline": "You're offline.\n\nTo send a SMS\n switch to the Connected mode.",
  "app_messages_thread_draft": "Draft: ",
  "app_messages_thread_not_sent": "Not sent: ",
  "app_messages_thread_from_this": "From this message",
  "app_messages_thread_you": "You: ",
  "app_settings_title_main": "Settings",
  "app_settings_title_main_new": "Settings New",

M module-apps/CMakeLists.txt => module-apps/CMakeLists.txt +4 -1
@@ 30,13 30,16 @@ set( SOURCES
    "widgets/BrightnessBox.cpp"
    "widgets/ModesBox.cpp"
    "widgets/BarGraph.cpp"
    "widgets/ActiveIconFactory.cpp"
    "widgets/TextWithIconsWidget.cpp"
    "options/OptionsModel.cpp"
    "options/type/OptionSimple.cpp"
    "options/type/OptionCall.cpp"
    "options/type/OptionContact.cpp"
    "options/type/OptionSetting.cpp"
    "options/type/OptionChangePin.cpp"
     )
    "options/type/OptionWithActiveIcons.cpp"
    )

add_library(${PROJECT_NAME} STATIC ${SOURCES} ${BOARD_SOURCES})


M module-apps/application-messages/windows/MessagesMainWindow.cpp => module-apps/application-messages/windows/MessagesMainWindow.cpp +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

#include "MessagesMainWindow.hpp"


@@ 108,7 108,7 @@ namespace gui
    {
        LOG_INFO("Data: %s", data ? data->getDescription().c_str() : "");
        {
            auto pdata = dynamic_cast<PhonebookSearchReuqest *>(data);
            auto pdata = dynamic_cast<PhonebookSearchRequest *>(data);
            if (pdata != nullptr) {
                using db::query::ThreadGetByContactID;
                auto query = std::make_unique<ThreadGetByContactID>(pdata->result->ID);

M module-apps/application-messages/windows/NewMessage.cpp => module-apps/application-messages/windows/NewMessage.cpp +3 -3
@@ 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 "NewMessage.hpp"


@@ 67,7 67,7 @@ namespace gui
            return;
        }

        if (auto searchRequest = dynamic_cast<PhonebookSearchReuqest *>(data); searchRequest != nullptr) {
        if (auto searchRequest = dynamic_cast<PhonebookSearchRequest *>(data); searchRequest != nullptr) {
            LOG_INFO("Received search results");
            contact = searchRequest->result;
            recipient->setText(contact->getFormattedName());


@@ 109,7 109,7 @@ namespace gui
            memento->setState(message);
            return app::manager::Controller::sendAction(application,
                                                        app::manager::actions::ShowContacts,
                                                        std::make_unique<PhonebookSearchReuqest>(),
                                                        std::make_unique<PhonebookSearchRequest>(),
                                                        app::manager::OnSwitchBehaviour::RunInBackground);
        }
        return true;

M module-apps/application-messages/windows/OptionsMessages.cpp => module-apps/application-messages/windows/OptionsMessages.cpp +2 -4
@@ 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 "OptionsMessages.hpp"


@@ 7,16 7,14 @@

#include <common_data/Clipboard.hpp>
#include <Option.hpp>
#include <Text.hpp>
#include <i18n/i18n.hpp>

#include <Text.hpp>
#include <memory>
#include <module-services/service-db/service-db/DBServiceAPI.hpp>
#include <module-apps/options/type/OptionCall.hpp>
#include <module-apps/options/type/OptionContact.hpp>

using namespace style::window;

std::list<gui::Option> smsWindowOptions(app::ApplicationMessages *app, const SMSRecord &record)
{
    ContactRecord contact = DBServiceAPI::ContactGetByIDWithTemporary(app, record.contactID)->front();

M module-apps/application-phonebook/ApplicationPhonebook.cpp => module-apps/application-phonebook/ApplicationPhonebook.cpp +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

#include "ApplicationPhonebook.hpp"


@@ 166,7 166,7 @@ namespace app

            if (main_window->isSearchRequested()) {
                searchModel->messagesSelectCallback = [=](gui::PhonebookItem *item) {
                    std::unique_ptr<PhonebookSearchReuqest> data = std::make_unique<PhonebookSearchReuqest>();
                    std::unique_ptr<PhonebookSearchRequest> data = std::make_unique<PhonebookSearchRequest>();
                    data->result                                 = item->contact;
                    data->setDescription("PhonebookSearchRequest");
                    return app::manager::Controller::switchBack(

M module-apps/application-phonebook/CMakeLists.txt => module-apps/application-phonebook/CMakeLists.txt +0 -2
@@ 19,7 19,6 @@ target_sources( ${PROJECT_NAME}
                "${CMAKE_CURRENT_LIST_DIR}/widgets/InputBoxWithLabelAndIconWidget.cpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/InputLinesWithLabelIWidget.cpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/OutputLinesTextWithLabelWidget.cpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/NumberWithIconsWidget.cpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/PhonebookItem.cpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/PhonebookListView.cpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/ContactFlagsWidget.cpp"


@@ 48,7 47,6 @@ target_sources( ${PROJECT_NAME}
                "${CMAKE_CURRENT_LIST_DIR}/widgets/OutputLinesTextWithLabelWidget.hpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/PhonebookListView.hpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/PhonebookItem.hpp"
                "${CMAKE_CURRENT_LIST_DIR}/widgets/NumberWithIconsWidget.hpp"
                "${CMAKE_CURRENT_LIST_DIR}/windows/PhonebookContactDetails.hpp"
                "${CMAKE_CURRENT_LIST_DIR}/windows/PhonebookNewContact.hpp"
                "${CMAKE_CURRENT_LIST_DIR}/windows/PhonebookNamecardOptions.hpp"

M module-apps/application-phonebook/data/PhonebookItemData.hpp => module-apps/application-phonebook/data/PhonebookItemData.hpp +20 -19
@@ 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


@@ 12,30 12,32 @@

class PhonebookItemData : public gui::SwitchData
{
    std::string text;
    std::shared_ptr<ContactRecord> contact = nullptr;

  public:
    PhonebookItemData(std::shared_ptr<ContactRecord> contact, const std::string &text = "")
        : text(text), contact(contact){};
    virtual ~PhonebookItemData(){};
    std::shared_ptr<ContactRecord> getContact()
    PhonebookItemData() = default;
    explicit PhonebookItemData(std::shared_ptr<ContactRecord> contact, const std::string &text = "")
        : text(text), contact(std::move(contact)){};

    std::shared_ptr<ContactRecord> getContact() const
    {
        return contact;
    }

  public:
    PhonebookItemData() : contact(nullptr)
    {}
    std::string text;
    std::shared_ptr<ContactRecord> contact = nullptr;
    const std::string &getText() const noexcept
    {
        return text;
    }
};

class PhonebookSearchQuery : public gui::SwitchData
{
  public:
    PhonebookSearchQuery(std::string _searchQuery) : searchQuery(_searchQuery){};
    virtual ~PhonebookSearchQuery(){};
    std::string getQuery()
    explicit PhonebookSearchQuery(std::string searchQuery) : searchQuery(std::move(searchQuery)){};
    const std::string &getQuery() const noexcept
    {
        return (searchQuery);
        return searchQuery;
    }

  protected:


@@ 47,7 49,6 @@ class PhonebookSearchResultsData : public gui::SwitchData
  public:
    PhonebookSearchResultsData() = delete;
    PhonebookSearchResultsData(std::unique_ptr<PhonebookModel> model) : model(std::move(model)){};
    virtual ~PhonebookSearchResultsData(){};

    std::unique_ptr<PhonebookModel> consumeSearchResultsModel()
    {


@@ 61,14 62,14 @@ class PhonebookSearchResultsData : public gui::SwitchData
    bool consumed = false;
};

class PhonebookSearchReuqest : public gui::SwitchData
class PhonebookSearchRequest : public gui::SwitchData
{
  public:
    std::string request                                 = "";
    std::shared_ptr<std::vector<ContactRecord>> results = nullptr;
    PhonebookSearchReuqest(std::string request, std::shared_ptr<std::vector<ContactRecord>> results)
        : request(request), results(results)
    PhonebookSearchRequest(std::string request, std::shared_ptr<std::vector<ContactRecord>> results)
        : request(std::move(request)), results(std::move(results))
    {}
    PhonebookSearchReuqest()              = default;
    PhonebookSearchRequest()              = default;
    std::shared_ptr<ContactRecord> result = nullptr;
};

M module-apps/application-phonebook/widgets/InformationWidget.cpp => module-apps/application-phonebook/widgets/InformationWidget.cpp +21 -16
@@ 1,10 1,12 @@
// 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 "InformationWidget.hpp"

#include "AppWindow.hpp"
#include "application-phonebook/data/PhonebookStyle.hpp"
#include "widgets/TextWithIconsWidget.hpp"
#include "widgets/ActiveIconFactory.hpp"

#include <ContactRecord.hpp>
#include <i18n/i18n.hpp>


@@ 32,28 34,31 @@ namespace gui
        titleLabel->activeItem = false;

        onLoadCallback = [=](std::shared_ptr<ContactRecord> contact) {
            if (contact->numbers.size() > 0) {
            auto createBox = [=](const utils::PhoneNumber::View &number, const UTF8 &font) {
                auto numberHBox = new TextWithIconsWidget(vBox);
                ActiveIconFactory factory(app);
                numberHBox->addText(number.getFormatted(), font);
                numberHBox->addIcon(factory.makeCallIcon(number));
                numberHBox->addIcon(factory.makeSMSIcon(number));
                return numberHBox;
            };

            if (contact->numbers.size() > 0) {
                setMinimumHeight(widgetMinimumArea.h + phonebookStyle::numbersWithIconsWidget::h);

                primaryNumberHBox = new NumberWithIconsWidget(
                    app, contact->numbers[0].number, style::window::font::mediumbold, nullptr);
                vBox->addWidget(primaryNumberHBox);
                primaryNumberHBox = createBox(contact->numbers[0].number, style::window::font::mediumbold);
            }
            if (contact->numbers.size() > 1) {
                setMinimumHeight(widgetMinimumArea.h + phonebookStyle::numbersWithIconsWidget::h);
                secondNumberHBox =
                    new NumberWithIconsWidget(app, contact->numbers[1].number, style::window::font::medium, nullptr);

                vBox->addWidget(secondNumberHBox);
                secondaryNumberHBox = createBox(contact->numbers[1].number, style::window::font::medium);

                // Set proper navigation if second number is present
                primaryNumberHBox->smsImage->setNavigationItem(NavigationDirection::DOWN, secondNumberHBox->smsImage);
                primaryNumberHBox->phoneImage->setNavigationItem(NavigationDirection::DOWN,
                                                                 secondNumberHBox->phoneImage);
                primaryNumberHBox->icons[0]->setNavigationItem(NavigationDirection::DOWN,
                                                               secondaryNumberHBox->icons[0]);
                primaryNumberHBox->icons[1]->setNavigationItem(NavigationDirection::DOWN,
                                                               secondaryNumberHBox->icons[1]);

                secondNumberHBox->smsImage->setNavigationItem(NavigationDirection::UP, primaryNumberHBox->smsImage);
                secondNumberHBox->phoneImage->setNavigationItem(NavigationDirection::UP, primaryNumberHBox->phoneImage);
                secondaryNumberHBox->icons[0]->setNavigationItem(NavigationDirection::UP, primaryNumberHBox->icons[0]);
                secondaryNumberHBox->icons[1]->setNavigationItem(NavigationDirection::UP, primaryNumberHBox->icons[1]);
            }
            if (contact->mail.length() > 0) {
                setMinimumHeight(widgetMinimumArea.h + phonebookStyle::informationWidget::email_text_h +


@@ 94,7 99,7 @@ namespace gui
            }

            // Clear VBox down navigation if second number is present.
            if (secondNumberHBox != nullptr) {
            if (secondaryNumberHBox != nullptr) {
                primaryNumberHBox->clearNavigationItem(NavigationDirection::DOWN);
            }


M module-apps/application-phonebook/widgets/InformationWidget.hpp => module-apps/application-phonebook/widgets/InformationWidget.hpp +9 -8
@@ 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


@@ 6,25 6,26 @@
#include "application-phonebook/data/PhonebookInternals.hpp"
#include "application-phonebook/data/PhonebookItemData.hpp"
#include "application-phonebook/widgets/ContactListItem.hpp"
#include "NumberWithIconsWidget.hpp"

#include <ListItem.hpp>
#include <Text.hpp>

namespace gui
{
    class TextWithIconsWidget;

    class InformationWidget : public ContactListItem
    {
      public:
        InformationWidget(app::Application *app);
        ~InformationWidget() override = default;
        auto onDimensionChanged(const BoundingBox &oldDim, const BoundingBox &newDim) -> bool override;
        VBox *vBox                                   = nullptr;
        Label *titleLabel                            = nullptr;
        NumberWithIconsWidget *primaryNumberHBox     = nullptr;
        NumberWithIconsWidget *secondNumberHBox      = nullptr;
        Text *emailText                              = nullptr;
        Item *savedFocusItem                         = nullptr;
        VBox *vBox                               = nullptr;
        Label *titleLabel                        = nullptr;
        TextWithIconsWidget *primaryNumberHBox   = nullptr;
        TextWithIconsWidget *secondaryNumberHBox = nullptr;
        Text *emailText                          = nullptr;
        Item *savedFocusItem                     = nullptr;
    };

} /* namespace gui */

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

#include "NumberWithIconsWidget.hpp"

#include "application-phonebook/data/PhonebookStyle.hpp"

#include <BottomBar.hpp>

#include <application-call/data/CallSwitchData.hpp>
#include <service-appmgr/Controller.hpp>
#include <module-apps/application-messages/data/SMSdata.hpp>

namespace gui
{
    NumberWithIconsWidget::NumberWithIconsWidget(app::Application *app,
                                                 const utils::PhoneNumber::View &number,
                                                 const std::string &font,
                                                 Item *parent)
        : HBox(parent, 0, 0, 0, 0)
    {
        setReverseOrder(true);
        setMinimumSize(phonebookStyle::informationWidget::w, phonebookStyle::numbersWithIconsWidget::h);
        setEdges(gui::RectangleEdge::None);
        setAlignment(Alignment(gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Center));

        smsImage                = new ImageBox(this,
                                0,
                                0,
                                phonebookStyle::numbersWithIconsWidget::sms_image_w,
                                phonebookStyle::numbersWithIconsWidget::sms_image_h,
                                new Image("mail"));
        smsImage->inputCallback = [=](Item &item, const InputEvent &input) {
            if (input.keyCode == KeyCode::KEY_ENTER && input.state == InputEvent::State::keyReleasedShort) {
                LOG_INFO("SMS operation started");
                auto data                        = std::make_unique<SMSSendRequest>(number, std::string{});
                data->ignoreCurrentWindowOnStack = true;
                return app::manager::Controller::sendAction(app,
                                                            app::manager::actions::CreateSms,
                                                            std::move(data),
                                                            app::manager::OnSwitchBehaviour::RunInBackground);
            }
            return false;
        };

        phoneImage = new ImageBox(this,
                                  0,
                                  0,
                                  phonebookStyle::numbersWithIconsWidget::phone_image_w,
                                  phonebookStyle::numbersWithIconsWidget::phone_image_h,
                                  new Image("phonebook_phone_ringing"));
        phoneImage->setMargins(Margins(phonebookStyle::numbersWithIconsWidget::phone_image_margin_left,
                                       0,
                                       phonebookStyle::numbersWithIconsWidget::phone_image_margin_right,
                                       0));
        phoneImage->inputCallback = [=](Item &item, const InputEvent &input) {
            if (input.keyCode == KeyCode::KEY_ENTER && input.state == InputEvent::State::keyReleasedShort) {
                app::manager::Controller::sendAction(app,
                                                     app::manager::actions::Dial,
                                                     std::make_unique<app::ExecuteCallData>(number),
                                                     app::manager::OnSwitchBehaviour::RunInBackground);
                LOG_INFO("Call operation started");
            }
            return false;
        };

        numberText = new TextFixedSize(this, 0, 0, 0, 0);
        numberText->setUnderline(false);
        numberText->setMaximumSize(phonebookStyle::informationWidget::w,
                                   phonebookStyle::numbersWithIconsWidget::number_text_h);
        numberText->setFont(font);
        numberText->setEdges(gui::RectangleEdge::None);
        numberText->setEditMode(EditMode::Browse);
        numberText->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
        numberText->setText(number.getFormatted());
        numberText->activeItem = false;

        focusChangedCallback = [&](Item &item) {
            setFocusItem(focus ? phoneImage : nullptr);
            return true;
        };

        phoneImage->focusChangedCallback = [&, app](Item &item) {
            if (phoneImage->focus) {
                phoneImage->setEdges(RectangleEdge::Bottom | RectangleEdge::Top);
                app->getCurrentWindow()->bottomBarTemporaryMode(
                    utils::localize.get(style::strings::common::call), BottomBar::Side::CENTER, false);
            }
            else {
                phoneImage->setEdges(RectangleEdge::None);
            }
            return true;
        };

        smsImage->focusChangedCallback = [&, app](Item &item) {
            if (smsImage->focus) {
                smsImage->setEdges(RectangleEdge::Bottom | RectangleEdge::Top);
                app->getCurrentWindow()->bottomBarTemporaryMode(
                    utils::localize.get(style::strings::common::send), BottomBar::Side::CENTER, false);
            }
            else {
                smsImage->setEdges(RectangleEdge::None);
            }
            return true;
        };
    }
} /* namespace gui */

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

#pragma once

#include "Application.hpp"

#include <BoxLayout.hpp>
#include <Image.hpp>
#include <ImageBox.hpp>
#include <TextFixedSize.hpp>
#include <module-utils/PhoneNumber.hpp>

namespace gui
{
    class NumberWithIconsWidget : public HBox
    {
      public:
        NumberWithIconsWidget(app::Application *app,
                              const utils::PhoneNumber::View &number,
                              const std::string &font,
                              Item *parent);
        ~NumberWithIconsWidget() override = default;

        TextFixedSize *numberText = nullptr;
        ImageBox *phoneImage      = nullptr;
        ImageBox *smsImage        = nullptr;
    };

} /* namespace gui */

M module-apps/application-phonebook/windows/PhonebookMainWindow.cpp => module-apps/application-phonebook/windows/PhonebookMainWindow.cpp +3 -3
@@ 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 "PhonebookMainWindow.hpp"


@@ 97,12 97,12 @@ namespace gui
    {
        LOG_INFO("onBeforeShow");

        auto contactRequest = dynamic_cast<PhonebookSearchReuqest *>(data);
        auto contactRequest = dynamic_cast<PhonebookSearchRequest *>(data);
        requestedSearch     = contactRequest != nullptr;
        if (requestedSearch) {
            enableNewContact                       = false;
            phonebookModel->messagesSelectCallback = [=](gui::PhonebookItem *item) {
                std::unique_ptr<PhonebookSearchReuqest> data = std::make_unique<PhonebookSearchReuqest>();
                std::unique_ptr<PhonebookSearchRequest> data = std::make_unique<PhonebookSearchRequest>();
                data->result                                 = item->contact;
                data->setDescription("PhonebookSearchRequest");
                return app::manager::Controller::switchBack(

A module-apps/options/type/OptionWithActiveIcons.cpp => module-apps/options/type/OptionWithActiveIcons.cpp +71 -0
@@ 0,0 1,71 @@
// Copyright (c) 2017-2021, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "OptionWithActiveIcons.hpp"
#include <widgets/TextWithIconsWidget.hpp>
#include <widgets/ActiveIconFactory.hpp>
#include <TextFixedSize.hpp>

using namespace gui::option;

namespace
{
    [[nodiscard]] auto makeIcon(gui::ActiveIconFactory &factory,
                                const std::shared_ptr<ContactRecord> &contact,
                                OptionWithActiveIcons::BasicIcon icon) -> gui::ImageBox *
    {
        switch (icon) {
        case OptionWithActiveIcons::BasicIcon::SendSMS:
            return factory.makeSMSIcon(contact->numbers.front().number);
        case OptionWithActiveIcons::BasicIcon::Call:
            return factory.makeCallIcon(contact->numbers.front().number);
        case OptionWithActiveIcons::BasicIcon::AddContact:
            return factory.makeAddContactIcon(contact);
        }
        return nullptr;
    }
} // namespace

OptionWithActiveIcons::OptionWithActiveIcons(app::Application *app,
                                             std::shared_ptr<ContactRecord> contact,
                                             std::vector<BasicIcon> icons)
    : app{app}, contact{std::move(contact)}, icons{std::move(icons)}
{}

auto OptionWithActiveIcons::build() const -> ListItem *
{
    auto optionItem = new gui::ListItem();
    optionItem->setMinimumSize(style::window::default_body_width, style::window::label::big_h);
    auto optionBodyHBox = new gui::TextWithIconsWidget(optionItem);
    const auto &number  = contact->numbers.front().number;
    optionBodyHBox->addText(!number.getE164().empty() ? number.getE164() : number.getFormatted(),
                            style::window::font::bigbold);

    ActiveIconFactory factory(app);
    for (auto icon : icons) {
        optionBodyHBox->addIcon(makeIcon(factory, contact, icon));
    }

    optionItem->dimensionChangedCallback = [optionBodyHBox](gui::Item &, const BoundingBox &newDim) -> bool {
        optionBodyHBox->setPosition(0, 0);
        optionBodyHBox->setSize(newDim.w, newDim.h);
        return true;
    };

    optionItem->inputCallback = [optionBodyHBox](Item &, const InputEvent &inputEvent) {
        return optionBodyHBox->onInput(inputEvent);
    };

    optionItem->focusChangedCallback = [optionItem, optionBodyHBox, this](gui::Item &item) -> bool {
        optionItem->setEdges(RectangleEdge::None);
        if (item.focus) {
            item.setFocusItem(optionBodyHBox);
        }
        else {
            item.setFocusItem(nullptr);
            app->getCurrentWindow()->bottomBarRestoreFromTemporaryMode();
        }
        return true;
    };
    return optionItem;
}

A module-apps/options/type/OptionWithActiveIcons.hpp => module-apps/options/type/OptionWithActiveIcons.hpp +35 -0
@@ 0,0 1,35 @@
// 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 "OptionBase.hpp"
#include <module-db/Interface/ContactRecord.hpp>

namespace app
{
    class Application;
}

namespace gui::option
{
    class OptionWithActiveIcons : public Base
    {
      public:
        enum class BasicIcon
        {
            SendSMS,
            Call,
            AddContact
        };
        OptionWithActiveIcons(app::Application *app,
                              std::shared_ptr<ContactRecord> contact,
                              std::vector<BasicIcon> icons);

      private:
        app::Application *app;
        std::shared_ptr<ContactRecord> contact;
        std::vector<BasicIcon> icons;
        [[nodiscard]] auto build() const -> ListItem * override;
    };
} // namespace gui::option

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

#include "ActiveIconFactory.hpp"

#include <cassert>
#include <i18n/i18n.hpp>
#include <service-appmgr/Controller.hpp>
#include <application-phonebook/data/PhonebookItemData.hpp>
#include <application-call/data/CallSwitchData.hpp>
#include <module-apps/application-messages/data/SMSdata.hpp>

using namespace gui;

ActiveIconFactory::ActiveIconFactory(app::Application *app) : app{app}
{
    assert(app);
}

auto ActiveIconFactory::makeCustomIcon(const UTF8 &image,
                                       std::function<bool(Item &)> onActivated,
                                       std::string bottomBarActivatedName) -> ImageBox *
{
    auto icon = new ImageBox(nullptr, 0, 0, style::widgets::iconsSize, style::widgets::iconsSize, new Image(image));
    icon->activeItem           = onActivated.operator bool();
    icon->activatedCallback    = std::move(onActivated);
    icon->focusChangedCallback = [icon, application = app, name = std::move(bottomBarActivatedName)](Item &item) {
        if (icon->focus) {
            icon->setEdges(RectangleEdge::Bottom | RectangleEdge::Top);
            application->getCurrentWindow()->bottomBarTemporaryMode(
                utils::localize.get(name), BottomBar::Side::CENTER, false);
        }
        else {
            icon->setEdges(RectangleEdge::None);
        }
        return true;
    };
    return icon;
}

auto ActiveIconFactory::makeSMSIcon(const utils::PhoneNumber::View &number) -> ImageBox *
{
    return makeCustomIcon(
        "mail",
        [application = app, number](gui::Item &item) {
            auto data                        = std::make_unique<SMSSendRequest>(number, std::string{});
            data->ignoreCurrentWindowOnStack = true;
            return app::manager::Controller::sendAction(application,
                                                        app::manager::actions::CreateSms,
                                                        std::move(data),
                                                        app::manager::OnSwitchBehaviour::RunInBackground);
        },
        style::strings::common::send);
}

auto ActiveIconFactory::makeCallIcon(const utils::PhoneNumber::View &number) -> ImageBox *
{
    return makeCustomIcon(
        "phonebook_phone_ringing",
        [application = app, number](gui::Item &item) {
            return app::manager::Controller::sendAction(application,
                                                        app::manager::actions::Dial,
                                                        std::make_unique<app::ExecuteCallData>(number),
                                                        app::manager::OnSwitchBehaviour::RunInBackground);
        },
        style::strings::common::call);
}

auto ActiveIconFactory::makeAddContactIcon(const std::shared_ptr<ContactRecord> &contact) -> ImageBox *
{
    return makeCustomIcon(
        "cross",
        [application = app, contact](gui::Item &item) {
            return app::manager::Controller::sendAction(application,
                                                        app::manager::actions::EditContact,
                                                        std::make_unique<PhonebookItemData>(contact),
                                                        app::manager::OnSwitchBehaviour::RunInBackground);
        },
        "common_add");
}

A module-apps/widgets/ActiveIconFactory.hpp => module-apps/widgets/ActiveIconFactory.hpp +26 -0
@@ 0,0 1,26 @@
// 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 "ImageBox.hpp"
#include <Application.hpp>
#include <module-db/Interface/ContactRecord.hpp>

namespace gui
{
    class ActiveIconFactory
    {
        app::Application *app;

      public:
        explicit ActiveIconFactory(app::Application *app);

        [[nodiscard]] auto makeCustomIcon(const UTF8 &image,
                                          std::function<bool(Item &)> onActivated,
                                          std::string bottomBarActivatedName) -> ImageBox *;
        [[nodiscard]] auto makeSMSIcon(const utils::PhoneNumber::View &number) -> ImageBox *;
        [[nodiscard]] auto makeCallIcon(const utils::PhoneNumber::View &number) -> ImageBox *;
        [[nodiscard]] auto makeAddContactIcon(const std::shared_ptr<ContactRecord> &contact) -> ImageBox *;
    };
} // namespace gui

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

#include "TextWithIconsWidget.hpp"
#include <Style.hpp>
#include <TextFixedSize.hpp>
#include <Image.hpp>
#include "Application.hpp"

using namespace gui;

TextWithIconsWidget::TextWithIconsWidget(gui::Item *parent)
{
    assert(parent);
    setMinimumSize(style::window::default_body_width, style::widgets::h);
    setEdges(gui::RectangleEdge::None);
    setAlignment(Alignment(gui::Alignment::Horizontal::Right, gui::Alignment::Vertical::Center));
    parent->addWidget(this);
}
void TextWithIconsWidget::addIcon(ImageBox *icon)
{
    assert(icon);
    icon->setMargins(Margins(0, 0, style::widgets::rightMargin, 0));
    addWidget(icon);
    icons.push_back(icon);
}

void TextWithIconsWidget::addText(const std::string &text, const UTF8 &font)
{
    auto numberText = new TextFixedSize(this, 0, 0, 0, 0);
    numberText->setUnderline(false);
    numberText->setMaximumSize(style::window::default_body_width, style::widgets::h);
    numberText->setFont(font);
    numberText->setEdges(gui::RectangleEdge::None);
    numberText->setEditMode(EditMode::Browse);
    numberText->setAlignment(Alignment(gui::Alignment::Horizontal::Left, gui::Alignment::Vertical::Center));
    numberText->setMargins(Margins(style::widgets::leftMargin, 0, 0, 0));
    numberText->setRichText(text);
    numberText->activeItem = false;
}

A module-apps/widgets/TextWithIconsWidget.hpp => module-apps/widgets/TextWithIconsWidget.hpp +29 -0
@@ 0,0 1,29 @@
// 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 <BoxLayout.hpp>
#include <ImageBox.hpp>
namespace app
{
    class Application;
}

namespace gui
{

    class TextWithIconsWidget : public HBox
    {
        app::Application *app = nullptr;

      public:
        explicit TextWithIconsWidget(gui::Item *parent);

        void addIcon(ImageBox *icon);
        void addText(const std::string &text, const UTF8 &font);

        std::vector<ImageBox *> icons;
    };

} /* namespace gui */

M module-gui/gui/widgets/Style.hpp => module-gui/gui/widgets/Style.hpp +8 -0
@@ 259,4 259,12 @@ namespace style
        inline constexpr auto default_left_text_padding = 10U;
    } // namespace padding

    namespace widgets
    {
        inline constexpr auto h           = 55U;
        inline constexpr auto iconsSize   = h;
        inline constexpr auto leftMargin  = 10U;
        inline constexpr auto rightMargin = 10U;
    } // namespace widgets

}; // namespace style