浏览代码

feat: on-demand contextual chatting (#211)

* improve existing texts

* add hint bubble

* add more replicas and reset

* fix format
Mikhail Gubenko 2 年之前
父节点
当前提交
d3299783cd

+ 1 - 0
flipp_pomodoro_app.h

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

+ 3 - 3
modules/flipp_pomodoro.c

@@ -18,9 +18,9 @@ PomodoroStage stages_sequence[] = {
 };
 
 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[] = {

+ 91 - 8
scenes/flipp_pomodoro_scene_timer.c

@@ -12,6 +12,50 @@ enum
     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)
 {
     furi_assert(ctx);
@@ -34,6 +78,14 @@ void flipp_pomodoro_scene_timer_on_next_stage(void *ctx)
         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)
 {
     furi_assert(ctx);
@@ -48,24 +100,55 @@ void flipp_pomodoro_scene_timer_on_enter(void *ctx)
 
     view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewTimer);
     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(
         app->timer_view,
-        flipp_pomodoro_scene_timer_on_next_stage,
-        app);
+        flipp_pomodoro_scene_timer_on_next_stage);
 };
 
-void flipp_pomodoro_scene_timer_handle_custom_event(FlippPomodoroApp *app, FlippPomodoroAppCustomEvent custom_event)
+char *flipp_pomodoro_scene_timer_get_contextual_hint(FlippPomodoroApp *app)
 {
-    if (custom_event == FlippPomodoroAppCustomEventTimerTick && flipp_pomodoro__is_stage_expired(app->state))
+    switch (flipp_pomodoro__get_stage(app->state))
     {
-        view_dispatcher_send_custom_event(
-            app->view_dispatcher,
-            FlippPomodoroAppCustomEventStageComplete);
+    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?";
     }
+}
 
-    if (custom_event == FlippPomodoroAppCustomEventStateUpdated)
+void flipp_pomodoro_scene_timer_handle_custom_event(FlippPomodoroApp *app, FlippPomodoroAppCustomEvent custom_event)
+{
+    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);
+        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;
     }
 };
 

+ 3 - 3
views/flipp_pomodoro_info_view.c

@@ -30,7 +30,7 @@ static void flipp_pomodoro_info_view_draw_statistics(Canvas *canvas, FlippPomodo
 {
     FuriString *stats_string = furi_string_alloc();
 
-    furi_string_printf(stats_string, "So Long,\nand Thanks for All the Focus...\nand for completing\n%i pomodoro(s)", model->pomodoros_completed);
+    furi_string_printf(stats_string, "So Long,\nand Thanks for All the Focus...\nand for completing\n\e#%i\e# pomodoro(s)", model->pomodoros_completed);
     const char *stats_string_formatted = furi_string_get_cstr(stats_string);
 
     elements_text_box(
@@ -107,14 +107,14 @@ bool flipp_pomodoro_info_view_input_callback(InputEvent *event, void *ctx)
 {
     FlippPomodoroInfoView *info_view = ctx;
 
-    if (event->type == InputTypePress) 
+    if (event->type == InputTypePress)
     {
         if (event->key == InputKeyRight && info_view->resume_timer_cb != NULL)
         {
             info_view->resume_timer_cb(info_view->user_action_cb_ctx);
             return ViewInputConsumed;
         }
-        else if (event->key == InputKeyLeft) 
+        else if (event->key == InputKeyLeft)
         {
             flipp_pomodoro_info_view_toggle_mode(info_view);
             return ViewInputConsumed;

+ 111 - 15
views/flipp_pomodoro_timer_view.c

@@ -20,13 +20,16 @@ struct FlippPomodoroTimerView
 {
     View *view;
     FlippPomodoroTimerViewInputCb right_cb;
-    void *right_cb_ctx;
+    FlippPomodoroTimerViewInputCb ok_cb;
+    void *callback_context;
 };
 
 typedef struct
 {
     IconAnimation *icon;
     FlippPomodoroState *state;
+    size_t scroll_counter;
+    char *current_hint;
 } FlippPomodoroTimerViewModel;
 
 static const Icon *stage_background_image[] = {
@@ -108,6 +111,58 @@ static void flipp_pomodoro_view_timer_draw_current_stage_label(Canvas *canvas, F
         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)
 {
     if (!_model)
@@ -128,10 +183,12 @@ static void flipp_pomodoro_view_timer_draw_callback(Canvas *canvas, void *_model
         flipp_pomodoro__stage_remaining_duration(model->state));
 
     flipp_pomodoro_view_timer_draw_current_stage_label(canvas, model->state);
+
     canvas_set_color(canvas, ColorBlack);
 
     canvas_set_font(canvas, FontSecondary);
     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)
@@ -140,19 +197,24 @@ bool flipp_pomodoro_view_timer_input_callback(InputEvent *event, void *ctx)
     furi_assert(event);
     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)
+    if (!is_press_event)
     {
-        furi_assert(timer->right_cb);
-        furi_assert(timer->right_cb_ctx);
-        timer->right_cb(timer->right_cb_ctx);
-        return ViewInputConsumed;
-    };
+        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)
@@ -161,13 +223,24 @@ View *flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView *timer)
     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)
 {
     with_view_model(
         view,
         FlippPomodoroTimerViewModel * model,
         {
-            furi_assert(model->state);
             if (model->icon)
             {
                 icon_animation_free(model->icon);
@@ -186,21 +259,43 @@ FlippPomodoroTimerView *flipp_pomodoro_view_timer_alloc()
     timer->view = view_alloc();
 
     view_allocate_model(flipp_pomodoro_view_timer_get_view(timer), ViewModelTypeLockFree, sizeof(FlippPomodoroTimerViewModel));
+
     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_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;
 };
 
-void flipp_pomodoro_view_timer_set_on_right_cb(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb right_cb, void *right_cb_ctx)
+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(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb right_cb)
 {
+    furi_assert(timer);
     furi_assert(right_cb);
-    furi_assert(right_cb_ctx);
     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)
 {
     furi_assert(view);
@@ -210,6 +305,7 @@ void flipp_pomodoro_view_timer_set_state(View *view, FlippPomodoroState *state)
         FlippPomodoroTimerViewModel * model,
         {
             model->state = state;
+            model->current_hint = NULL;
         },
         false);
     flipp_pomodoro_view_timer_assign_animation(view);

+ 7 - 1
views/flipp_pomodoro_timer_view.h

@@ -15,4 +15,10 @@ 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_on_right_cb(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb right_cb, void *right_cb_ctx);
+void flipp_pomodoro_view_timer_set_callback_context(FlippPomodoroTimerView *timer, void *callback_ctx);
+
+void flipp_pomodoro_view_timer_set_on_right_cb(FlippPomodoroTimerView *timer, 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);