Skip to content

Commit d329978

Browse files
authored
feat: on-demand contextual chatting (#211)
* improve existing texts * add hint bubble * add more replicas and reset * fix format
1 parent 52cc11a commit d329978

6 files changed

+216
-30
lines changed

flipp_pomodoro_app.h

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ typedef enum
1818
FlippPomodoroAppCustomEventStageSkip = 100,
1919
FlippPomodoroAppCustomEventStageComplete, // By Expiration
2020
FlippPomodoroAppCustomEventTimerTick,
21+
FlippPomodoroAppCustomEventTimerAskHint,
2122
FlippPomodoroAppCustomEventStateUpdated,
2223
FlippPomodoroAppCustomEventResumeTimer,
2324
} FlippPomodoroAppCustomEvent;

modules/flipp_pomodoro.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ PomodoroStage stages_sequence[] = {
1818
};
1919

2020
char *current_stage_label[] = {
21-
[FlippPomodoroStageFocus] = "Continue focus for:",
22-
[FlippPomodoroStageRest] = "Keep rest for:",
23-
[FlippPomodoroStageLongBreak] = "Long Break for:",
21+
[FlippPomodoroStageFocus] = "Focusing...",
22+
[FlippPomodoroStageRest] = "Short Break...",
23+
[FlippPomodoroStageLongBreak] = "Long Break...",
2424
};
2525

2626
char *next_stage_label[] = {

scenes/flipp_pomodoro_scene_timer.c

+91-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,50 @@ enum
1212
SceneEventNotConusmed = false
1313
};
1414

15+
static char *work_hints[] = {
16+
"Can you explain the problem as if I'm five?",
17+
"Expected output vs. reality: what's the difference?",
18+
"Ever thought of slicing the problem into bite-sized pieces?",
19+
"What's the story when you walk through the code?",
20+
"Any error messages gossiping about the issue?",
21+
"What tricks have you tried to fix this?",
22+
"Did you test the code, or just hoping for the best?",
23+
"How's this code mingling with the rest of the app?",
24+
"Any sneaky side effects causing mischief?",
25+
"What are you assuming, and is it safe to do so?",
26+
"Did you remember to invite all the edge cases to the party?",
27+
"What happens in the isolation chamber (running code separately)?",
28+
"Can you make the issue appear on command?",
29+
"What's the scene at the crime spot when the error occurs?",
30+
"Did you seek wisdom from the grand oracle (Google)?",
31+
"What if you take a different path to solve this?",
32+
"Did you take a coffee break to reboot your brain?"};
33+
34+
static char *break_hints[] = {
35+
"Time to stretch! Remember, your body isn't made of code.",
36+
"Hydrate or diedrate! Grab a glass of water.",
37+
"Blink! Your eyes need a break too.",
38+
"How about a quick dance-off with your shadow?",
39+
"Ever tried chair yoga? Now's the time!",
40+
"Time for a quick peek out the window. The outside world still exists!",
41+
"Quick, think about kittens! Or puppies! Or baby turtles!",
42+
"Time for a laugh. Look up a joke or two!",
43+
"Sing a song. Bonus points for making up your own lyrics.",
44+
"Do a quick tidy-up. A clean space is a happy space!",
45+
"Time to play 'air' musical instrument for a minute.",
46+
"How about a quick doodle? Unleash your inner Picasso!",
47+
"Practice your superhero pose. Feel the power surge!",
48+
"Quick, tell yourself a joke. Don't worry, I won't judge.",
49+
"Time to practice your mime skills. Stuck in a box, anyone?",
50+
"Ever tried juggling? Now's your chance!",
51+
"Do a quick self high-five, you're doing great!"};
52+
53+
static char *random_string_of_list(char **hints, size_t num_hints)
54+
{
55+
int random_index = rand() % num_hints;
56+
return hints[random_index];
57+
}
58+
1559
void flipp_pomodoro_scene_timer_sync_view_state(void *ctx)
1660
{
1761
furi_assert(ctx);
@@ -34,6 +78,14 @@ void flipp_pomodoro_scene_timer_on_next_stage(void *ctx)
3478
FlippPomodoroAppCustomEventStageSkip);
3579
};
3680

