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