#include "flipp_pomodoro_timer_view.h" #include #include #include #include #include "../helpers/debug.h" #include "../flipp_pomodoro_app.h" #include "../modules/flipp_pomodoro.h" // Auto-compiled icons #include "flipp_pomodoro_icons.h" enum { ViewInputConsumed = true, ViewInputNotConusmed = false, }; struct FlippPomodoroTimerView { View* view; FlippPomodoroTimerViewInputCb right_cb; 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[] = { [FlippPomodoroStageFocus] = &A_flipp_pomodoro_focus_64, [FlippPomodoroStageRest] = &A_flipp_pomodoro_rest_64, [FlippPomodoroStageLongBreak] = &A_flipp_pomodoro_rest_64, }; static void flipp_pomodoro_view_timer_draw_countdown(Canvas* canvas, TimeDifference remaining_time) { canvas_set_font(canvas, FontBigNumbers); const uint8_t right_border_margin = 1; 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 - right_border_margin; const uint8_t countdown_box_y = 15; elements_bold_rounded_frame( canvas, countdown_box_x, countdown_box_y, countdown_box_width, countdown_box_height); FuriString* timer_string = furi_string_alloc(); furi_string_printf(timer_string, "%02u:%02u", remaining_time.minutes, remaining_time.seconds); const char* remaining_stage_time_string = furi_string_get_cstr(timer_string); 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); furi_string_free(timer_string); }; static void draw_str_with_drop_shadow( Canvas* canvas, uint8_t x, uint8_t y, Align horizontal, Align vertical, const char* str) { canvas_set_color(canvas, ColorWhite); for(int x_off = -2; x_off <= 2; x_off++) { for(int y_off = -2; y_off <= 2; y_off++) { canvas_draw_str_aligned(canvas, x + x_off, y + y_off, horizontal, vertical, str); } } canvas_set_color(canvas, ColorBlack); canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, str); } static void flipp_pomodoro_view_timer_draw_current_stage_label(Canvas* canvas, FlippPomodoroState* state) { canvas_set_font(canvas, FontPrimary); draw_str_with_drop_shadow( canvas, canvas_width(canvas), 0, AlignRight, AlignTop, 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) { return; }; FlippPomodoroTimerViewModel* model = _model; canvas_clear(canvas); if(model->icon) { canvas_draw_icon_animation(canvas, 0, 0, model->icon); } flipp_pomodoro_view_timer_draw_countdown( canvas, 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) { furi_assert(ctx); furi_assert(event); FlippPomodoroTimerView* timer = ctx; const bool is_press_event = event->type == InputTypePress; if(!is_press_event) { 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) { furi_assert(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, { if(model->icon) { icon_animation_free(model->icon); } model->icon = icon_animation_alloc( stage_background_image[flipp_pomodoro__get_stage(model->state)]); view_tie_icon_animation(view, model->icon); icon_animation_start(model->icon); }, true); } FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc() { FlippPomodoroTimerView* timer = malloc(sizeof(FlippPomodoroTimerView)); 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_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); timer->right_cb = right_cb; }; 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); furi_assert(state); with_view_model( view, FlippPomodoroTimerViewModel * model, { model->state = state; model->current_hint = NULL; }, false); flipp_pomodoro_view_timer_assign_animation(view); }; void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer) { furi_assert(timer); with_view_model( timer->view, FlippPomodoroTimerViewModel * model, { icon_animation_free(model->icon); }, false); view_free(timer->view); free(timer); };