MX 2 лет назад
Родитель
Сommit
e4d9959cfd

+ 1 - 0
flipp_pomodoro_app.h

@@ -17,6 +17,7 @@ typedef enum {
     FlippPomodoroAppCustomEventStageSkip = 100,
     FlippPomodoroAppCustomEventStageSkip = 100,
     FlippPomodoroAppCustomEventStageComplete, // By Expiration
     FlippPomodoroAppCustomEventStageComplete, // By Expiration
     FlippPomodoroAppCustomEventTimerTick,
     FlippPomodoroAppCustomEventTimerTick,
+    FlippPomodoroAppCustomEventTimerAskHint,
     FlippPomodoroAppCustomEventStateUpdated,
     FlippPomodoroAppCustomEventStateUpdated,
     FlippPomodoroAppCustomEventResumeTimer,
     FlippPomodoroAppCustomEventResumeTimer,
 } FlippPomodoroAppCustomEvent;
 } FlippPomodoroAppCustomEvent;

+ 3 - 3
modules/flipp_pomodoro.c

@@ -18,9 +18,9 @@ PomodoroStage stages_sequence[] = {
 };
 };
 
 
 char* current_stage_label[] = {
 char* current_stage_label[] = {
-    [FlippPomodoroStageFocus] = "Continue focus for:",
-    [FlippPomodoroStageRest] = "Keep rest for:",
-    [FlippPomodoroStageLongBreak] = "Long Break for:",
+    [FlippPomodoroStageFocus] = "Focusing...",
+    [FlippPomodoroStageRest] = "Short Break...",
+    [FlippPomodoroStageLongBreak] = "Long Break...",
 };
 };
 
 
 char* next_stage_label[] = {
 char* next_stage_label[] = {

+ 85 - 8
scenes/flipp_pomodoro_scene_timer.c

@@ -8,6 +8,49 @@
 
 
 enum { SceneEventConusmed = true, SceneEventNotConusmed = false };
 enum { SceneEventConusmed = true, SceneEventNotConusmed = false };
 
 
+static char* work_hints[] = {
+    "Can you explain the problem as if I'm five?",
+    "Expected output vs. reality: what's the difference?",
+    "Ever thought of slicing the problem into bite-sized pieces?",
+    "What's the story when you walk through the code?",
+    "Any error messages gossiping about the issue?",
+    "What tricks have you tried to fix this?",
+    "Did you test the code, or just hoping for the best?",
+    "How's this code mingling with the rest of the app?",
+    "Any sneaky side effects causing mischief?",
+    "What are you assuming, and is it safe to do so?",
+    "Did you remember to invite all the edge cases to the party?",
+    "What happens in the isolation chamber (running code separately)?",
+    "Can you make the issue appear on command?",
+    "What's the scene at the crime spot when the error occurs?",
+    "Did you seek wisdom from the grand oracle (Google)?",
+    "What if you take a different path to solve this?",
+    "Did you take a coffee break to reboot your brain?"};
+
+static char* break_hints[] = {
+    "Time to stretch! Remember, your body isn't made of code.",
+    "Hydrate or diedrate! Grab a glass of water.",
+    "Blink! Your eyes need a break too.",
+    "How about a quick dance-off with your shadow?",
+    "Ever tried chair yoga? Now's the time!",
+    "Time for a quick peek out the window. The outside world still exists!",
+    "Quick, think about kittens! Or puppies! Or baby turtles!",
+    "Time for a laugh. Look up a joke or two!",
+    "Sing a song. Bonus points for making up your own lyrics.",
+    "Do a quick tidy-up. A clean space is a happy space!",
+    "Time to play 'air' musical instrument for a minute.",
+    "How about a quick doodle? Unleash your inner Picasso!",
+    "Practice your superhero pose. Feel the power surge!",
+    "Quick, tell yourself a joke. Don't worry, I won't judge.",
+    "Time to practice your mime skills. Stuck in a box, anyone?",
+    "Ever tried juggling? Now's your chance!",
+    "Do a quick self high-five, you're doing great!"};
+
+static char* random_string_of_list(char** hints, size_t num_hints) {
+    int random_index = rand() % num_hints;
+    return hints[random_index];
+}
+
 void flipp_pomodoro_scene_timer_sync_view_state(void* ctx) {
 void flipp_pomodoro_scene_timer_sync_view_state(void* ctx) {
     furi_assert(ctx);
     furi_assert(ctx);
 
 
@@ -25,6 +68,12 @@ void flipp_pomodoro_scene_timer_on_next_stage(void* ctx) {
     view_dispatcher_send_custom_event(app->view_dispatcher, FlippPomodoroAppCustomEventStageSkip);
     view_dispatcher_send_custom_event(app->view_dispatcher, FlippPomodoroAppCustomEventStageSkip);
 };
 };
 
 
+void flipp_pomodoro_scene_timer_on_ask_hint(void* ctx) {
+    FlippPomodoroApp* app = ctx;
+    view_dispatcher_send_custom_event(
+        app->view_dispatcher, FlippPomodoroAppCustomEventTimerAskHint);
+}
+
 void flipp_pomodoro_scene_timer_on_enter(void* ctx) {
 void flipp_pomodoro_scene_timer_on_enter(void* ctx) {
     furi_assert(ctx);
     furi_assert(ctx);
 
 
@@ -37,21 +86,49 @@ void flipp_pomodoro_scene_timer_on_enter(void* ctx) {
 
 
     view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewTimer);
     view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewTimer);
     flipp_pomodoro_scene_timer_sync_view_state(app);
     flipp_pomodoro_scene_timer_sync_view_state(app);
+
+    flipp_pomodoro_view_timer_set_callback_context(app->timer_view, app);
+
+    flipp_pomodoro_view_timer_set_on_ok_cb(
+        app->timer_view, flipp_pomodoro_scene_timer_on_ask_hint);
+
     flipp_pomodoro_view_timer_set_on_right_cb(
     flipp_pomodoro_view_timer_set_on_right_cb(
-        app->timer_view, flipp_pomodoro_scene_timer_on_next_stage, app);
+        app->timer_view, flipp_pomodoro_scene_timer_on_next_stage);
 };
 };
 
 
+char* flipp_pomodoro_scene_timer_get_contextual_hint(FlippPomodoroApp* app) {
+    switch(flipp_pomodoro__get_stage(app->state)) {
+    case FlippPomodoroStageFocus:
+        return random_string_of_list(work_hints, sizeof(work_hints) / sizeof(work_hints[0]));
+    case FlippPomodoroStageRest:
+    case FlippPomodoroStageLongBreak:
+        return random_string_of_list(break_hints, sizeof(break_hints) / sizeof(break_hints[0]));
+    default:
+        return "What's up?";
+    }
+}
+
 void flipp_pomodoro_scene_timer_handle_custom_event(
 void flipp_pomodoro_scene_timer_handle_custom_event(
     FlippPomodoroApp* app,
     FlippPomodoroApp* app,
     FlippPomodoroAppCustomEvent custom_event) {
     FlippPomodoroAppCustomEvent custom_event) {
-    if(custom_event == FlippPomodoroAppCustomEventTimerTick &&
-       flipp_pomodoro__is_stage_expired(app->state)) {
-        view_dispatcher_send_custom_event(
-            app->view_dispatcher, FlippPomodoroAppCustomEventStageComplete);
-    }
-
-    if(custom_event == FlippPomodoroAppCustomEventStateUpdated) {
+    switch(custom_event) {
+    case FlippPomodoroAppCustomEventTimerTick:
+        if(flipp_pomodoro__is_stage_expired(app->state)) {
+            view_dispatcher_send_custom_event(
+                app->view_dispatcher, FlippPomodoroAppCustomEventStageComplete);
+        }
+        break;
+    case FlippPomodoroAppCustomEventStateUpdated:
         flipp_pomodoro_scene_timer_sync_view_state(app);
         flipp_pomodoro_scene_timer_sync_view_state(app);
+        break;
+    case FlippPomodoroAppCustomEventTimerAskHint:
+        flipp_pomodoro_view_timer_display_hint(
+            flipp_pomodoro_view_timer_get_view(app->timer_view),
+            flipp_pomodoro_scene_timer_get_contextual_hint(app));
+        break;
+    default:
+        // optional: code to be executed if custom_event doesn't match any cases
+        break;
     }
     }
 };
 };
 
 

+ 1 - 1
views/flipp_pomodoro_info_view.c

@@ -29,7 +29,7 @@ static void
 
 
     furi_string_printf(
     furi_string_printf(
         stats_string,
         stats_string,
-        "So Long,\nand Thanks for All the Focus...\nand for completing\n%i pomodoro(s)",
+        "So Long,\nand Thanks for All the Focus...\nand for completing\n\e#%i\e# pomodoro(s)",
         model->pomodoros_completed);
         model->pomodoros_completed);
     const char* stats_string_formatted = furi_string_get_cstr(stats_string);
     const char* stats_string_formatted = furi_string_get_cstr(stats_string);
 
 

+ 112 - 17
views/flipp_pomodoro_timer_view.c

@@ -18,12 +18,15 @@ enum {
 struct FlippPomodoroTimerView {
 struct FlippPomodoroTimerView {
     View* view;
     View* view;
     FlippPomodoroTimerViewInputCb right_cb;
     FlippPomodoroTimerViewInputCb right_cb;
-    void* right_cb_ctx;
+    FlippPomodoroTimerViewInputCb ok_cb;
+    void* callback_context;
 };
 };
 
 
 typedef struct {
 typedef struct {
     IconAnimation* icon;
     IconAnimation* icon;
     FlippPomodoroState* state;
     FlippPomodoroState* state;
+    size_t scroll_counter;
+    char* current_hint;
 } FlippPomodoroTimerViewModel;
 } FlippPomodoroTimerViewModel;
 
 
 static const Icon* stage_background_image[] = {
 static const Icon* stage_background_image[] = {
@@ -89,6 +92,55 @@ static void
         flipp_pomodoro__current_stage_label(state));
         flipp_pomodoro__current_stage_label(state));
 }
 }
 
 
+static void
+    flipp_pomodoro_view_timer_draw_hint(Canvas* canvas, FlippPomodoroTimerViewModel* model) {
+    size_t MAX_SCROLL_COUNTER = 300;
+    uint8_t SCROLL_DELAY_FRAMES = 3;
+
+    if(model->scroll_counter >= MAX_SCROLL_COUNTER || model->current_hint == NULL) {
+        return;
+    }
+
+    uint8_t hint_width = 90;
+    uint8_t hint_height = 18;
+
+    uint8_t hint_x = canvas_width(canvas) - hint_width - 6;
+    uint8_t hint_y = 35;
+
+    FuriString* displayed_hint_string = furi_string_alloc();
+
+    furi_string_printf(displayed_hint_string, "%s", model->current_hint);
+
+    size_t perfect_duration = furi_string_size(displayed_hint_string) * 1.5;
+
+    if(model->scroll_counter > perfect_duration) {
+        model->scroll_counter = MAX_SCROLL_COUNTER;
+        furi_string_free(displayed_hint_string);
+        return;
+    }
+
+    size_t scroll_offset = (model->scroll_counter < SCROLL_DELAY_FRAMES) ?
+                               0 :
+                               model->scroll_counter - SCROLL_DELAY_FRAMES;
+
+    canvas_set_color(canvas, ColorWhite);
+    canvas_draw_box(canvas, hint_x, hint_y, hint_width + 3, hint_height);
+    canvas_set_color(canvas, ColorBlack);
+
+    elements_bubble(canvas, hint_x, hint_y, hint_width, hint_height);
+
+    elements_scrollable_text_line(
+        canvas,
+        hint_x + 6,
+        hint_y + 12,
+        hint_width - 4,
+        displayed_hint_string,
+        scroll_offset,
+        true);
+    furi_string_free(displayed_hint_string);
+    model->scroll_counter++;
+}
+
 static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model) {
 static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model) {
     if(!_model) {
     if(!_model) {
         return;
         return;
@@ -105,10 +157,12 @@ static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model
         canvas, flipp_pomodoro__stage_remaining_duration(model->state));
         canvas, flipp_pomodoro__stage_remaining_duration(model->state));
 
 
     flipp_pomodoro_view_timer_draw_current_stage_label(canvas, model->state);
     flipp_pomodoro_view_timer_draw_current_stage_label(canvas, model->state);
+
     canvas_set_color(canvas, ColorBlack);
     canvas_set_color(canvas, ColorBlack);
 
 
     canvas_set_font(canvas, FontSecondary);
     canvas_set_font(canvas, FontSecondary);
     elements_button_right(canvas, flipp_pomodoro__next_stage_label(model->state));
     elements_button_right(canvas, flipp_pomodoro__next_stage_label(model->state));
+    flipp_pomodoro_view_timer_draw_hint(canvas, model);
 };
 };
 
 
 bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
 bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
