~aleteoryx/muditaos

8c2bef8acf8bfa41498e0d87a543ecc80451ddbe — rrandomsky 1 year, 9 months ago 4470113
[BH-1899] Focus timer basic

Added a basic version of the Focuse timer function.
This basic version allows the user to run the focus timer with
only the default parameters. Focus time is set at 25 minutes,
short break at 5 minutes for 10 focus intervals.
M products/BellHybrid/apps/application-bell-focus-timer/ApplicationFocusTimer.cpp => products/BellHybrid/apps/application-bell-focus-timer/ApplicationFocusTimer.cpp +10 -1
@@ 11,7 11,10 @@
#include "presenter/FocusSettingsPresenter.hpp"
#include "presenter/FocusTimerPresenter.hpp"

#include "common/models/TimeModel.hpp"
#include <common/models/AudioModel.hpp>
#include <common/windows/SessionPausedWindow.hpp>
#include <common/windows/BellFinishedWindow.hpp>
#include <system/messages/SentinelRegistrationMessage.hpp>

namespace app


@@ 63,11 66,17 @@ namespace app
        });

        windowsFactory.attach(focus::window::name::timer, [this](ApplicationCommon *app, const std::string &name) {
            auto timeModel = std::make_unique<app::TimeModel>();
            auto presenter = std::make_unique<app::focus::FocusTimerPresenter>(
                app, settings.get(), *batteryModel, *lowBatteryInfoModel);
                app, settings.get(), std::move(timeModel), *batteryModel, *lowBatteryInfoModel);
            return std::make_unique<focus::FocusTimerWindow>(app, std::move(presenter));
        });

        windowsFactory.attach(gui::window::session_paused::sessionPaused,
                              [](ApplicationCommon *app, const std::string &name) {
                                  return std::make_unique<gui::SessionPausedWindow>(app);
                              });

        attachPopups({gui::popup::ID::AlarmActivated,
                      gui::popup::ID::AlarmDeactivated,
                      gui::popup::ID::PowerOff,

M products/BellHybrid/apps/application-bell-focus-timer/data/FocusCommon.hpp => products/BellHybrid/apps/application-bell-focus-timer/data/FocusCommon.hpp +9 -0
@@ 4,6 4,9 @@
#pragma once

#include <AppWindowConstants.hpp>
#include <Paths.hpp>
#include <filesystem>
#include <string>

namespace app::focus
{


@@ 15,4 18,10 @@ namespace app::focus
        inline constexpr auto settings = "FocusTimerSettingsWindow";
        inline constexpr auto timer    = "FocusTimerSessionWindow";
    } // namespace window::name

    inline std::filesystem::path getFocusTimeAudioPath()
    {
        return paths::audio::proprietary() / paths::audio::focusTimer() / "FocusTimer_Gong.mp3";
    }

} // namespace app::focus

A products/BellHybrid/apps/application-bell-focus-timer/data/FocusTimerStyle.hpp => products/BellHybrid/apps/application-bell-focus-timer/data/FocusTimerStyle.hpp +72 -0
@@ 0,0 1,72 @@
// Copyright (c) 2017-2024, Mudita Sp. z.o.o. All rights reserved.
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#pragma once

#include <Style.hpp>
#include "widgets/BellBaseLayout.hpp"

namespace app::focusTimerStyle
{
    namespace runningStyle
    {
        namespace progress
        {
            inline constexpr auto radius                   = 192u;
            inline constexpr auto penWidth                 = 3u;
            inline constexpr auto verticalDeviationDegrees = 38u;
        } // namespace progress

        namespace timer
        {
            inline constexpr auto font      = style::window::font::supersizeme;
            inline constexpr auto maxSizeX  = 340u;
            inline constexpr auto maxSizeY  = 198u;
            inline constexpr auto marginTop = 19u;
        } // namespace timer

        namespace pauseIcon
        {
            inline constexpr auto image     = "big_pause";
            inline constexpr auto maxSizeX  = 203u;
            inline constexpr auto maxSizeY  = 203u;
            inline constexpr auto marginTop = timer::marginTop - (maxSizeY - timer::maxSizeY);
        } // namespace pauseIcon

        namespace ringIcon
        {
            inline constexpr auto image     = "big_bell_ringing";
            inline constexpr auto maxSizeX  = 220u;
            inline constexpr auto maxSizeY  = 203u;
            inline constexpr auto marginTop = timer::marginTop - (maxSizeY - timer::maxSizeY);
        } // namespace ringIcon

        namespace bottomDescription
        {
            inline constexpr auto marginTop = 38u;
            inline constexpr auto maxSizeX  = style::bell_base_layout::w;
            inline constexpr auto maxSizeY  = 42u;
            inline constexpr auto font      = style::window::font::verybig;
        } // namespace bottomDescription

        namespace clock
        {
            inline constexpr auto marginTop = 17u;
            inline constexpr auto maxSizeX  = 340u;
            inline constexpr auto maxSizeY  = 84u;
        } // namespace clock
    }     // namespace runningStyle

    namespace timerStyle
    {
        namespace text
        {
            inline constexpr auto font = style::window::font::supersizemelight;
        }

        namespace minute
        {
            inline constexpr auto font = style::window::font::largelight;
        }
    } // namespace timerStyle
} // namespace app::focusTimerStyle

M products/BellHybrid/apps/application-bell-focus-timer/presenter/FocusTimerPresenter.cpp => products/BellHybrid/apps/application-bell-focus-timer/presenter/FocusTimerPresenter.cpp +173 -2
@@ 3,13 3,184 @@

#include "FocusTimerPresenter.hpp"

#include "FocusCommon.hpp"
#include "Application.hpp"
#include "widgets/ProgressTimer.hpp"

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

namespace
{
    static constexpr std::chrono::milliseconds betweenSessionDelayTime{5000};
} // namespace

namespace app::focus
{

    FocusTimerPresenter::FocusTimerPresenter(app::ApplicationCommon *app,
                                             settings::Settings *settings,
                                             std::unique_ptr<AbstractTimeModel> timeModel,
                                             AbstractBatteryModel &batteryModel,
                                             AbstractLowBatteryInfoModel &lowBatteryInfoModel)
        : app{app}, settings{settings}, batteryModel{batteryModel}, lowBatteryInfoModel{lowBatteryInfoModel}
    {}
        : app{app}, settings{settings}, batteryModel{batteryModel},
          lowBatteryInfoModel{lowBatteryInfoModel}, timeModel{std::move(timeModel)}
    {
        //        focusSessionDuration = std::chrono::minutes{
        //                utils::getNumericValue<int>(settings->getValue(focusDBRecordName,
        //                settings::SettingsScope::AppLocal))};
        //        shortBreakDuration = std::chrono::minutes{
        //                utils::getNumericValue<int>(settings->getValue(focusDBRecordName,
        //                settings::SettingsScope::AppLocal))};
        //        focusSessionsLeft =
        //                utils::getNumericValue<int>(settings->getValue(focusDBRecordName,
        //                settings::SettingsScope::AppLocal));

        focusSessionDuration = std::chrono::minutes{25};
        shortBreakDuration   = std::chrono::minutes{5};

        allFocusSessionsCount = 10;
        focusSessionsLeft     = allFocusSessionsCount;

        betweenSessionTimer = sys::TimerFactory::createSingleShotTimer(
            app, "betweenSessionTimer", betweenSessionDelayTime, [this](sys::Timer &) { executeNextStep(); });
    }

    void FocusTimerPresenter::setTimer(std::unique_ptr<app::TimerWithCallbacks> &&_timer)
    {
        timer = std::move(_timer);
        timer->registerOnFinishedCallback([this]() { executeNextStep(); });
    }

    void FocusTimerPresenter::handleUpdateTimeEvent()
    {
        getView()->setTime(timeModel->getCurrentTime());
    }

    void FocusTimerPresenter::start()
    {
        static_cast<app::Application *>(app)->suspendIdleTimer();
        startTime();
    }

    void FocusTimerPresenter::stop()
    {
        finish();
    }

    bool FocusTimerPresenter::isTimerStopped()
    {
        return timer->isStopped();
    }

    bool FocusTimerPresenter::isAllSessionsFinished()
    {
        return currentTimerPhase == FocusTimerPhase::AllFocusSessionsEnded;
    }

    void FocusTimerPresenter::pause()
    {
        betweenSessionTimer.stop();
        timer->stop();
        getView()->pause();
    }

    void FocusTimerPresenter::resume()
    {
        isMiddleTimeBetweenBreakAndFocus() ? executeNextStep() : timer->start();
        getView()->resume();
    }

    void FocusTimerPresenter::abandon()
    {
        finish();
    }

    void FocusTimerPresenter::finish()
    {
        timer->stop();
        betweenSessionTimer.stop();
        app->switchWindow(focus::window::name::main);
    }

    void FocusTimerPresenter::executeNextStep()
    {
        switch (currentTimerPhase) {
        case FocusTimerPhase::FocusTime:
            focusSessionsLeft--;
            if (focusSessionsLeft == 0) {
                currentTimerPhase = FocusTimerPhase::AllFocusSessionsEnded;
                getView()->onAllFocusSessionsFinished();
                startTime();
            }
            else {
                currentTimerPhase = FocusTimerPhase::FocusTimeEnded;
                getView()->onFocusSessionFinished();
                startTime();
            }
            break;

        case FocusTimerPhase::FocusTimeEnded:
            currentTimerPhase = FocusTimerPhase::ShortBreakTime;
            getView()->onShortBreakStarted();
            startTime();
            break;

        case FocusTimerPhase::ShortBreakTime:
            currentTimerPhase = FocusTimerPhase::ShortBreakTimeEnded;
            getView()->onShortBreakFinished();
            startTime();
            break;

        case FocusTimerPhase::ShortBreakTimeEnded:
            currentTimerPhase = FocusTimerPhase::FocusTime;
            getView()->onFocusSessionStarted();
            startTime();
            break;

        case FocusTimerPhase::AllFocusSessionsEnded:
            finish();
            break;
        }
    }

    void FocusTimerPresenter::onBeforeShow()
    {
        getView()->setTimeFormat(timeModel->getTimeFormat());
    }

    void FocusTimerPresenter::startTime()
    {
        switch (currentTimerPhase) {
        case FocusTimerPhase::FocusTime:
            timer->reset(std::chrono::minutes(focusSessionDuration));
            timer->start();
            break;

        case FocusTimerPhase::ShortBreakTime:
            timer->reset(std::chrono::minutes(shortBreakDuration));
            timer->start();
            break;

        case FocusTimerPhase::FocusTimeEnded:
        case FocusTimerPhase::ShortBreakTimeEnded:
        case FocusTimerPhase::AllFocusSessionsEnded:
            betweenSessionTimer.start();
            break;
        }
    }

    bool FocusTimerPresenter::isMiddleTimeBetweenBreakAndFocus()
    {
        switch (currentTimerPhase) {
        case FocusTimerPhase::FocusTimeEnded:
        case FocusTimerPhase::ShortBreakTimeEnded:
            return true;
        default:
            return false;
        }
    }

} // namespace app::focus

M products/BellHybrid/apps/application-bell-focus-timer/presenter/FocusTimerPresenter.hpp => products/BellHybrid/apps/application-bell-focus-timer/presenter/FocusTimerPresenter.hpp +65 -2
@@ 7,6 7,15 @@
#include <apps-common/ApplicationCommon.hpp>
#include <common/models/BatteryModel.hpp>
#include <common/models/LowBatteryInfoModel.hpp>
#include <apps-common/widgets/TimerWithCallbacks.hpp>
#include <time/time_locale.hpp>
#include "widgets/ProgressTimer.hpp"

namespace app
{
    class AbstractTimeModel;
    class ApplicationCommon;
} // namespace app

namespace app::focus
{


@@ 16,28 25,82 @@ namespace app::focus
        class View
        {
          public:
            virtual ~View() noexcept = default;
            virtual ~View() noexcept                                        = default;
            virtual void onAllFocusSessionsFinished()                       = 0;
            virtual void onFocusSessionStarted()                            = 0;
            virtual void onFocusSessionFinished()                           = 0;
            virtual void onShortBreakStarted()                              = 0;
            virtual void onShortBreakFinished()                             = 0;
            virtual void setTime(std::time_t newTime)                       = 0;
            virtual void setTimeFormat(utils::time::Locale::TimeFormat fmt) = 0;
            virtual void pause()                                            = 0;
            virtual void resume()                                           = 0;
        };

        class Presenter : public BasePresenter<View>
        {
          public:
            virtual ~Presenter() noexcept = default;
            virtual ~Presenter() noexcept                                           = default;
            virtual void setTimer(std::unique_ptr<app::TimerWithCallbacks> &&timer) = 0;
            virtual void handleUpdateTimeEvent()                                    = 0;
            virtual bool isTimerStopped()                                           = 0;
            virtual bool isAllSessionsFinished()                                    = 0;
            virtual void start()                                                    = 0;
            virtual void stop()                                                     = 0;
            virtual void pause()                                                    = 0;
            virtual void resume()                                                   = 0;
            virtual void abandon()                                                  = 0;
            virtual void finish()                                                   = 0;
            virtual void onBeforeShow()                                             = 0;
        };
    };

    class FocusTimerPresenter : public FocusTimerContract::Presenter
    {
      public:
        enum class FocusTimerPhase
        {
            FocusTime,
            FocusTimeEnded,
            ShortBreakTime,
            ShortBreakTimeEnded,
            AllFocusSessionsEnded
        };
        FocusTimerPresenter(app::ApplicationCommon *app,
                            settings::Settings *settings,
                            std::unique_ptr<AbstractTimeModel> timeModel,
                            AbstractBatteryModel &batteryModel,
                            AbstractLowBatteryInfoModel &lowBatteryInfoModel);

        void setTimer(std::unique_ptr<app::TimerWithCallbacks> &&_timer) override;
        void handleUpdateTimeEvent() override;
        bool isTimerStopped() override;
        bool isAllSessionsFinished() override;
        void start() override;
        void stop() override;
        void pause() override;
        void resume() override;
        void abandon() override;
        void finish() override;
        void onBeforeShow() override;
        void startTime();

      private:
        app::ApplicationCommon *app{nullptr};
        settings::Settings *settings{nullptr};
        AbstractBatteryModel &batteryModel;
        AbstractLowBatteryInfoModel &lowBatteryInfoModel;

        std::unique_ptr<app::TimerWithCallbacks> timer;
        sys::TimerHandle betweenSessionTimer;
        std::unique_ptr<AbstractTimeModel> timeModel;
        std::chrono::minutes focusSessionDuration;
        std::chrono::minutes shortBreakDuration;
        std::uint32_t allFocusSessionsCount;
        std::uint32_t focusSessionsLeft;
        FocusTimerPhase currentTimerPhase = FocusTimerPhase::FocusTime;

        void executeNextStep();
        bool isMiddleTimeBetweenBreakAndFocus();
    };
} // namespace app::focus