81+
void flipp_pomodoro_scene_timer_on_ask_hint(void *ctx)
82+
{
83+
FlippPomodoroApp *app = ctx;
84+
view_dispatcher_send_custom_event(
85+
app->view_dispatcher,
86+
FlippPomodoroAppCustomEventTimerAskHint);
87+
}
88+
3789
void flipp_pomodoro_scene_timer_on_enter(void *ctx)
3890
{
3991
furi_assert(ctx);
@@ -48,24 +100,55 @@ void flipp_pomodoro_scene_timer_on_enter(void *ctx)
48100

49101
view_dispatcher_switch_to_view(app->view_dispatcher, FlippPomodoroAppViewTimer);
50102
flipp_pomodoro_scene_timer_sync_view_state(app);
103+
104+
flipp_pomodoro_view_timer_set_callback_context(app->timer_view, app);
105+
106+
flipp_pomodoro_view_timer_set_on_ok_cb(
107+
app->timer_view,
108+
flipp_pomodoro_scene_timer_on_ask_hint);
109+
51110
flipp_pomodoro_view_timer_set_on_right_cb(
52111
app->timer_view,
53-
flipp_pomodoro_scene_timer_on_next_stage,
54-
app);
112+
flipp_pomodoro_scene_timer_on_next_stage);
55113
};
56114

57-
void flipp_pomodoro_scene_timer_handle_custom_event(FlippPomodoroApp *app, FlippPomodoroAppCustomEvent custom_event)
115+
char *flipp_pomodoro_scene_timer_get_contextual_hint(FlippPomodoroApp *app)
58116
{
59-
if (custom_event == FlippPomodoroAppCustomEventTimerTick && flipp_pomodoro__is_stage_expired(app->state))
117+
switch (flipp_pomodoro__get_stage(app->state))
60118
{
61-
view_dispatcher_send_custom_event(
62-
app->view_dispatcher,
63-
FlippPomodoroAppCustomEventStageComplete);
119+
case FlippPomodoroStageFocus:
120+
return random_string_of_list(work_hints, sizeof(work_hints) / sizeof(work_hints[0]));
121+
case FlippPomodoroStageRest:
122+
case FlippPomodoroStageLongBreak:
123+
return random_string_of_list(break_hints, sizeof(break_hints) / sizeof(break_hints[0]));
124+
default:
125+
return "What's up?";
64126
}
127+
}
65128

66-
if (custom_event == FlippPomodoroAppCustomEventStateUpdated)
129+
void flipp_pomodoro_scene_timer_handle_custom_event(FlippPomodoroApp *app, FlippPomodoroAppCustomEvent custom_event)
130+
{
131+
switch (custom_event)
67132
{
133+
case FlippPomodoroAppCustomEventTimerTick:
134+
if (flipp_pomodoro__is_stage_expired(app->state))
135+
{
136+
view_dispatcher_send_custom_event(
137+
app->view_dispatcher,
138+
FlippPomodoroAppCustomEventStageComplete);
139+
}
140+
break;
141+
case FlippPomodoroAppCustomEventStateUpdated:
68142
flipp_pomodoro_scene_timer_sync_view_state(app);
143+
break;
144+
case FlippPomodoroAppCustomEventTimerAskHint:
145+
flipp_pomodoro_view_timer_display_hint(
146+
flipp_pomodoro_view_timer_get_view(app->timer_view),
147+
flipp_pomodoro_scene_timer_get_contextual_hint(app));
148+
break;
149+
default:
150+
// optional: code to be executed if custom_event doesn't match any cases
151+
break;
69152
}
70153
};
71154

views/flipp_pomodoro_info_view.c