@@ -116,18 +170,22 @@ bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
     furi_assert(event);
     furi_assert(event);
     FlippPomodoroTimerView* timer = ctx;
     FlippPomodoroTimerView* timer = ctx;
 
 
-    const bool should_trigger_right_event_cb = (event->type == InputTypePress) &&
-                                               (event->key == InputKeyRight) &&
-                                               (timer->right_cb != NULL);
+    const bool is_press_event = event->type == InputTypePress;
 
 
-    if(should_trigger_right_event_cb) {
-        furi_assert(timer->right_cb);
-        furi_assert(timer->right_cb_ctx);
-        timer->right_cb(timer->right_cb_ctx);
-        return ViewInputConsumed;
-    };
+    if(!is_press_event) {
+        return ViewInputNotConusmed;
+    }
 
 
-    return ViewInputNotConusmed;
+    switch(event->key) {
+    case InputKeyRight:
+        timer->right_cb(timer->callback_context);
+        return ViewInputConsumed;
+    case InputKeyOk:
+        timer->ok_cb(timer->callback_context);
+        return ViewInputConsumed;
+    default:
+        return ViewInputNotConusmed;
+    }
 };
 };
 
 
 View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) {
 View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) {
@@ -135,12 +193,22 @@ View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) {
     return timer->view;
     return timer->view;
 };
 };
 
 