M products/BellHybrid/apps/application-bell-focus-timer/windows/FocusTimerWindow.cpp => products/BellHybrid/apps/application-bell-focus-timer/windows/FocusTimerWindow.cpp +223 -0
@@ 2,10 2,27 @@
// For licensing, see https://github.com/mudita/MuditaOS/LICENSE.md

#include "FocusTimerWindow.hpp"
#include <data/FocusTimerStyle.hpp>

#include <Application.hpp>
#include <products/BellHybrid/services/audio/include/audio/AudioMessage.hpp>
#include <apps-common/widgets/BellBaseLayout.hpp>
#include <apps-common/ApplicationCommon.hpp>
#include <apps-common/widgets/ProgressTimerWithBarGraphAndCounter.hpp>
#include <module-gui/gui/input/InputEvent.hpp>

namespace
{
    constexpr auto focusTimerProgressTimerName{"FocusProgressTimer"};
    constexpr std::chrono::seconds focusTimerProgressTimerPeriod{1};
    constexpr auto focusTimerProgressMode{app::ProgressCountdownMode::Increasing};
    constexpr auto focusTimerText{"Focus timer"};
    constexpr auto timeToFocusText{"Time to focus"};
    constexpr auto shortBreakTimeText{"Short break"};
    constexpr auto timeForBreakText{"Time for a break"};
    constexpr auto endOfAllSessionText{"Well done!"};
} // namespace