+3-3
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ static void flipp_pomodoro_info_view_draw_statistics(Canvas *canvas, FlippPomodo
3030
{
3131
FuriString *stats_string = furi_string_alloc();
3232

33-
furi_string_printf(stats_string, "So Long,\nand Thanks for All the Focus...\nand for completing\n%i pomodoro(s)", model->pomodoros_completed);
33+
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);
3434
const char *stats_string_formatted = furi_string_get_cstr(stats_string);
3535

3636
elements_text_box(
@@ -107,14 +107,14 @@ bool flipp_pomodoro_info_view_input_callback(InputEvent *event, void *ctx)
107107
{
108108
FlippPomodoroInfoView *info_view = ctx;
109109

110-
if (event->type == InputTypePress)
110+
if (event->type == InputTypePress)
111111
{
112112
if (event->key == InputKeyRight && info_view->resume_timer_cb != NULL)
113113
{
114114
info_view->resume_timer_cb(info_view->user_action_cb_ctx);
115115
return ViewInputConsumed;
116116
}
117-
else if (event->key == InputKeyLeft)
117+
else if (event->key == InputKeyLeft)
118118
{
119119
flipp_pomodoro_info_view_toggle_mode(info_view);
120120
return ViewInputConsumed;

views/flipp_pomodoro_timer_view.c

+111-15
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,16 @@ struct FlippPomodoroTimerView
2020
{
2121
View *view;
2222
FlippPomodoroTimerViewInputCb right_cb;
23-
void *right_cb_ctx;
23+
FlippPomodoroTimerViewInputCb ok_cb;
24+
void *callback_context;
2425
};
2526

2627
typedef struct
2728
{
2829
IconAnimation *icon;
2930
FlippPomodoroState *state;
31+
size_t scroll_counter;
32+
char *current_hint;
3033
} FlippPomodoroTimerViewModel;
3134

3235
static const Icon *stage_background_image[] = {
@@ -108,6 +111,58 @@ static void flipp_pomodoro_view_timer_draw_current_stage_label(Canvas *canvas, F
108111
flipp_pomodoro__current_stage_label(state));
109112
}
110113

114+
static void flipp_pomodoro_view_timer_draw_hint(Canvas *canvas, FlippPomodoroTimerViewModel *model)
115+
{
116+
size_t MAX_SCROLL_COUNTER = 300;
117+
uint8_t SCROLL_DELAY_FRAMES = 3;
118+
119+
if (model->scroll_counter >= MAX_SCROLL_COUNTER || model->current_hint == NULL)
120+
{
121+
return;
122+
}
123+
124+
uint8_t hint_width = 90;
125+
uint8_t hint_height = 18;
126+
127+
uint8_t hint_x = canvas_width(canvas) - hint_width - 6;
128+
uint8_t hint_y = 35;
129+
130+
FuriString *displayed_hint_string = furi_string_alloc();
131+
132+
furi_string_printf(
133+
displayed_hint_string,
134+
"%s",
135+
model->current_hint);
136+
137+
size_t perfect_duration = furi_string_size(displayed_hint_string) * 1.5;
138+
139+
if (model->scroll_counter > perfect_duration)
140+
{
141+
model->scroll_counter = MAX_SCROLL_COUNTER;
142+
furi_string_free(displayed_hint_string);
143+
return;
144+
}
145+
146+
size_t scroll_offset = (model->scroll_counter < SCROLL_DELAY_FRAMES) ? 0 : model->scroll_counter - SCROLL_DELAY_FRAMES;
147+
148+
canvas_set_color(canvas, ColorWhite);
149+
canvas_draw_box(canvas, hint_x, hint_y, hint_width + 3, hint_height);
150+
canvas_set_color(canvas, ColorBlack);
151+
152+
elements_bubble(canvas, hint_x, hint_y, hint_width, hint_height);
153+
154+
elements_scrollable_text_line(
155+
canvas,
156+
hint_x + 6,
157+
hint_y + 12,
158+
hint_width - 4,
159+
displayed_hint_string,
160+
scroll_offset,
161+
true);
162+
furi_string_free(displayed_hint_string);
163+
model->scroll_counter++;
164+
}
165+
111166
static void flipp_pomodoro_view_timer_draw_callback(Canvas *canvas, void *_model)
112167
{
113168
if (!_model)
@@ -128,10 +183,12 @@ static void flipp_pomodoro_view_timer_draw_callback(Canvas *canvas, void *_model
128183
flipp_pomodoro__stage_remaining_duration(model->state));
129184

130185
flipp_pomodoro_view_timer_draw_current_stage_label(canvas, model->state);
186+
131187
canvas_set_color(canvas, ColorBlack);
132188

133189
canvas_set_font(canvas, FontSecondary);
134190
elements_button_right(canvas, flipp_pomodoro__next_stage_label(model->state));
191+
flipp_pomodoro_view_timer_draw_hint(canvas, model);
135192
};
136193

137194
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)
140197
furi_assert(event);
141198
FlippPomodoroTimerView *timer = ctx;
142199

143-
const bool should_trigger_right_event_cb = (event->type == InputTypePress) &&
144-
(event->key == InputKeyRight) &&
145-
(timer->right_cb != NULL);
200+
const bool is_press_event = event->type == InputTypePress;
146201

147-
if (should_trigger_right_event_cb)
202+
if (!is_press_event)
148203
{
149-
furi_assert(timer->right_cb);
150-
furi_assert(timer->right_cb_ctx);
151-
timer->right_cb(timer->right_cb_ctx);
152-
return ViewInputConsumed;
153-
};
204+
return ViewInputNotConusmed;
205+
}
154206

155-
return ViewInputNotConusmed;
207+
switch (event->key)
208+
{
209+
case InputKeyRight:
210+
timer->right_cb(timer->callback_context);
211+
return ViewInputConsumed;
212+
case InputKeyOk:
213+
timer->ok_cb(timer->callback_context);
214+
return ViewInputConsumed;
215+
default:
216+
return ViewInputNotConusmed;
217+
}
156218
};
157219

158220
View *flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView *timer)
@@ -161,13 +223,24 @@ View *flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView *timer)
161223
return timer->view;
162224
};
163225

