Mikhail G 3 лет назад
Родитель
Сommit
621c6fdddf

+ 1 - 1
application.fam

@@ -3,7 +3,7 @@ App(
     name="Flipp Pomodoro",
     apptype=FlipperAppType.EXTERNAL,
     entry_point="flipp_pomodoro_main",
-    requires=["gui"],
+    requires=["gui", "notification"],
     stack_size=1 * 1024,
     fap_category="Productivity",
     fap_icon_assets="images",

+ 187 - 45
flipp_pomodoro.c

@@ -1,6 +1,7 @@
 #include <furi.h>
 #include <furi_hal.h>
 
+#include <notification/notification_messages.h>
 #include <gui/gui.h>
 #include <gui/elements.h>
 #include <input/input.h>
@@ -9,29 +10,104 @@
  * Just set fap_icon_assets in application.fam and #include {APPID}_icons.h */
 #include "flipp_pomodoro_icons.h"
 
+const int SECONDS_IN_MINUTE = 60;
+
+/// @brief Actions to be processed in a queue
 typedef enum {
-    TimerTickType,
+    TimerTickType = 42,
     InputEventType,
 } ActionType;
 
+/// @brief Single action contains type and payload
 typedef struct {
     ActionType type;
     void* payload;
 } Action;
 
-/**
- * Flipp Pomodoro state management
-*/
-
 typedef enum {
     Work,
     Rest,
 } PomodoroStage;
 
+static const NotificationSequence work_start_notification = {
+    &message_display_backlight_on,
+
+    &message_vibro_on,
+
+    &message_note_b5,
+    &message_delay_250,
+
+    &message_note_d5,
+    &message_delay_250,
+
+    &message_sound_off,
+    &message_vibro_off,
+
+    &message_green_255,
+    &message_delay_1000,
+    &message_green_0,
+    &message_delay_250,
+    &message_green_255,
+    &message_delay_1000,
+
+    NULL,
+};
+
+static const NotificationSequence rest_start_notification = {
+    &message_display_backlight_on,
+
+    &message_vibro_on,
+
+    &message_note_d5,
+    &message_delay_250,
+
+    &message_note_b5,
+    &message_delay_250,
+
+    &message_sound_off,
+    &message_vibro_off,
+
+    &message_red_255,
+    &message_delay_1000,
+    &message_red_0,
+    &message_delay_250,
+    &message_red_255,
+    &message_delay_1000,
+
+    NULL,
+};
+
+static const NotificationSequence* stage_start_notification_sequence_map[] = {
+    [Work] = &work_start_notification,
+    [Rest] = &rest_start_notification,
+};
+
+static char* next_stage_label[] = {
+    [Work] = "Get Rest",
+    [Rest] = "Start Work",
+};
+
+static const Icon* stage_background_image[] = {
+    [Work] = &I_flipp_pomodoro_work_64,
+    [Rest] = &I_flipp_pomodoro_rest_64,
+};
+
+static const PomodoroStage stage_rotaion_map[] = {
+    [Work] = Rest,
+    [Rest] = Work,
+};
+
+static const int32_t stage_duration_seconds_map[] = {
+    [Work] = 25 * SECONDS_IN_MINUTE,
+    [Rest] = 5 * SECONDS_IN_MINUTE,
+};
+
+const PomodoroStage default_stage = Work;
+
+/// @brief Container for a time period
 typedef struct {
     uint8_t seconds;
     uint8_t minutes;
-    uint8_t hours;
     uint32_t total_seconds;
 } TimeDifference;
 
@@ -41,62 +117,115 @@ typedef struct {
 } FlippPomodoroState;
 
 /// @brief Calculates difference between two provided timestamps
-/// @param begin 
-/// @param end 
-/// @return 
+/// @param begin - start timestamp of the period
+/// @param end - end timestamp of the period to measure
+/// @return TimeDifference struct
 static TimeDifference get_timestamp_difference_seconds(uint32_t begin, uint32_t end) {
     const uint32_t duration_seconds = end - begin;
-    return (TimeDifference){.total_seconds=duration_seconds};
+
+    uint32_t minutes = (duration_seconds / SECONDS_IN_MINUTE) % SECONDS_IN_MINUTE;
+    uint32_t seconds = duration_seconds % SECONDS_IN_MINUTE;
+
+    return (TimeDifference){.total_seconds=duration_seconds, .minutes=minutes, .seconds=seconds};
 }
 
 static void flipp_pomodoro__toggle_stage(FlippPomodoroState* state) {
-    state->stage = state->stage == Work ? Rest : Work;
+    furi_assert(state);
+    state->stage = stage_rotaion_map[state->stage];
     state->started_at_timestamp = furi_hal_rtc_get_timestamp();
 }
 
 static char* flipp_pomodoro__next_stage_label(FlippPomodoroState* state) {
-    return state->stage == Work ? "To rest" : "To work";
+    furi_assert(state);
+    return next_stage_label[state->stage];
 };
 
-static TimeDifference flipp_pomodoro__stage_duration(FlippPomodoroState* state) {
-    const uint32_t now = furi_hal_rtc_get_timestamp();
-    return get_timestamp_difference_seconds(state->started_at_timestamp, now);
-}
 
 static void flipp_pomodoro__destroy(FlippPomodoroState* state) {
+    furi_assert(state);
     free(state);
 }
 
+static uint32_t flipp_pomodoro__stage_expires_timestamp(FlippPomodoroState* state) {
+    return state->started_at_timestamp + stage_duration_seconds_map[state->stage];
+}
+
+static TimeDifference flipp_pomodoro__stage_remaining_duration(FlippPomodoroState* state) {
+    const uint32_t now = furi_hal_rtc_get_timestamp();
+    const uint32_t stage_ends_at = flipp_pomodoro__stage_expires_timestamp(state);
+    return get_timestamp_difference_seconds(now, stage_ends_at);
+}
+
+static bool flipp_pomodoro__is_stage_expired(FlippPomodoroState* state) {
+    const uint32_t now = furi_hal_rtc_get_timestamp();
+    const uint32_t expired_by = flipp_pomodoro__stage_expires_timestamp(state);
+    const uint8_t seamless_change_span_seconds = 1;
+    return (now - seamless_change_span_seconds) >= expired_by;
+}
+
 static FlippPomodoroState flipp_pomodoro__new() {
     const uint32_t now = furi_hal_rtc_get_timestamp();
-    const FlippPomodoroState new_state = {.stage=Work, .started_at_timestamp=now};
+    const FlippPomodoroState new_state = {.stage=default_stage, .started_at_timestamp=now};
     return new_state;
 }
 
+typedef struct {
+    FlippPomodoroState* state;
+} DrawContext;
+
 // Screen is 128x64 px
 static void app_draw_callback(Canvas* canvas, void* ctx) {
-    // WARNING: place no side-effects into rener cycle
-    canvas_clear(canvas);
-    FlippPomodoroState* state = ctx;
+    // WARNING: place no side-effects into rener cycle!!
+    DrawContext* draw_context = ctx;
 
-    FuriString* timer_string = furi_string_alloc();
+    const TimeDifference remaining_stage_time = flipp_pomodoro__stage_remaining_duration(draw_context->state);
 
-    const uint32_t now = flipp_pomodoro__stage_duration(state).total_seconds;
-
-    furi_string_printf(timer_string, "%lu", now);
+    // Format remaining stage time;
+    FuriString* timer_string = furi_string_alloc();
+    furi_string_printf(timer_string, "%02u:%02u", remaining_stage_time.minutes, remaining_stage_time.seconds);
+    const char* remaining_stage_time_string = furi_string_get_cstr(timer_string);
 
-    elements_text_box(canvas, 50, 20, 30, 50, AlignCenter, AlignCenter, furi_string_get_cstr(timer_string), false);
-    elements_button_right(canvas, flipp_pomodoro__next_stage_label(state));
+    // Render interface
+    canvas_clear(canvas);
 
+    canvas_draw_icon(canvas, 0, 0, stage_background_image[draw_context->state->stage]);
+
+    // Countdown section
+    const uint8_t countdown_box_height = canvas_height(canvas)*0.4;
+    const uint8_t countdown_box_width = canvas_width(canvas)*0.5;
+    const uint8_t countdown_box_x = canvas_width(canvas) - countdown_box_width - 2;
+    const uint8_t countdown_box_y = 0;
+
+    elements_bold_rounded_frame(canvas,
+        countdown_box_x,
+        countdown_box_y,
+        countdown_box_width,
+        countdown_box_height
+    );
+
+    canvas_set_font(canvas, FontBigNumbers);
+    canvas_draw_str_aligned(
+        canvas,
+        countdown_box_x + (countdown_box_width/2),
+        countdown_box_y + (countdown_box_height/2),
+        AlignCenter,
+        AlignCenter,
+        remaining_stage_time_string
+    );
+
+    // Draw layout
+    canvas_set_font(canvas, FontSecondary);
+    elements_button_right(canvas, flipp_pomodoro__next_stage_label(draw_context->state));
+
+
+    //  Cleanup
     furi_string_free(timer_string);
 }
 
 static void clock_tick_callback(void* ctx) {
-
     furi_assert(ctx);
     FuriMessageQueue* queue = ctx;
     Action action = {.type = TimerTickType};
-    // It's OK to loose this event if system overloaded
     furi_message_queue_put(queue, &action, 0);
 }
 
@@ -134,34 +263,46 @@ int32_t flipp_pomodoro_main(void* p) {
 
     // Configure view port
     ViewPort* view_port = view_port_alloc();
-    view_port_draw_callback_set(view_port, app_draw_callback, &state);
+    DrawContext draw_context = {.state=&state};
+    view_port_draw_callback_set(view_port, app_draw_callback, &draw_context);
     view_port_input_callback_set(view_port, app_input_callback, event_queue);
 
-    FuriTimer* timer = furi_timer_alloc(clock_tick_callback, FuriTimerTypePeriodic, &event_queue);
+    // Initiate timer
+    FuriTimer* timer = furi_timer_alloc(clock_tick_callback, FuriTimerTypePeriodic, event_queue);
+    furi_timer_start(timer, 500);
+
+    NotificationApp* notification_app = furi_record_open(RECORD_NOTIFICATION);
 
     // Register view port in GUI
     Gui* gui = furi_record_open(RECORD_GUI);
     gui_add_view_port(gui, view_port, GuiLayerFullscreen);
 
-    furi_timer_start(timer, 500);
-
-    Action action;
 
     bool running = true;
     while(running) {
-        if(furi_message_queue_get(event_queue, &action, 200) == FuriStatusOk) {
-            switch (action.type) {
-            case InputEventType:
-                running = input_events_reducer(&state, action.payload);
-                break;
-            case TimerTickType:
-                // TODO: track time is over and make switch
-                break;
-            default:
-                break;
-            }
-            view_port_update(view_port);
+        Action action;
+        if(furi_message_queue_get(event_queue, &action, 200) != FuriStatusOk) {
+            continue;
+        };
+
+        if(!action.type) {
+            continue;
+        }
+        
+        switch (action.type) {
+        case InputEventType:
+            running = input_events_reducer(&state, action.payload);
+            break;
+        case TimerTickType:
+            if(flipp_pomodoro__is_stage_expired(&state)) {
+                flipp_pomodoro__toggle_stage(&state);
+                notification_message(notification_app, stage_start_notification_sequence_map[state.stage]);
+            };
+            break;
+        default:
+            break;
         }
+        view_port_update(view_port); // Only re-draw on event
     }
 
     view_port_enabled_set(view_port, false);
@@ -171,6 +312,7 @@ int32_t flipp_pomodoro_main(void* p) {
     furi_record_close(RECORD_GUI);
     furi_timer_free(timer);
     flipp_pomodoro__destroy(&state);
+    furi_record_close(RECORD_NOTIFICATION);
 
     return 0;
 }

BIN
images/dolphin_71x25.png


BIN
images/flipp_pomodoro_14.png


BIN
images/flipp_pomodoro_rest_64.png


BIN
images/flipp_pomodoro_work_64.png