namespace app::focus
{
    FocusTimerWindow::FocusTimerWindow(app::ApplicationCommon *app,


@@ 20,10 37,216 @@ namespace app::focus
    void FocusTimerWindow::buildInterface()
    {
        AppWindow::buildInterface();

        statusBar->setVisible(false);
        header->setTitleVisibility(false);
        navBar->setVisible(false);

        buildLayout();
        configureTimer();
    }

    void FocusTimerWindow::buildLayout()
    {
        using namespace app::focusTimerStyle;
        const auto progressArcRadius = runningStyle::progress::radius;
        const auto progressArcWidth  = runningStyle::progress::penWidth;
        const auto arcStartAngle =
            -90 - runningStyle::progress::verticalDeviationDegrees; // -90 to start drawing the circle from top
        const auto arcSweepAngle    = 360 - (2 * runningStyle::progress::verticalDeviationDegrees);
        const auto arcProgressSteps = 1000;

        gui::Arc::ShapeParams arcParams;
        arcParams.setCenterPoint(gui::Point(getWidth() / 2, getHeight() / 2))
            .setRadius(progressArcRadius)
            .setStartAngle(arcStartAngle)
            .setSweepAngle(arcSweepAngle)
            .setPenWidth(progressArcWidth)
            .setBorderColor(gui::ColorFullBlack);

        progress = new gui::ArcProgressBar(this,
                                           arcParams,
                                           gui::ArcProgressBar::ProgressDirection::CounterClockwise,
                                           gui::ArcProgressBar::ProgressChange::DecrementFromFull);
        progress->setMaximum(arcProgressSteps);

        mainVBox = new gui::VBox(this, 0, 0, style::window_width, style::window_height);

        clock = new gui::BellStatusClock(mainVBox);
        clock->setMaximumSize(runningStyle::clock::maxSizeX, runningStyle::clock::maxSizeY);
        clock->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));
        clock->setMargins(gui::Margins(0, runningStyle::clock::marginTop, 0, 0));