226+
void flipp_pomodoro_view_timer_display_hint(View *view, char *hint)
227+
{
228+
with_view_model(
229+
view,
230+
FlippPomodoroTimerViewModel * model,
231+
{
232+
model->scroll_counter = 0;
233+
model->current_hint = hint;
234+
},
235+
true);
236+
}
237+
164238
void flipp_pomodoro_view_timer_assign_animation(View *view)
165239
{
166240
with_view_model(
167241
view,
168242
FlippPomodoroTimerViewModel * model,
169243
{
170-
furi_assert(model->state);
171244
if (model->icon)
172245
{
173246
icon_animation_free(model->icon);
@@ -186,21 +259,43 @@ FlippPomodoroTimerView *flipp_pomodoro_view_timer_alloc()
186259
timer->view = view_alloc();
187260

188261
view_allocate_model(flipp_pomodoro_view_timer_get_view(timer), ViewModelTypeLockFree, sizeof(FlippPomodoroTimerViewModel));
262+
189263
view_set_context(flipp_pomodoro_view_timer_get_view(timer), timer);
190264
view_set_draw_callback(timer->view, flipp_pomodoro_view_timer_draw_callback);
191265
view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback);
192266

267+
with_view_model(
268+
flipp_pomodoro_view_timer_get_view(timer),
269+
FlippPomodoroTimerViewModel * model,
270+
{
271+
model->scroll_counter = 0;
272+
},
273+
false);
274+
193275
return timer;
194276
};
195277

196-
void flipp_pomodoro_view_timer_set_on_right_cb(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb right_cb, void *right_cb_ctx)
278+
void flipp_pomodoro_view_timer_set_callback_context(FlippPomodoroTimerView *timer, void *callback_ctx)
279+
{
280+
furi_assert(timer);
281+
furi_assert(callback_ctx);
282+
timer->callback_context = callback_ctx;
283+
}
284+
285+
void flipp_pomodoro_view_timer_set_on_right_cb(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb right_cb)
197286
{
287+
furi_assert(timer);
198288
furi_assert(right_cb);
199-
furi_assert(right_cb_ctx);
200289
timer->right_cb = right_cb;
201-
timer->right_cb_ctx = right_cb_ctx;
202290
};
203291

292+
void flipp_pomodoro_view_timer_set_on_ok_cb(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb ok_kb)
293+
{
294+
furi_assert(ok_kb);
295+
furi_assert(timer);
296+
timer->ok_cb = ok_kb;
297+
}
298+
204299
void flipp_pomodoro_view_timer_set_state(View *view, FlippPomodoroState *state)
205300
{
206301
furi_assert(view);
@@ -210,6 +305,7 @@ void flipp_pomodoro_view_timer_set_state(View *view, FlippPomodoroState *state)
210305
FlippPomodoroTimerViewModel * model,
211306
{
212307
model->state = state;
308+
model->current_hint = NULL;
213309
},
214310
false);
215311
flipp_pomodoro_view_timer_assign_animation(view);

views/flipp_pomodoro_timer_view.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,10 @@ void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView *timer);
1515

1616
void flipp_pomodoro_view_timer_set_state(View *view, FlippPomodoroState *state);
1717

18-
void flipp_pomodoro_view_timer_set_on_right_cb(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb right_cb, void *right_cb_ctx);
18+
void flipp_pomodoro_view_timer_set_callback_context(FlippPomodoroTimerView *timer, void *callback_ctx);
19+
20+
void flipp_pomodoro_view_timer_set_on_right_cb(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb right_cb);
21+
22+
void flipp_pomodoro_view_timer_set_on_ok_cb(FlippPomodoroTimerView *timer, FlippPomodoroTimerViewInputCb ok_cb);
23+
24+
void flipp_pomodoro_view_timer_display_hint(View *view, char *hint);

0 commit comments

Comments
 (0)