+void flipp_pomodoro_view_timer_display_hint(View* view, char* hint) {
+    with_view_model(
+        view,
+        FlippPomodoroTimerViewModel * model,
+        {
+            model->scroll_counter = 0;
+            model->current_hint = hint;
+        },
+        true);
+}
+
 void flipp_pomodoro_view_timer_assign_animation(View* view) {
 void flipp_pomodoro_view_timer_assign_animation(View* view) {
     with_view_model(
     with_view_model(
         view,
         view,
         FlippPomodoroTimerViewModel * model,
         FlippPomodoroTimerViewModel * model,
         {
         {
-            furi_assert(model->state);
             if(model->icon) {
             if(model->icon) {
                 icon_animation_free(model->icon);
                 icon_animation_free(model->icon);
             }
             }
@@ -160,28 +228,55 @@ FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc() {
         flipp_pomodoro_view_timer_get_view(timer),
         flipp_pomodoro_view_timer_get_view(timer),
         ViewModelTypeLockFree,
         ViewModelTypeLockFree,
         sizeof(FlippPomodoroTimerViewModel));
         sizeof(FlippPomodoroTimerViewModel));
+
     view_set_context(flipp_pomodoro_view_timer_get_view(timer), timer);
     view_set_context(flipp_pomodoro_view_timer_get_view(timer), timer);
     view_set_draw_callback(timer->view, flipp_pomodoro_view_timer_draw_callback);
     view_set_draw_callback(timer->view, flipp_pomodoro_view_timer_draw_callback);
     view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback);
     view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback);
 
 
+    with_view_model(
+        flipp_pomodoro_view_timer_get_view(timer),
+        FlippPomodoroTimerViewModel * model,
+        { model->scroll_counter = 0; },
+        false);
+
     return timer;
     return timer;
 };
 };
 
 
+void flipp_pomodoro_view_timer_set_callback_context(
+    FlippPomodoroTimerView* timer,
+    void* callback_ctx) {
+    furi_assert(timer);
+    furi_assert(callback_ctx);
+    timer->callback_context = callback_ctx;
+}
+
 void flipp_pomodoro_view_timer_set_on_right_cb(
 void flipp_pomodoro_view_timer_set_on_right_cb(
     FlippPomodoroTimerView* timer,
     FlippPomodoroTimerView* timer,
-    FlippPomodoroTimerViewInputCb right_cb,
-    void* right_cb_ctx) {
+    FlippPomodoroTimerViewInputCb right_cb) {
+    furi_assert(timer);
     furi_assert(right_cb);
     furi_assert(right_cb);
-    furi_assert(right_cb_ctx);
     timer->right_cb = right_cb;
     timer->right_cb = right_cb;
-    timer->right_cb_ctx = right_cb_ctx;
 };
 };
 
 
+void flipp_pomodoro_view_timer_set_on_ok_cb(
+    FlippPomodoroTimerView* timer,
+    FlippPomodoroTimerViewInputCb ok_kb) {
+    furi_assert(ok_kb);
+    furi_assert(timer);
+    timer->ok_cb = ok_kb;
+}
+
 void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state) {
 void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state) {
     furi_assert(view);
     furi_assert(view);
     furi_assert(state);
     furi_assert(state);
     with_view_model(
     with_view_model(
-        view, FlippPomodoroTimerViewModel * model, { model->state = state; }, false);
+        view,
+        FlippPomodoroTimerViewModel * model,
+        {
+            model->state = state;
+            model->current_hint = NULL;
+        },
+        false);
     flipp_pomodoro_view_timer_assign_animation(view);
     flipp_pomodoro_view_timer_assign_animation(view);
 };
 };
 
 

+ 11 - 2
views/flipp_pomodoro_timer_view.h

@@ -15,7 +15,16 @@ void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer);
 
 
 void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state);
 void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state);
 
 
+void flipp_pomodoro_view_timer_set_callback_context(
+    FlippPomodoroTimerView* timer,
+    void* callback_ctx);
+
 void flipp_pomodoro_view_timer_set_on_right_cb(
 void flipp_pomodoro_view_timer_set_on_right_cb(
     FlippPomodoroTimerView* timer,
     FlippPomodoroTimerView* timer,
-    FlippPomodoroTimerViewInputCb right_cb,
-    void* right_cb_ctx);
+    FlippPomodoroTimerViewInputCb right_cb);
+
+void flipp_pomodoro_view_timer_set_on_ok_cb(
+    FlippPomodoroTimerView* timer,
+    FlippPomodoroTimerViewInputCb ok_cb);
+
+void flipp_pomodoro_view_timer_display_hint(View* view, char* hint);