        timer = new gui::TimeFixedWidget(mainVBox, 0, 0, runningStyle::timer::maxSizeX, runningStyle::timer::maxSizeY);
        timer->setFontAndDimensions(runningStyle::timer::font);
        timer->setMinimumSize(runningStyle::timer::maxSizeX, runningStyle::timer::maxSizeY);
        timer->setMargins(gui::Margins(0, runningStyle::timer::marginTop, 0, 0));
        timer->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Center));

        iconPause = new gui::Icon(mainVBox, 0, 0, 0, 0, {}, {});
        iconPause->setMinimumSize(runningStyle::pauseIcon::maxSizeX, runningStyle::pauseIcon::maxSizeY);
        iconPause->setMargins(gui::Margins(0, runningStyle::pauseIcon::marginTop, 0, 0));
        iconPause->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Top));
        iconPause->image->set(runningStyle::pauseIcon::image, gui::ImageTypeSpecifier::W_G);
        iconPause->setVisible(false);

        iconRing = new gui::Icon(mainVBox, 0, 0, 0, 0, {}, {});
        iconRing->setMinimumSize(runningStyle::ringIcon::maxSizeX, runningStyle::ringIcon::maxSizeY);
        iconRing->setMargins(gui::Margins(0, runningStyle::ringIcon::marginTop, 0, 0));
        iconRing->setAlignment(gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Top));
        iconRing->image->set(runningStyle::ringIcon::image, gui::ImageTypeSpecifier::W_G);
        iconRing->setVisible(false);

        bottomDescription = new gui::TextFixedSize(mainVBox, 0, 0, 250, 80);
        bottomDescription->setMaximumSize(runningStyle::bottomDescription::maxSizeX,
                                          runningStyle::bottomDescription::maxSizeY);
        bottomDescription->setFont(runningStyle::bottomDescription::font);
        bottomDescription->setMargins(gui::Margins(0, 0, 0, 0));
        bottomDescription->activeItem = false;
        bottomDescription->setAlignment(
            gui::Alignment(gui::Alignment::Horizontal::Center, gui::Alignment::Vertical::Top));
        bottomDescription->setRichText(focusTimerText);
        bottomDescription->drawUnderline(false);
        bottomDescription->setVisible(true);

        mainVBox->resizeItems();
    }

    void FocusTimerWindow::onBeforeShow(gui::ShowMode mode, gui::SwitchData *data)
    {
        AppWindow::onBeforeShow(mode, data);
        presenter->onBeforeShow();
        updateTime();
        if (mode == gui::ShowMode::GUI_SHOW_INIT) {
            playGong();
            presenter->start();
        }
    }

    bool FocusTimerWindow::onInput(const gui::InputEvent &inputEvent)
    {
        if (inputEvent.isShortRelease(gui::KeyCode::KEY_ENTER)) {
            if (presenter->isAllSessionsFinished()) {
                presenter->finish();
            }
            if (presenter->isTimerStopped()) {
                presenter->resume();
            }
            else {
                presenter->pause();
            }
            return true;
        }
        if (inputEvent.isShortRelease(gui::KeyCode::KEY_RF)) {
            static_cast<app::Application *>(application)->resumeIdleTimer();
            presenter->abandon();
            return true;
        }

        return AppWindow::onInput(inputEvent);
    }

    void FocusTimerWindow::onAllFocusSessionsFinished()
    {
        timer->setVisible(false);
        iconPause->setVisible(false);
        iconRing->setVisible(true);
        bottomDescription->setVisible(true);
        bottomDescription->setText(endOfAllSessionText);
        mainVBox->resizeItems();
        playGong();
    }

    void FocusTimerWindow::onFocusSessionStarted()
    {
        timer->setVisible(true);
        iconPause->setVisible(false);
        iconRing->setVisible(false);
        bottomDescription->setVisible(true);
        bottomDescription->setText(focusTimerText);
        mainVBox->resizeItems();
    }

    void FocusTimerWindow::onFocusSessionFinished()
    {
        timer->setVisible(false);
        iconPause->setVisible(false);
        iconRing->setVisible(true);
        bottomDescription->setVisible(true);
        bottomDescription->setText(timeForBreakText);
        mainVBox->resizeItems();
        playGong();
    }

    void FocusTimerWindow::onShortBreakStarted()
    {
        timer->setVisible(true);
        iconPause->setVisible(false);
        iconRing->setVisible(false);
        bottomDescription->setVisible(true);
        bottomDescription->setText(shortBreakTimeText);
        mainVBox->resizeItems();
    }

    void FocusTimerWindow::onShortBreakFinished()
    {
        timer->setVisible(false);
        iconPause->setVisible(false);
        iconRing->setVisible(true);
        bottomDescription->setVisible(true);
        bottomDescription->setText(timeToFocusText);
        mainVBox->resizeItems();
        playGong();
    }

    void FocusTimerWindow::pause()
    {
        timer->setVisible(false);
        iconPause->setVisible(true);
        iconRing->setVisible(false);
        mainVBox->resizeItems();
    }

    void FocusTimerWindow::resume()
    {
        timer->setVisible(true);
        iconPause->setVisible(false);
        iconRing->setVisible(false);
        mainVBox->resizeItems();
    }

    void FocusTimerWindow::configureTimer()
    {
        auto progressTimer = std::make_unique<app::ProgressTimerWithBarGraphAndCounter>(
            application, *this, focusTimerProgressTimerName, focusTimerProgressTimerPeriod, focusTimerProgressMode);
        progressTimer->attach(progress);
        progressTimer->attach(timer);
        presenter->setTimer(std::move(progressTimer));
    }

    void FocusTimerWindow::setTime(std::time_t newTime)
    {
        clock->setTime(newTime);
        clock->setTimeFormatSpinnerVisibility(true);
    }

    void FocusTimerWindow::setTimeFormat(utils::time::Locale::TimeFormat fmt)
    {
        clock->setTimeFormat(fmt);
    }

    gui::RefreshModes FocusTimerWindow::updateTime()
    {
        if (presenter != nullptr) {
            presenter->handleUpdateTimeEvent();
        }
        return gui::RefreshModes::GUI_REFRESH_FAST;
    }

    void FocusTimerWindow::playGong()
    {
        auto msg = std::make_shared<service::AudioStartPlaybackRequest>(app::focus::getFocusTimeAudioPath(),
                                                                        audio::PlaybackType::Meditation);
        application->bus.sendUnicast(std::move(msg), service::audioServiceName);
    }
} // namespace app::focus

