From d3299783cde2162f09b54deb48f6c0c80190c6d8 Mon Sep 17 00:00:00 2001 From: Mikhail Gubenko <19877114+Th3Un1q3@users.noreply.github.com> Date: Sun, 16 Jul 2023 15:50:45 +0200 Subject: [PATCH] feat: on-demand contextual chatting (#211) * improve existing texts * add hint bubble * add more replicas and reset * fix format --- flipp_pomodoro_app.h | 1 + modules/flipp_pomodoro.c | 6 +- scenes/flipp_pomodoro_scene_timer.c | 99 ++++++++++++++++++++-- views/flipp_pomodoro_info_view.c | 6 +- views/flipp_pomodoro_timer_view.c | 126 ++++++++++++++++++++++++---- views/flipp_pomodoro_timer_view.h | 8 +- 6 files changed, 216 insertions(+), 30 deletions(-) diff --git a/flipp_pomodoro_app.h b/flipp_pomodoro_app.h index b6b0275825b..6934823f5c4 100644 --- a/flipp_pomodoro_app.h +++ b/flipp_pomodoro_app.h @@ -18,6 +18,7 @@ typedef enum FlippPomodoroAppCustomEventStageSkip = 100, FlippPomodoroAppCustomEventStageComplete, // By Expiration FlippPomodoroAppCustomEventTimerTick, + FlippPomodoroAppCustomEventTimerAskHint, FlippPomodoroAppCustomEventStateUpdated, FlippPomodoroAppCustomEventResumeTimer, } FlippPomodoroAppCustomEvent; diff --git a/modules/flipp_pomodoro.c b/modules/flipp_pomodoro.c index 726b5a19214..cef08d4d045 100644 --- a/modules/flipp_pomodoro.c +++ b/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[] = { diff --git a/scenes/flipp_pomodoro_scene_timer.c b/scenes/flipp_pomodoro_scene_timer.c index 74033a3a1d5..6869c2da274 100644 --- a/scenes/flipp_pomodoro_scene_timer.c +++ b/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; } }; diff --git a/views/flipp_pomodoro_info_view.c b/views/flipp_pomodoro_info_view.c index 770be391b0d..dafec917718 100644 --- a/views/flipp_pomodoro_info_view.c +++ b/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; diff --git a/views/flipp_pomodoro_timer_view.c b/views/flipp_pomodoro_timer_view.c index efb84640b2c..bbc67dd1a6e 100644 --- a/views/flipp_pomodoro_timer_view.c +++ b/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); diff --git a/views/flipp_pomodoro_timer_view.h b/views/flipp_pomodoro_timer_view.h index e94c6f4f635..b3af9c54a76 100644 --- a/views/flipp_pomodoro_timer_view.h +++ b/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);