11#include <furi.h>
22#include <furi_hal.h>
33
4+ #include <notification/notification_messages.h>
45#include <gui/gui.h>
56#include <gui/elements.h>
67#include <input/input.h>
910 * Just set fap_icon_assets in application.fam and #include {APPID}_icons.h */
1011#include "flipp_pomodoro_icons.h"
1112
13+ const int SECONDS_IN_MINUTE = 60 ;
14+
15+ /// @brief Actions to be processed in a queue
1216typedef enum {
13- TimerTickType ,
17+ TimerTickType = 42 ,
1418 InputEventType ,
1519} ActionType ;
1620
21+ /// @brief Single action contains type and payload
1722typedef struct {
1823 ActionType type ;
1924 void * payload ;
2025} Action ;
2126
22- /**
23- * Flipp Pomodoro state management
24- */
25-
2627typedef enum {
2728 Work ,
2829 Rest ,
2930} PomodoroStage ;
3031
32+ static const NotificationSequence work_start_notification = {
33+ & message_display_backlight_on ,
34+
35+ & message_vibro_on ,
36+
37+ & message_note_b5 ,
38+ & message_delay_250 ,
39+
40+ & message_note_d5 ,
41+ & message_delay_250 ,
42+
43+ & message_sound_off ,
44+ & message_vibro_off ,
45+
46+ & message_green_255 ,
47+ & message_delay_1000 ,
48+ & message_green_0 ,
49+ & message_delay_250 ,
50+ & message_green_255 ,
51+ & message_delay_1000 ,
52+
53+ NULL ,
54+ };
55+
56+ static const NotificationSequence rest_start_notification = {
57+ & message_display_backlight_on ,
58+
59+ & message_vibro_on ,
60+
61+ & message_note_d5 ,
62+ & message_delay_250 ,
63+
64+ & message_note_b5 ,
65+ & message_delay_250 ,
66+
67+ & message_sound_off ,
68+ & message_vibro_off ,
69+
70+ & message_red_255 ,
71+ & message_delay_1000 ,
72+ & message_red_0 ,
73+ & message_delay_250 ,
74+ & message_red_255 ,
75+ & message_delay_1000 ,
76+
77+ NULL ,
78+ };
79+
80+ static const NotificationSequence * stage_start_notification_sequence_map [] = {
81+ [Work ] = & work_start_notification ,
82+ [Rest ] = & rest_start_notification ,
83+ };
84+
85+ static char * next_stage_label [] = {
86+ [Work ] = "Get Rest" ,
87+ [Rest ] = "Start Work" ,
88+ };
89+
90+ static const Icon * stage_background_image [] = {
91+ [Work ] = & I_flipp_pomodoro_work_64 ,
92+ [Rest ] = & I_flipp_pomodoro_rest_64 ,
93+ };
94+
95+ static const PomodoroStage stage_rotaion_map [] = {
96+ [Work ] = Rest ,
97+ [Rest ] = Work ,
98+ };
99+
100+ static const int32_t stage_duration_seconds_map [] = {
101+ [Work ] = 25 * SECONDS_IN_MINUTE ,
102+ [Rest ] = 5 * SECONDS_IN_MINUTE ,
103+ };
104+
105+ const PomodoroStage default_stage = Work ;
106+
107+ /// @brief Container for a time period
31108typedef struct {
32109 uint8_t seconds ;
33110 uint8_t minutes ;
34- uint8_t hours ;
35111 uint32_t total_seconds ;
36112} TimeDifference ;
37113
@@ -41,62 +117,115 @@ typedef struct {
41117} FlippPomodoroState ;
42118
43119/// @brief Calculates difference between two provided timestamps
44- /// @param begin
45- /// @param end
46- /// @return
120+ /// @param begin - start timestamp of the period
121+ /// @param end - end timestamp of the period to measure
122+ /// @return TimeDifference struct
47123static TimeDifference get_timestamp_difference_seconds (uint32_t begin , uint32_t end ) {
48124 const uint32_t duration_seconds = end - begin ;
49- return (TimeDifference ){.total_seconds = duration_seconds };
125+
126+ uint32_t minutes = (duration_seconds / SECONDS_IN_MINUTE ) % SECONDS_IN_MINUTE ;
127+ uint32_t seconds = duration_seconds % SECONDS_IN_MINUTE ;
128+
129+ return (TimeDifference ){.total_seconds = duration_seconds , .minutes = minutes , .seconds = seconds };
50130}
51131
52132static void flipp_pomodoro__toggle_stage (FlippPomodoroState * state ) {
53- state -> stage = state -> stage == Work ? Rest : Work ;
133+ furi_assert (state );
134+ state -> stage = stage_rotaion_map [state -> stage ];
54135 state -> started_at_timestamp = furi_hal_rtc_get_timestamp ();
55136}
56137
57138static char * flipp_pomodoro__next_stage_label (FlippPomodoroState * state ) {
58- return state -> stage == Work ? "To rest" : "To work" ;
139+ furi_assert (state );
140+ return next_stage_label [state -> stage ];
59141};
60142
61- static TimeDifference flipp_pomodoro__stage_duration (FlippPomodoroState * state ) {
62- const uint32_t now = furi_hal_rtc_get_timestamp ();
63- return get_timestamp_difference_seconds (state -> started_at_timestamp , now );
64- }
65143
66144static void flipp_pomodoro__destroy (FlippPomodoroState * state ) {
145+ furi_assert (state );
67146 free (state );
68147}
69148
149+ static uint32_t flipp_pomodoro__stage_expires_timestamp (FlippPomodoroState * state ) {
150+ return state -> started_at_timestamp + stage_duration_seconds_map [state -> stage ];
151+ }
152+
153+ static TimeDifference flipp_pomodoro__stage_remaining_duration (FlippPomodoroState * state ) {
154+ const uint32_t now = furi_hal_rtc_get_timestamp ();
155+ const uint32_t stage_ends_at = flipp_pomodoro__stage_expires_timestamp (state );
156+ return get_timestamp_difference_seconds (now , stage_ends_at );
157+ }
158+
159+ static bool flipp_pomodoro__is_stage_expired (FlippPomodoroState * state ) {
160+ const uint32_t now = furi_hal_rtc_get_timestamp ();
161+ const uint32_t expired_by = flipp_pomodoro__stage_expires_timestamp (state );
162+ const uint8_t seamless_change_span_seconds = 1 ;
163+ return (now - seamless_change_span_seconds ) >= expired_by ;
164+ }
165+
70166static FlippPomodoroState flipp_pomodoro__new () {
71167 const uint32_t now = furi_hal_rtc_get_timestamp ();
72- const FlippPomodoroState new_state = {.stage = Work , .started_at_timestamp = now };
168+ const FlippPomodoroState new_state = {.stage = default_stage , .started_at_timestamp = now };
73169 return new_state ;
74170}
75171
172+ typedef struct {
173+ FlippPomodoroState * state ;
174+ } DrawContext ;
175+
76176// Screen is 128x64 px
77177static void app_draw_callback (Canvas * canvas , void * ctx ) {
78- // WARNING: place no side-effects into rener cycle
79- canvas_clear (canvas );
80- FlippPomodoroState * state = ctx ;
178+ // WARNING: place no side-effects into rener cycle!!
179+ DrawContext * draw_context = ctx ;
81180
82- FuriString * timer_string = furi_string_alloc ( );
181+ const TimeDifference remaining_stage_time = flipp_pomodoro__stage_remaining_duration ( draw_context -> state );
83182
84- const uint32_t now = flipp_pomodoro__stage_duration (state ).total_seconds ;
85-
86- furi_string_printf (timer_string , "%lu" , now );
183+ // Format remaining stage time;
184+ FuriString * timer_string = furi_string_alloc ();
185+ furi_string_printf (timer_string , "%02u:%02u" , remaining_stage_time .minutes , remaining_stage_time .seconds );
186+ const char * remaining_stage_time_string = furi_string_get_cstr (timer_string );
87187
88- elements_text_box ( canvas , 50 , 20 , 30 , 50 , AlignCenter , AlignCenter , furi_string_get_cstr ( timer_string ), false);
89- elements_button_right (canvas , flipp_pomodoro__next_stage_label ( state ) );
188+ // Render interface
189+ canvas_clear (canvas );
90190
191+ canvas_draw_icon (canvas , 0 , 0 , stage_background_image [draw_context -> state -> stage ]);
192+
193+ // Countdown section
194+ const uint8_t countdown_box_height = canvas_height (canvas )* 0.4 ;
195+ const uint8_t countdown_box_width = canvas_width (canvas )* 0.5 ;
196+ const uint8_t countdown_box_x = canvas_width (canvas ) - countdown_box_width - 2 ;
197+ const uint8_t countdown_box_y = 0 ;
198+
199+ elements_bold_rounded_frame (canvas ,
200+ countdown_box_x ,
201+ countdown_box_y ,
202+ countdown_box_width ,
203+ countdown_box_height
204+ );
205+
206+ canvas_set_font (canvas , FontBigNumbers );
207+ canvas_draw_str_aligned (
208+ canvas ,
209+ countdown_box_x + (countdown_box_width /2 ),
210+ countdown_box_y + (countdown_box_height /2 ),
211+ AlignCenter ,
212+ AlignCenter ,
213+ remaining_stage_time_string
214+ );
215+
216+ // Draw layout
217+ canvas_set_font (canvas , FontSecondary );
218+ elements_button_right (canvas , flipp_pomodoro__next_stage_label (draw_context -> state ));
219+
220+
221+ // Cleanup
91222 furi_string_free (timer_string );
92223}
93224
94225static void clock_tick_callback (void * ctx ) {
95-
96226 furi_assert (ctx );
97227 FuriMessageQueue * queue = ctx ;
98228 Action action = {.type = TimerTickType };
99- // It's OK to loose this event if system overloaded
100229 furi_message_queue_put (queue , & action , 0 );
101230}
102231
@@ -134,34 +263,46 @@ int32_t flipp_pomodoro_main(void* p) {
134263
135264 // Configure view port
136265 ViewPort * view_port = view_port_alloc ();
137- view_port_draw_callback_set (view_port , app_draw_callback , & state );
266+ DrawContext draw_context = {.state = & state };
267+ view_port_draw_callback_set (view_port , app_draw_callback , & draw_context );
138268 view_port_input_callback_set (view_port , app_input_callback , event_queue );
139269
140- FuriTimer * timer = furi_timer_alloc (clock_tick_callback , FuriTimerTypePeriodic , & event_queue );
270+ // Initiate timer
271+ FuriTimer * timer = furi_timer_alloc (clock_tick_callback , FuriTimerTypePeriodic , event_queue );
272+ furi_timer_start (timer , 500 );
273+
274+ NotificationApp * notification_app = furi_record_open (RECORD_NOTIFICATION );
141275
142276 // Register view port in GUI
143277 Gui * gui = furi_record_open (RECORD_GUI );
144278 gui_add_view_port (gui , view_port , GuiLayerFullscreen );
145279
146- furi_timer_start (timer , 500 );
147-
148- Action action ;
149280
150281 bool running = true;
151282 while (running ) {
152- if (furi_message_queue_get (event_queue , & action , 200 ) == FuriStatusOk ) {
153- switch (action .type ) {
154- case InputEventType :
155- running = input_events_reducer (& state , action .payload );
156- break ;
157- case TimerTickType :
158- // TODO: track time is over and make switch
159- break ;
160- default :
161- break ;
162- }
163- view_port_update (view_port );
283+ Action action ;
284+ if (furi_message_queue_get (event_queue , & action , 200 ) != FuriStatusOk ) {
285+ continue ;
286+ };
287+
288+ if (!action .type ) {
289+ continue ;
290+ }
291+
292+ switch (action .type ) {
293+ case InputEventType :
294+ running = input_events_reducer (& state , action .payload );
295+ break ;
296+ case TimerTickType :
297+ if (flipp_pomodoro__is_stage_expired (& state )) {
298+ flipp_pomodoro__toggle_stage (& state );
299+ notification_message (notification_app , stage_start_notification_sequence_map [state .stage ]);
300+ };
301+ break ;
302+ default :
303+ break ;
164304 }
305+ view_port_update (view_port ); // Only re-draw on event
165306 }
166307
167308 view_port_enabled_set (view_port , false);
@@ -171,6 +312,7 @@ int32_t flipp_pomodoro_main(void* p) {
171312 furi_record_close (RECORD_GUI );
172313 furi_timer_free (timer );
173314 flipp_pomodoro__destroy (& state );
315+ furi_record_close (RECORD_NOTIFICATION );
174316
175317 return 0 ;
176318}
0 commit comments