M products/BellHybrid/apps/application-bell-focus-timer/windows/FocusTimerWindow.hpp => products/BellHybrid/apps/application-bell-focus-timer/windows/FocusTimerWindow.hpp +29 -0
@@ 6,6 6,11 @@
#include "data/FocusCommon.hpp"
#include "presenter/FocusTimerPresenter.hpp"

#include <Text.hpp>
#include <apps-common/widgets/BarGraph.hpp>
#include <apps-common/widgets/TimeFixedWidget.hpp>
#include <common/widgets/BellStatusClock.hpp>
#include <gui/widgets/Icon.hpp>
#include <apps-common/windows/AppWindow.hpp>

namespace app::focus


@@ 17,10 22,34 @@ namespace app::focus
                         std::unique_ptr<FocusTimerContract::Presenter> &&windowPresenter,
                         const std::string &name = window::name::timer);

        void onBeforeShow(gui::ShowMode mode, gui::SwitchData *data) override;
        void buildInterface() override;
        bool onInput(const gui::InputEvent &inputEvent) override;
        void onAllFocusSessionsFinished() override;
        void onFocusSessionStarted() override;
        void onFocusSessionFinished() override;
        void onShortBreakStarted() override;
        void onShortBreakFinished() override;
        void pause() override;
        void resume() override;

      private:
        std::unique_ptr<FocusTimerContract::Presenter> presenter;
        gui::VBox *mainVBox                   = nullptr;
        gui::ArcProgressBar *progress         = nullptr;
        gui::TimeFixedWidget *timer           = nullptr;
        gui::TextFixedSize *bottomDescription = nullptr;
        gui::Icon *iconPause                  = nullptr;
        gui::Icon *iconRing                   = nullptr;
        gui::BellStatusClock *clock           = nullptr;

        void setTime(std::time_t newTime) override;
        void setTimeFormat(utils::time::Locale::TimeFormat fmt) override;
        gui::RefreshModes updateTime() override;

        void buildLayout();
        void configureTimer();

        void playGong();
    };
} // namespace app::focus

