diff --git a/src/components/timer/TimerController.cpp b/src/components/timer/TimerController.cpp index ea945213ba..da47cde44b 100644 --- a/src/components/timer/TimerController.cpp +++ b/src/components/timer/TimerController.cpp @@ -16,24 +16,30 @@ void TimerController::Init(Pinetime::System::SystemTask* systemTask) { void TimerController::StartTimer(uint32_t duration) { xTimerChangePeriod(timer, pdMS_TO_TICKS(duration), 0); xTimerStart(timer, 0); + state = TimerState::Running; } -uint32_t TimerController::GetTimeRemaining() { - if (IsRunning()) { - TickType_t remainingTime = xTimerGetExpiryTime(timer) - xTaskGetTickCount(); - return (remainingTime * 1000 / configTICK_RATE_HZ); +uint32_t TimerController::GetTimeRemainingMs() { + TickType_t remainingTime = 0; + switch (state) { + case TimerState::Stopped: + break; + case TimerState::Running: + remainingTime = xTimerGetExpiryTime(timer) - xTaskGetTickCount(); + break; + case TimerState::Finished: + remainingTime = xTaskGetTickCount() - xTimerGetExpiryTime(timer); + break; } - return 0; + return (remainingTime * 1000 / configTICK_RATE_HZ); } void TimerController::StopTimer() { xTimerStop(timer, 0); -} - -bool TimerController::IsRunning() { - return (xTimerIsTimerActive(timer) == pdTRUE); + state = TimerState::Stopped; } void TimerController::OnTimerEnd() { + state = TimerState::Finished; systemTask->PushMessage(System::Messages::OnTimerDone); } diff --git a/src/components/timer/TimerController.h b/src/components/timer/TimerController.h index 20f07e8210..a26ccfbb3c 100644 --- a/src/components/timer/TimerController.h +++ b/src/components/timer/TimerController.h @@ -20,15 +20,20 @@ namespace Pinetime { void StopTimer(); - uint32_t GetTimeRemaining(); - - bool IsRunning(); + uint32_t GetTimeRemainingMs(); void OnTimerEnd(); + enum class TimerState { Stopped, Running, Finished }; + + TimerState State() const { + return state; + } + private: System::SystemTask* systemTask = nullptr; TimerHandle_t timer; + TimerState state = TimerState::Stopped; }; } } diff --git a/src/displayapp/DisplayApp.cpp b/src/displayapp/DisplayApp.cpp index 69830eadbd..7e6c1d59bf 100644 --- a/src/displayapp/DisplayApp.cpp +++ b/src/displayapp/DisplayApp.cpp @@ -200,10 +200,15 @@ void DisplayApp::Refresh() { case Messages::NewNotification: LoadNewScreen(Apps::NotificationsPreview, DisplayApp::FullRefreshDirections::Down); break; + case Messages::GoToClock: + if (currentApp != Apps::Clock) { + LoadNewScreen(Apps::Clock, DisplayApp::FullRefreshDirections::None); + } + break; case Messages::TimerDone: if (currentApp == Apps::Timer) { auto* timer = static_cast(currentScreen.get()); - timer->Reset(); + timer->SetTimerFinished(); } else { LoadNewScreen(Apps::Timer, DisplayApp::FullRefreshDirections::Up); } @@ -396,7 +401,7 @@ void DisplayApp::LoadScreen(Apps app, DisplayApp::FullRefreshDirections directio Screens::Notifications::Modes::Preview); break; case Apps::Timer: - currentScreen = std::make_unique(this, timerController); + currentScreen = std::make_unique(this, timerController, *systemTask); break; case Apps::Alarm: currentScreen = std::make_unique(this, alarmController, settingsController.GetClockType(), *systemTask); diff --git a/src/displayapp/Messages.h b/src/displayapp/Messages.h index afa7709a9d..759cee3c35 100644 --- a/src/displayapp/Messages.h +++ b/src/displayapp/Messages.h @@ -7,6 +7,7 @@ namespace Pinetime { enum class Messages : uint8_t { GoToSleep, GoToRunning, + GoToClock, UpdateDateTime, UpdateBleConnection, TouchEvent, diff --git a/src/displayapp/screens/Timer.cpp b/src/displayapp/screens/Timer.cpp index 136d6b52af..c0a9ab9bb1 100644 --- a/src/displayapp/screens/Timer.cpp +++ b/src/displayapp/screens/Timer.cpp @@ -5,6 +5,9 @@ #include using namespace Pinetime::Applications::Screens; +using Pinetime::Controllers::TimerController; + +#define TIMER_RING_DURATION_MSEC (10 * 1000) static void btnEventHandler(lv_obj_t* obj, lv_event_t event) { auto* screen = static_cast(obj->user_data); @@ -17,9 +20,14 @@ static void btnEventHandler(lv_obj_t* obj, lv_event_t event) { } } -Timer::Timer(DisplayApp* app, Controllers::TimerController& timerController) : Screen(app), timerController {timerController} { +static void StopRingingTaskCallback(lv_task_t* task) { + auto* screen = static_cast(task->user_data); + screen->StopRinging(); +} - lv_obj_t* colonLabel = lv_label_create(lv_scr_act(), nullptr); +Timer::Timer(DisplayApp* app, TimerController& timerController, System::SystemTask& systemTask) + : Screen(app), timerController {timerController}, systemTask {systemTask} { + colonLabel = lv_label_create(lv_scr_act(), nullptr); lv_obj_set_style_local_text_font(colonLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, &jetbrains_mono_76); lv_obj_set_style_local_text_color(colonLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, LV_COLOR_WHITE); lv_label_set_text_static(colonLabel, ":"); @@ -62,10 +70,16 @@ Timer::Timer(DisplayApp* app, Controllers::TimerController& timerController) : S txtPlayPause = lv_label_create(lv_scr_act(), nullptr); lv_obj_align(txtPlayPause, btnPlayPause, LV_ALIGN_CENTER, 0, 0); - if (timerController.IsRunning()) { - SetTimerRunning(); - } else { - SetTimerStopped(); + switch (timerController.State()) { + case TimerController::TimerState::Running: + SetTimerRunning(); + break; + case TimerController::TimerState::Stopped: + SetTimerStopped(); + break; + case TimerController::TimerState::Finished: + SetTimerFinished(); + break; } taskRefresh = lv_task_create(RefreshTaskCallback, LV_DISP_DEF_REFR_PERIOD, LV_TASK_PRIO_MID, this); @@ -84,9 +98,11 @@ void Timer::ButtonPressed() { void Timer::MaskReset() { buttonPressing = false; // A click event is processed before a release event, - // so the release event would override the "Pause" text without this check - if (!timerController.IsRunning()) { + // so the release event would override the text without this check + if (timerController.State() == TimerController::TimerState::Stopped) { lv_label_set_text_static(txtPlayPause, "Start"); + } else if (timerController.State() == TimerController::TimerState::Finished) { + lv_label_set_text_static(txtPlayPause, "Stop"); } maskPosition = 0; UpdateMask(); @@ -103,45 +119,100 @@ void Timer::UpdateMask() { } void Timer::Refresh() { - if (timerController.IsRunning()) { - uint32_t seconds = timerController.GetTimeRemaining() / 1000; + if (timerController.State() != TimerController::TimerState::Stopped) { + // Update counters if running or finished + uint32_t seconds = timerController.GetTimeRemainingMs() / 1000; minuteCounter.SetValue(seconds / 60); secondCounter.SetValue(seconds % 60); - } else if (buttonPressing && xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) { + } + if (timerController.State() != TimerController::TimerState::Running && buttonPressing && + xTaskGetTickCount() > pressTime + pdMS_TO_TICKS(150)) { + // Support long-pressing the button if stopped or finished lv_label_set_text_static(txtPlayPause, "Reset"); maskPosition += 15; if (maskPosition > 240) { MaskReset(); - Reset(); + if (timerController.State() == TimerController::TimerState::Stopped) { + Reset(); + } else { + StopRinging(); + timerController.StopTimer(); + systemTask.PushMessage(System::Messages::GoToClock); + } } else { UpdateMask(); } } } +void Timer::UpdateColor() { + lv_color_t color = timerController.State() == TimerController::TimerState::Finished ? LV_COLOR_RED : LV_COLOR_WHITE; + lv_obj_set_style_local_text_color(colonLabel, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color); + minuteCounter.SetTextColor(color); + secondCounter.SetTextColor(color); +} + void Timer::SetTimerRunning() { minuteCounter.HideControls(); secondCounter.HideControls(); lv_label_set_text_static(txtPlayPause, "Pause"); + MaskReset(); + UpdateColor(); +} + +void Timer::SetTimerFinished() { + minuteCounter.HideControls(); + secondCounter.HideControls(); + lv_label_set_text_static(txtPlayPause, "Stop"); + UpdateColor(); + uint32_t msecRemaining = timerController.GetTimeRemainingMs(); + if (msecRemaining < TIMER_RING_DURATION_MSEC) { + taskStopRinging = + lv_task_create(StopRingingTaskCallback, pdMS_TO_TICKS(TIMER_RING_DURATION_MSEC - msecRemaining), LV_TASK_PRIO_MID, this); + systemTask.PushMessage(System::Messages::DisableSleeping); + } +} + +bool Timer::StopRinging() { + if (taskStopRinging == nullptr) { + return false; + } + lv_task_del(taskStopRinging); + taskStopRinging = nullptr; + systemTask.PushMessage(System::Messages::StopRinging); + systemTask.PushMessage(System::Messages::EnableSleeping); + return true; } void Timer::SetTimerStopped() { minuteCounter.ShowControls(); secondCounter.ShowControls(); lv_label_set_text_static(txtPlayPause, "Start"); + UpdateColor(); } void Timer::ToggleRunning() { - if (timerController.IsRunning()) { - uint32_t seconds = timerController.GetTimeRemaining() / 1000; - minuteCounter.SetValue(seconds / 60); - secondCounter.SetValue(seconds % 60); - timerController.StopTimer(); - SetTimerStopped(); - } else if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) { - timerController.StartTimer((secondCounter.GetValue() + minuteCounter.GetValue() * 60) * 1000); - Refresh(); - SetTimerRunning(); + switch (timerController.State()) { + case TimerController::TimerState::Running: { + uint32_t seconds = timerController.GetTimeRemainingMs() / 1000; + minuteCounter.SetValue(seconds / 60); + secondCounter.SetValue(seconds % 60); + } + timerController.StopTimer(); + SetTimerStopped(); + break; + case TimerController::TimerState::Finished: + StopRinging(); + timerController.StopTimer(); + Reset(); + break; + case TimerController::TimerState::Stopped: + if (secondCounter.GetValue() + minuteCounter.GetValue() > 0) { + timerController.StartTimer((secondCounter.GetValue() + minuteCounter.GetValue() * 60) * 1000); + Refresh(); + SetTimerRunning(); + } + break; } } @@ -150,3 +221,14 @@ void Timer::Reset() { secondCounter.SetValue(0); SetTimerStopped(); } + +bool Timer::OnButtonPushed() { + return StopRinging(); +} + +bool Timer::OnTouchEvent(TouchEvents event) { + if (event == TouchEvents::SwipeDown) { + StopRinging(); + } + return false; +} \ No newline at end of file diff --git a/src/displayapp/screens/Timer.h b/src/displayapp/screens/Timer.h index 306281d778..906f389e8e 100644 --- a/src/displayapp/screens/Timer.h +++ b/src/displayapp/screens/Timer.h @@ -12,19 +12,25 @@ namespace Pinetime::Applications::Screens { class Timer : public Screen { public: - Timer(DisplayApp* app, Controllers::TimerController& timerController); + Timer(DisplayApp* app, Controllers::TimerController& timerController, System::SystemTask& systemTask); ~Timer() override; void Refresh() override; void Reset(); void ToggleRunning(); void ButtonPressed(); void MaskReset(); + void SetTimerFinished(); + bool StopRinging(); + bool OnButtonPushed() override; + bool OnTouchEvent(TouchEvents event) override; private: void SetTimerRunning(); void SetTimerStopped(); void UpdateMask(); + void UpdateColor(); Controllers::TimerController& timerController; + System::SystemTask& systemTask; lv_obj_t* btnPlayPause; lv_obj_t* txtPlayPause; @@ -36,7 +42,9 @@ namespace Pinetime::Applications::Screens { lv_task_t* taskRefresh; Widgets::Counter minuteCounter = Widgets::Counter(0, 59, jetbrains_mono_76); + lv_obj_t* colonLabel; Widgets::Counter secondCounter = Widgets::Counter(0, 59, jetbrains_mono_76); + lv_task_t* taskStopRinging = nullptr; bool buttonPressing = false; lv_coord_t maskPosition = 0; diff --git a/src/displayapp/widgets/Counter.cpp b/src/displayapp/widgets/Counter.cpp index b486e3727f..512211a922 100644 --- a/src/displayapp/widgets/Counter.cpp +++ b/src/displayapp/widgets/Counter.cpp @@ -120,6 +120,10 @@ void Counter::SetValueChangedEventCallback(void* userData, void (*handler)(void* this->ValueChangedHandler = handler; } +void Counter::SetTextColor(lv_color_t color) { + lv_obj_set_style_local_text_color(number, LV_LABEL_PART_MAIN, LV_STATE_DEFAULT, color); +} + void Counter::Create() { counterContainer = lv_obj_create(lv_scr_act(), nullptr); lv_obj_set_style_local_bg_color(counterContainer, LV_BTN_PART_MAIN, LV_STATE_DEFAULT, Colors::bgAlt); diff --git a/src/displayapp/widgets/Counter.h b/src/displayapp/widgets/Counter.h index 825860b8d9..dbf41b09fb 100644 --- a/src/displayapp/widgets/Counter.h +++ b/src/displayapp/widgets/Counter.h @@ -18,6 +18,7 @@ namespace Pinetime { void EnableMonthMode(); void SetMax(int newMax); void SetValueChangedEventCallback(void* userData, void (*handler)(void* userData)); + void SetTextColor(lv_color_t color); int GetValue() const { return value; diff --git a/src/systemtask/Messages.h b/src/systemtask/Messages.h index b7fee8a52c..593c2efe19 100644 --- a/src/systemtask/Messages.h +++ b/src/systemtask/Messages.h @@ -6,6 +6,7 @@ namespace Pinetime { enum class Messages : uint8_t { GoToSleep, GoToRunning, + GoToClock, TouchWakeUp, OnNewTime, OnNewNotification, diff --git a/src/systemtask/SystemTask.cpp b/src/systemtask/SystemTask.cpp index 73f573fa0e..06c04c23b5 100644 --- a/src/systemtask/SystemTask.cpp +++ b/src/systemtask/SystemTask.cpp @@ -281,6 +281,12 @@ void SystemTask::Work() { alarmController.ScheduleAlarm(); } break; + case Messages::GoToClock: + if (state == SystemTaskState::Sleeping) { + GoToRunning(); + } + displayApp.PushMessage(Pinetime::Applications::Display::Messages::GoToClock); + break; case Messages::OnNewNotification: if (settingsController.GetNotificationStatus() == Pinetime::Controllers::Settings::Notification::On) { if (state == SystemTaskState::Sleeping) { @@ -295,7 +301,7 @@ void SystemTask::Work() { if (state == SystemTaskState::Sleeping) { GoToRunning(); } - motorController.RunForDuration(35); + motorController.StartRinging(); displayApp.PushMessage(Pinetime::Applications::Display::Messages::TimerDone); break; case Messages::SetOffAlarm: