~aleteoryx/muditaos

191a86f93dec17f0b4d83cf07c8140f47e427adf — Mateusz Piesta 3 years ago 84dd538
[BH-1537] Statistic frontend

Added integration of statistics model,
presenter and widget.
M image/assets/lang/English.json => image/assets/lang/English.json +4 -1
@@ 725,5 725,8 @@
  "app_bell_settings_factory_reset": "Factory reset",
  "app_bell_settings_display_factory_reset_confirmation": "<text>Reset to factory<br></br>settings ?</text>",
  "app_meditation_summary": "<text>You've meditated for<br />",
  "app_meditation_countdown_desc": "Starts in"
  "app_meditation_countdown_desc": "Starts in",
  "app_meditation_summary_total": "<text>Total:<br /><token>$VALUE</token></text>",
  "app_meditation_summary_average": "Average/day:",
  "app_meditation_summary_title": "<text>Last <token>$VALUE</token> days</text>"
}

M products/BellHybrid/apps/application-bell-meditation-timer/MeditationTimer.cpp => products/BellHybrid/apps/application-bell-meditation-timer/MeditationTimer.cpp +8 -5
@@ 13,6 13,7 @@
#include "models/ChimeVolume.hpp"
#include "models/StartDelay.hpp"
#include "models/ChimeInterval.hpp"
#include "models/Statistics.hpp"

#include "presenter/SettingsPresenter.hpp"
#include "presenter/StatisticsPresenter.hpp"


@@ 45,6 46,7 @@ namespace app
        chimeIntervalModel = std::make_unique<meditation::models::ChimeInterval>(this);
        chimeVolumeModel   = std::make_unique<meditation::models::ChimeVolume>(*audioModel);
        startDelayModel    = std::make_unique<meditation::models::StartDelay>(this);
        statisticsModel    = std::make_unique<meditation::models::Statistics>(this);

        createUserInterface();



@@ 66,10 68,11 @@ namespace app
                                  return std::make_unique<meditation::SettingsWindow>(app, std::move(presenter));
                              });

        windowsFactory.attach(meditation::StatisticsWindow::name, [](ApplicationCommon *app, const std::string &name) {
            auto presenter = std::make_unique<app::meditation::StatisticsPresenter>(app);
            return std::make_unique<meditation::StatisticsWindow>(app, std::move(presenter));
        });
        windowsFactory.attach(
            meditation::StatisticsWindow::name, [this](ApplicationCommon *app, const std::string &name) {
                auto presenter = std::make_unique<app::meditation::StatisticsPresenter>(app, *statisticsModel);
                return std::make_unique<meditation::StatisticsWindow>(app, std::move(presenter));
            });

        windowsFactory.attach(
            meditation::MeditationTimerWindow::name, [this](ApplicationCommon *app, const std::string &name) {


@@ 86,7 89,7 @@ namespace app
                              [this](ApplicationCommon *app, const std::string &name) {
                                  auto timeModel = std::make_unique<app::TimeModel>();
                                  auto presenter = std::make_unique<app::meditation::MeditationProgressPresenter>(
                                      app, settings.get(), std::move(timeModel), *chimeIntervalModel);
                                      app, settings.get(), std::move(timeModel), *chimeIntervalModel, *statisticsModel);
                                  return std::make_unique<gui::MeditationRunningWindow>(app, std::move(presenter));
                              });
        windowsFactory.attach(gui::window::session_paused::sessionPaused,

M products/BellHybrid/apps/application-bell-meditation-timer/data/Contract.hpp => products/BellHybrid/apps/application-bell-meditation-timer/data/Contract.hpp +9 -0
@@ 25,4 25,13 @@ namespace app::meditation::contract
        virtual void handleEnter()                                                      = 0;
        virtual void exitWithoutSave()                                                  = 0;
    };

    class StatisticsPresenter : public BasePresenter<View>
    {
      public:
        virtual ~StatisticsPresenter() noexcept                                         = default;
        virtual auto getPagesProvider() const -> std::shared_ptr<gui::ListItemProvider> = 0;
        virtual void eraseProviderData()                                                = 0;
        virtual void handleExit()                                                       = 0;
    };
} // namespace app::meditation::contract

M products/BellHybrid/apps/application-bell-meditation-timer/include/application-bell-meditation-timer/MeditationTimer.hpp => products/BellHybrid/apps/application-bell-meditation-timer/include/application-bell-meditation-timer/MeditationTimer.hpp +2 -0
@@ 11,6 11,7 @@ namespace app::meditation::models
    class ChimeInterval;
    class ChimeVolume;
    class StartDelay;
    class Statistics;
} // namespace app::meditation::models

namespace app


@@ 45,6 46,7 @@ namespace app
        std::unique_ptr<app::meditation::models::ChimeVolume> chimeVolumeModel;
        std::unique_ptr<app::meditation::models::StartDelay> startDelayModel;
        std::unique_ptr<AbstractAudioModel> audioModel;
        std::unique_ptr<app::meditation::models::Statistics> statisticsModel;
    };

    template <> struct ManifestTraits<MeditationTimer>

M products/BellHybrid/apps/application-bell-meditation-timer/models/Statistics.cpp => products/BellHybrid/apps/application-bell-meditation-timer/models/Statistics.cpp +6 -5
@@ 27,14 27,15 @@ namespace app::meditation::models
    Statistics::Statistics(app::ApplicationCommon *app) : app::AsyncCallbackReceiver{app}, app{app}
    {}

    void Statistics::addEntry(const time_t utcTimestamp, const std::chrono::minutes duration)
    void Statistics::addEntry(const std::chrono::minutes duration)
    {
        const auto addRequest = AsyncRequest::createFromMessage(
            std::make_unique<messages::Add>(Entry(utcTimestamp, duration)), service::name::db);
        addRequest->execute(app, this, [this](sys::ResponseMessage *) { return true; });
        const auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
        const auto addRequest =
            AsyncRequest::createFromMessage(std::make_unique<messages::Add>(Entry(now, duration)), service::name::db);
        addRequest->execute(app, this, [](sys::ResponseMessage *) { return true; });
    }

    std::optional<Summary> Statistics::getSummary(const std::uint32_t days)
    std::optional<Summary> Statistics::getSummary(const std::uint32_t days) const
    {
        const auto result = sendDBRequest(app, std::make_shared<messages::GetByDays>(days));
        if (not result) {

M products/BellHybrid/apps/application-bell-meditation-timer/models/Statistics.hpp => products/BellHybrid/apps/application-bell-meditation-timer/models/Statistics.hpp +2 -2
@@ 26,8 26,8 @@ namespace app::meditation::models
    {
      public:
        explicit Statistics(app::ApplicationCommon *app);
        void addEntry(time_t utcTimestamp, std::chrono::minutes duration);
        std::optional<Summary> getSummary(std::uint32_t days);
        void addEntry(std::chrono::minutes duration);
        std::optional<Summary> getSummary(std::uint32_t days) const;

      private:
        app::ApplicationCommon *app{nullptr};

M products/BellHybrid/apps/application-bell-meditation-timer/presenter/MeditationProgressPresenter.cpp => products/BellHybrid/apps/application-bell-meditation-timer/presenter/MeditationProgressPresenter.cpp +17 -3
@@ 5,11 5,11 @@
#include "MeditationCommon.hpp"
#include "MeditationProgressPresenter.hpp"
#include "models/ChimeInterval.hpp"
#include "models/Statistics.hpp"

#include <common/LanguageUtils.hpp>
#include <common/models/TimeModel.hpp>
#include <common/windows/BellFinishedWindow.hpp>
#include <common/windows/SessionPausedWindow.hpp>
#include <service-db/Settings.hpp>

namespace


@@ 28,8 28,10 @@ namespace app::meditation
    MeditationProgressPresenter::MeditationProgressPresenter(app::ApplicationCommon *app,
                                                             settings::Settings *settings,
                                                             std::unique_ptr<AbstractTimeModel> timeModel,
                                                             models::ChimeInterval &chimeIntervalModel)
        : app{app}, settings{settings}, timeModel{std::move(timeModel)}, chimeIntervalModel{chimeIntervalModel}
                                                             models::ChimeInterval &chimeIntervalModel,
                                                             models::Statistics &statisticsModel)
        : app{app}, settings{settings}, timeModel{std::move(timeModel)}, chimeIntervalModel{chimeIntervalModel},
          statisticsModel{statisticsModel}
    {
        duration = std::chrono::minutes{
            utils::getNumericValue<int>(settings->getValue(meditationDBRecordName, settings::SettingsScope::AppLocal))};


@@ 86,6 88,9 @@ namespace app::meditation
        const auto elapsed     = std::chrono::duration_cast<std::chrono::minutes>(timer->getElapsed());
        const auto summaryText = utils::translate("app_meditation_summary") + std::to_string(elapsed.count()) + " " +
                                 utils::language::getCorrectMinutesNumeralForm(elapsed.count());

        addMeditationEntry(elapsed);

        app->switchWindow(
            gui::window::bell_finished::defaultName,
            gui::BellFinishedWindowData::Factory::create("big_namaste_W_G",


@@ 101,6 106,9 @@ namespace app::meditation
        const auto elapsed     = std::chrono::duration_cast<std::chrono::minutes>(timer->getElapsed());
        const auto summaryText = utils::translate("app_meditation_summary") + std::to_string(elapsed.count()) + " " +
                                 utils::language::getCorrectMinutesAccusativeForm(elapsed.count());

        addMeditationEntry(elapsed);

        app->switchWindow(
            gui::window::bell_finished::defaultName,
            gui::BellFinishedWindowData::Factory::create("big_namaste_W_G",


@@ 127,4 135,10 @@ namespace app::meditation
    {
        getView()->setTimeFormat(timeModel->getTimeFormat());
    }
    void MeditationProgressPresenter::addMeditationEntry(const std::chrono::minutes elapsed)
    {
        if (elapsed > std::chrono::minutes::zero()) {
            statisticsModel.addEntry(elapsed);
        }
    }
} // namespace app::meditation

M products/BellHybrid/apps/application-bell-meditation-timer/presenter/MeditationProgressPresenter.hpp => products/BellHybrid/apps/application-bell-meditation-timer/presenter/MeditationProgressPresenter.hpp +6 -1
@@ 31,6 31,7 @@ namespace app::meditation
    namespace models
    {
        class ChimeInterval;
        class Statistics;
    } // namespace models

    class MeditationProgressContract


@@ 71,6 72,7 @@ namespace app::meditation
        std::unique_ptr<app::TimerWithCallbacks> timer;
        std::unique_ptr<AbstractTimeModel> timeModel;
        models::ChimeInterval &chimeIntervalModel;
        models::Statistics &statisticsModel;
        std::chrono::minutes duration;
        std::chrono::seconds interval;



@@ 79,11 81,14 @@ namespace app::meditation
        void onProgressFinished();
        void onIntervalReached();

        void addMeditationEntry(std::chrono::minutes elapsed);

      public:
        MeditationProgressPresenter(app::ApplicationCommon *app,
                                    settings::Settings *settings,
                                    std::unique_ptr<AbstractTimeModel> timeModel,
                                    models::ChimeInterval &chimeIntervalModel);
                                    models::ChimeInterval &chimeIntervalModel,
                                    models::Statistics &statisticsModel);

        void setTimer(std::unique_ptr<app::TimerWithCallbacks> &&_timer) override;
        void handleUpdateTimeEvent() override;

M products/BellHybrid/apps/application-bell-meditation-timer/presenter/StatisticsPresenter.cpp => products/BellHybrid/apps/application-bell-meditation-timer/presenter/StatisticsPresenter.cpp +27 -27
@@ 8,48 8,48 @@

#include <db/MeditationStatsMessages.hpp>
#include <ApplicationCommon.hpp>
#include <common/windows/BellFinishedWindow.hpp>
#include <common/BellListItemProvider.hpp>

namespace app::meditation
namespace
{
    StatisticsPresenter::StatisticsPresenter(app::ApplicationCommon *app)
    std::string createTitle(const std::string &str, const std::uint32_t days)
    {
        const auto model = std::make_unique<models::Statistics>(app);

        const auto t1      = cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks());
        const auto summary = model->getSummary(500);
        const auto t2      = cpp_freertos::Ticks::TicksToMs(cpp_freertos::Ticks::GetTicks());
        auto parser       = gui::text::RichTextParser{};
        const auto result = parser.parse(
            utils::translate(str), nullptr, gui::text::RichTextParser::TokenMap({{"$VALUE", std::to_string(days)}}));
        return result->getText();
    }
} // namespace

        auto entry1 = new SummaryListItem("Total [min]", std::to_string(summary->sum.count()));
        auto entry2 = new SummaryListItem("Avg [min]", std::to_string(summary->avg.count()));
        auto entry3 = new SummaryListItem("Entries", std::to_string(summary->count));
        auto entry4 = new SummaryListItem("Query took [ms]", std::to_string(t2 - t1));
namespace app::meditation
{
    StatisticsPresenter::StatisticsPresenter(app::ApplicationCommon *app, const models::Statistics &statisticsModel)
        : app{app}
    {
        BellListItemProvider::Items listItems;
        for (const auto e : std::array<std::uint32_t, 3>{7, 30, 365}) {
            if (const auto summary = statisticsModel.getSummary(e)) {
                auto listItem =
                    new SummaryListItem(createTitle("app_meditation_summary_title", e), summary->sum, summary->avg);
                listItems.push_back(listItem);
            }
            else {
                LOG_ERROR("Fetching summary for the last %" PRIu32 " days failed", e);
            }
        }

        listItemsProvider = std::make_shared<BellListItemProvider>(
            BellListItemProvider::Items{reinterpret_cast<gui::BellSideListItemWithCallbacks *>(entry1),
                                        reinterpret_cast<gui::BellSideListItemWithCallbacks *>(entry2),
                                        reinterpret_cast<gui::BellSideListItemWithCallbacks *>(entry3),
                                        reinterpret_cast<gui::BellSideListItemWithCallbacks *>(entry4)});
        listItemsProvider = std::make_shared<BellListItemProvider>(std::move(listItems));
    }
    void StatisticsPresenter::eraseProviderData()
    {
        listItemsProvider->clearData();
    }
    void StatisticsPresenter::loadData()
    {}
    void StatisticsPresenter::saveData()
    {}
    auto StatisticsPresenter::getPagesProvider() const -> std::shared_ptr<gui::ListItemProvider>
    {
        return listItemsProvider;
    }
    void StatisticsPresenter::handleEnter()
    void StatisticsPresenter::handleExit()
    {
        app->switchWindow(
            gui::window::bell_finished::defaultName,
            gui::BellFinishedWindowData::Factory::create("circle_success_big", MeditationMainWindow::defaultName));
        app->returnToPreviousWindow();
    }
    void StatisticsPresenter::exitWithoutSave()
    {}
} // namespace app::meditation

M products/BellHybrid/apps/application-bell-meditation-timer/presenter/StatisticsPresenter.hpp => products/BellHybrid/apps/application-bell-meditation-timer/presenter/StatisticsPresenter.hpp +7 -6
@@ 15,16 15,17 @@ namespace app

namespace app::meditation
{
    class StatisticsPresenter : public contract::Presenter
    namespace models
    {
        class Statistics;
    }
    class StatisticsPresenter : public contract::StatisticsPresenter
    {
      public:
        explicit StatisticsPresenter(app::ApplicationCommon *app);
        StatisticsPresenter(app::ApplicationCommon *app, const models::Statistics &statisticsModel);
        auto getPagesProvider() const -> std::shared_ptr<gui::ListItemProvider> override;
        void loadData() override;
        void saveData() override;
        void eraseProviderData() override;
        void handleEnter() override;
        void exitWithoutSave() override;
        void handleExit() override;

      private:
        app::ApplicationCommon *app{nullptr};

M products/BellHybrid/apps/application-bell-meditation-timer/widgets/SummaryListItem.cpp => products/BellHybrid/apps/application-bell-meditation-timer/widgets/SummaryListItem.cpp +13 -10
@@ 24,6 24,17 @@ namespace
        return ret;
    }

    gui::TextFixedSize *createCenterText(gui::Item *parent)
    {
        using namespace gui;
        auto widget = new TextFixedSize(parent);
        widget->setFont(style::bell_sidelist_item::title_font);
        widget->setEdges(RectangleEdge::None);
        widget->activeItem = false;
        widget->drawUnderline(false);
        return widget;
    }

} // namespace

namespace app::meditation


@@ 40,24 51,16 @@ namespace app::meditation
        duoBox->setAlignment(Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        duoBox->setMaximumSize(::style::bell_base_layout::w, ::style::bell_base_layout::h);

        auto centerText = new TextFixedSize(duoBox);
        auto centerText = createCenterText(duoBox);
        centerText->setMinimumSize(style::bell_base_layout::w, 2 * (style::bell_base_layout::center_layout_h / 3));
        centerText->setFont(style::bell_sidelist_item::title_font);
        centerText->setEdges(RectangleEdge::None);
        centerText->activeItem = false;
        centerText->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Top));
        centerText->setRichText(utils::translate("app_meditation_summary_total"),
                                text::RichTextParser::TokenMap({{"$VALUE", format(total)}}));
        centerText->drawUnderline(false);

        auto centerText2 = new TextFixedSize(duoBox);
        centerText2->setFont(style::bell_sidelist_item::title_font);
        auto centerText2 = createCenterText(duoBox);
        centerText2->setMinimumSize(style::bell_base_layout::w, style::bell_base_layout::center_layout_h / 3);
        centerText2->setEdges(RectangleEdge::None);
        centerText2->activeItem = false;
        centerText2->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Bottom));
        centerText2->setRichText(utils::translate("app_meditation_summary_average"));
        centerText2->drawUnderline(false);

        setupBottomDescription(format(average));
        bottomText->setFont(style::bell_sidelist_item::title_font);

M products/BellHybrid/apps/application-bell-meditation-timer/widgets/SummaryListItem.hpp => products/BellHybrid/apps/application-bell-meditation-timer/widgets/SummaryListItem.hpp +6 -3
@@ 7,13 7,16 @@

#include <chrono>

namespace gui
{
    class TextFixedSize;
}

namespace app::meditation
{
    class SummaryListItem : public gui::BellSideListItemWithCallbacks
    {
      public:
        SummaryListItem(const std::string &topDescription,
                        std::chrono::minutes total,
                        std::chrono::minutes average);
        SummaryListItem(const std::string &topDescription, std::chrono::minutes total, std::chrono::minutes average);
    };
} // namespace app::meditation

M products/BellHybrid/apps/application-bell-meditation-timer/windows/StatisticsWindow.cpp => products/BellHybrid/apps/application-bell-meditation-timer/windows/StatisticsWindow.cpp +47 -23
@@ 5,24 5,35 @@

#include "MeditationMainWindow.hpp"

#include <ListView.hpp>
#include <common/data/StyleCommon.hpp>
#include <apps-common/ApplicationCommon.hpp>
#include <module-gui/gui/input/InputEvent.hpp>
#include <apps-common/InternalModel.hpp>

namespace
{
    constexpr auto height     = 400;
    constexpr auto width      = 380;
    constexpr auto top_margin = 41;
} // namespace
#include <module-gui/gui/widgets/SideListView.hpp>

namespace app::meditation
{
    using namespace gui;

    InputEvent transformKeyToKnobEvent(const InputEvent &inputEvent)
    {
        InputEvent newEvent{inputEvent};

        if (inputEvent.is(KeyCode::KEY_UP)) {
            newEvent.setKeyCode(gui::KeyCode::KEY_ENTER);
        }

        if (inputEvent.is(KeyCode::KEY_DOWN)) {
            newEvent.setKeyCode(KeyCode::KEY_RF);
        }

        return newEvent;
    }

    bool filterInputEvents(const gui::InputEvent &inputEvent)
    {
        return inputEvent.isShortRelease(KeyCode::KEY_ENTER);
    }

    StatisticsWindow::StatisticsWindow(app::ApplicationCommon *app,
                                       std::unique_ptr<app::meditation::contract::Presenter> presenter)
                                       std::unique_ptr<app::meditation::contract::StatisticsPresenter> presenter)
        : AppWindow(app, name), presenter{std::move(presenter)}
    {
        this->presenter->attach(this);


@@ 42,30 53,43 @@ namespace app::meditation
        header->setTitleVisibility(false);
        navBar->setVisible(false);

        list = new ListView(this,
                            style::window::default_left_margin,
                            top_margin,
                            width,
                            height,
                            presenter->getPagesProvider(),
                            listview::ScrollBarType::Fixed);
        list->setAlignment(Alignment(Alignment::Horizontal::Center, Alignment::Vertical::Center));
        sideListView = new gui::SideListView(
            this, 0U, 0U, this->getWidth(), this->getHeight(), presenter->getPagesProvider(), PageBarType::None);
        sideListView->setEdges(RectangleEdge::None);
        sideListView->setBoundaries(gui::Boundaries::Continuous);

        list->rebuildList();
        sideListView->rebuildList(listview::RebuildType::Full);

        setFocusItem(sideListView);
    }

    void StatisticsWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    {
        setFocusItem(list);
        setFocusItem(sideListView);
    }

    bool StatisticsWindow::onInput(const gui::InputEvent &inputEvent)
    {
        if (filterInputEvents(inputEvent)) {
            return true;
        }

        if (inputEvent.isShortRelease(KeyCode::KEY_RF)) {
            presenter->handleExit();
            return true;
        }

        if (sideListView->onInput(transformKeyToKnobEvent(inputEvent))) {
            return true;
        }

        return AppWindow::onInput(inputEvent);
    }

    void StatisticsWindow::onClose(CloseReason reason)
    {
        presenter->eraseProviderData();
        if (reason != CloseReason::Popup) {
            presenter->eraseProviderData();
        }
    }
} // namespace app::meditation

M products/BellHybrid/apps/application-bell-meditation-timer/windows/StatisticsWindow.hpp => products/BellHybrid/apps/application-bell-meditation-timer/windows/StatisticsWindow.hpp +4 -4
@@ 10,7 10,6 @@
namespace gui
{
    class SideListView;
    class ListView;
} // namespace gui

namespace app::meditation


@@ 19,7 18,8 @@ namespace app::meditation
    {
      public:
        static constexpr auto name = "MeditationStatisticsWindow";
        StatisticsWindow(app::ApplicationCommon *app, std::unique_ptr<app::meditation::contract::Presenter> presenter);
        StatisticsWindow(app::ApplicationCommon *app,
                         std::unique_ptr<app::meditation::contract::StatisticsPresenter> presenter);

        void buildInterface() override;
        void onBeforeShow(gui::ShowMode mode, gui::SwitchData *data) override;


@@ 28,7 28,7 @@ namespace app::meditation
        void rebuild() override;

      private:
        gui::ListView *list{};
        std::unique_ptr<app::meditation::contract::Presenter> presenter;
        gui::SideListView *sideListView{};
        std::unique_ptr<app::meditation::contract::StatisticsPresenter> presenter;
    };
} // namespace app::meditation