M products/BellHybrid/assets/assets_common.json => products/BellHybrid/assets/assets_common.json +1 -0
@@ 86,6 86,7 @@
        {"name": "release_audio.tgz", "tarfile" :"./image/assets/audio/bell/bg_sounds/Woodland_Ambiance.mp3", "output": "assets/audio/relaxation/Woodland_Ambiance.mp3"},
        {"name": "release_audio.tgz", "tarfile" :"./image/assets/audio/bell/meditation/Meditation_Gong.mp3", "output": "assets/audio/meditation/Meditation_Gong.mp3"},
        {"name": "release_audio.tgz", "tarfile" :"./image/assets/audio/bell/meditation/Meditation_End.mp3", "output": "assets/audio/meditation/Meditation_End.mp3"},
        {"name": "release_audio.tgz", "tarfile" :"./image/assets/audio/bell/focus_timer/FocusTimer_Gong.mp3", "output": "assets/audio/focus_timer/FocusTimer_Gong.mp3"},
        {"name": "release_audio.tgz", "tarfile" :"./image/assets/audio/bell/prewakeup/Joyful_Awakening.mp3", "output": "assets/audio/prewakeup/Joyful_Awakening.mp3"},
        {"name": "release_audio.tgz", "tarfile" :"./image/assets/audio/bell/prewakeup/Morning_Spirit.mp3", "output": "assets/audio/prewakeup/Morning_Spirit.mp3"},
        {"name": "release_audio.tgz", "tarfile" :"./image/assets/audio/bell/prewakeup/Radiant_Morning.mp3", "output": "assets/audio/prewakeup/Radiant_Morning.mp3"},

M products/BellHybrid/paths/Paths.cpp => products/BellHybrid/paths/Paths.cpp +5 -0
@@ 44,6 44,11 @@ std::filesystem::path paths::audio::meditation() noexcept
    return "meditation";
}

std::filesystem::path paths::audio::focusTimer() noexcept
{
    return "focus_timer";
}

std::filesystem::path paths::audio::colorOfNoises() noexcept
{
    return "noises";

M products/BellHybrid/paths/Paths.hpp => products/BellHybrid/paths/Paths.hpp +1 -0
@@ 17,6 17,7 @@ namespace paths
        std::filesystem::path bedtimeReminder() noexcept;
        std::filesystem::path relaxation() noexcept;
        std::filesystem::path meditation() noexcept;
        std::filesystem::path focusTimer() noexcept;
        std::filesystem::path colorOfNoises() noexcept;
    } // namespace audio
} // namespace paths