flipp_pomodoro_timer_view.c 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. #include "flipp_pomodoro_timer_view.h"
  2. #include <furi.h>
  3. #include <gui/gui.h>
  4. #include <gui/elements.h>
  5. #include <gui/view.h>
  6. #include "../helpers/debug.h"
  7. #include "../flipp_pomodoro_app.h"
  8. #include "../modules/flipp_pomodoro.h"
  9. // Auto-compiled icons
  10. #include "flipp_pomodoro_icons.h"
  11. enum {
  12. ViewInputConsumed = true,
  13. ViewInputNotConusmed = false,
  14. };
  15. struct FlippPomodoroTimerView {
  16. View* view;
  17. FlippPomodoroTimerViewInputCb right_cb;
  18. FlippPomodoroTimerViewInputCb ok_cb;
  19. void* callback_context;
  20. };
  21. typedef struct {
  22. IconAnimation* icon;
  23. FlippPomodoroState* state;
  24. size_t scroll_counter;
  25. char* current_hint;
  26. } FlippPomodoroTimerViewModel;
  27. static const Icon* stage_background_image[] = {
  28. [FlippPomodoroStageFocus] = &A_flipp_pomodoro_focus_64,
  29. [FlippPomodoroStageRest] = &A_flipp_pomodoro_rest_64,
  30. [FlippPomodoroStageLongBreak] = &A_flipp_pomodoro_rest_64,
  31. };
  32. static void
  33. flipp_pomodoro_view_timer_draw_countdown(Canvas* canvas, TimeDifference remaining_time) {
  34. canvas_set_font(canvas, FontBigNumbers);
  35. const uint8_t right_border_margin = 1;
  36. const uint8_t countdown_box_height = canvas_height(canvas) * 0.4;
  37. const uint8_t countdown_box_width = canvas_width(canvas) * 0.5;
  38. const uint8_t countdown_box_x =
  39. canvas_width(canvas) - countdown_box_width - right_border_margin;
  40. const uint8_t countdown_box_y = 15;
  41. elements_bold_rounded_frame(
  42. canvas, countdown_box_x, countdown_box_y, countdown_box_width, countdown_box_height);
  43. FuriString* timer_string = furi_string_alloc();
  44. furi_string_printf(timer_string, "%02u:%02u", remaining_time.minutes, remaining_time.seconds);
  45. const char* remaining_stage_time_string = furi_string_get_cstr(timer_string);
  46. canvas_draw_str_aligned(
  47. canvas,
  48. countdown_box_x + (countdown_box_width / 2),
  49. countdown_box_y + (countdown_box_height / 2),
  50. AlignCenter,
  51. AlignCenter,
  52. remaining_stage_time_string);
  53. furi_string_free(timer_string);
  54. };
  55. static void draw_str_with_drop_shadow(
  56. Canvas* canvas,
  57. uint8_t x,
  58. uint8_t y,
  59. Align horizontal,
  60. Align vertical,
  61. const char* str) {
  62. canvas_set_color(canvas, ColorWhite);
  63. for(int x_off = -2; x_off <= 2; x_off++) {
  64. for(int y_off = -2; y_off <= 2; y_off++) {
  65. canvas_draw_str_aligned(canvas, x + x_off, y + y_off, horizontal, vertical, str);
  66. }
  67. }
  68. canvas_set_color(canvas, ColorBlack);
  69. canvas_draw_str_aligned(canvas, x, y, horizontal, vertical, str);
  70. }
  71. static void
  72. flipp_pomodoro_view_timer_draw_current_stage_label(Canvas* canvas, FlippPomodoroState* state) {
  73. canvas_set_font(canvas, FontPrimary);
  74. draw_str_with_drop_shadow(
  75. canvas,
  76. canvas_width(canvas),
  77. 0,
  78. AlignRight,
  79. AlignTop,
  80. flipp_pomodoro__current_stage_label(state));
  81. }
  82. static void
  83. flipp_pomodoro_view_timer_draw_hint(Canvas* canvas, FlippPomodoroTimerViewModel* model) {
  84. size_t MAX_SCROLL_COUNTER = 300;
  85. uint8_t SCROLL_DELAY_FRAMES = 3;
  86. if(model->scroll_counter >= MAX_SCROLL_COUNTER || model->current_hint == NULL) {
  87. return;
  88. }
  89. uint8_t hint_width = 90;
  90. uint8_t hint_height = 18;
  91. uint8_t hint_x = canvas_width(canvas) - hint_width - 6;
  92. uint8_t hint_y = 35;
  93. FuriString* displayed_hint_string = furi_string_alloc();
  94. furi_string_printf(displayed_hint_string, "%s", model->current_hint);
  95. size_t perfect_duration = furi_string_size(displayed_hint_string) * 1.5;
  96. if(model->scroll_counter > perfect_duration) {
  97. model->scroll_counter = MAX_SCROLL_COUNTER;
  98. furi_string_free(displayed_hint_string);
  99. return;
  100. }
  101. size_t scroll_offset = (model->scroll_counter < SCROLL_DELAY_FRAMES) ?
  102. 0 :
  103. model->scroll_counter - SCROLL_DELAY_FRAMES;
  104. canvas_set_color(canvas, ColorWhite);
  105. canvas_draw_box(canvas, hint_x, hint_y, hint_width + 3, hint_height);
  106. canvas_set_color(canvas, ColorBlack);
  107. elements_bubble(canvas, hint_x, hint_y, hint_width, hint_height);
  108. elements_scrollable_text_line(
  109. canvas,
  110. hint_x + 6,
  111. hint_y + 12,
  112. hint_width - 4,
  113. displayed_hint_string,
  114. scroll_offset,
  115. true);
  116. furi_string_free(displayed_hint_string);
  117. model->scroll_counter++;
  118. }
  119. static void flipp_pomodoro_view_timer_draw_callback(Canvas* canvas, void* _model) {
  120. if(!_model) {
  121. return;
  122. };
  123. FlippPomodoroTimerViewModel* model = _model;
  124. canvas_clear(canvas);
  125. if(model->icon) {
  126. canvas_draw_icon_animation(canvas, 0, 0, model->icon);
  127. }
  128. flipp_pomodoro_view_timer_draw_countdown(
  129. canvas, flipp_pomodoro__stage_remaining_duration(model->state));
  130. flipp_pomodoro_view_timer_draw_current_stage_label(canvas, model->state);
  131. canvas_set_color(canvas, ColorBlack);
  132. canvas_set_font(canvas, FontSecondary);
  133. elements_button_right(canvas, flipp_pomodoro__next_stage_label(model->state));
  134. flipp_pomodoro_view_timer_draw_hint(canvas, model);
  135. };
  136. bool flipp_pomodoro_view_timer_input_callback(InputEvent* event, void* ctx) {
  137. furi_assert(ctx);
  138. furi_assert(event);
  139. FlippPomodoroTimerView* timer = ctx;
  140. const bool is_press_event = event->type == InputTypePress;
  141. if(!is_press_event) {
  142. return ViewInputNotConusmed;
  143. }
  144. switch(event->key) {
  145. case InputKeyRight:
  146. timer->right_cb(timer->callback_context);
  147. return ViewInputConsumed;
  148. case InputKeyOk:
  149. timer->ok_cb(timer->callback_context);
  150. return ViewInputConsumed;
  151. default:
  152. return ViewInputNotConusmed;
  153. }
  154. };
  155. View* flipp_pomodoro_view_timer_get_view(FlippPomodoroTimerView* timer) {
  156. furi_assert(timer);
  157. return timer->view;
  158. };
  159. void flipp_pomodoro_view_timer_display_hint(View* view, char* hint) {
  160. with_view_model(
  161. view,
  162. FlippPomodoroTimerViewModel * model,
  163. {
  164. model->scroll_counter = 0;
  165. model->current_hint = hint;
  166. },
  167. true);
  168. }
  169. void flipp_pomodoro_view_timer_assign_animation(View* view) {
  170. with_view_model(
  171. view,
  172. FlippPomodoroTimerViewModel * model,
  173. {
  174. if(model->icon) {
  175. icon_animation_free(model->icon);
  176. }
  177. model->icon = icon_animation_alloc(
  178. stage_background_image[flipp_pomodoro__get_stage(model->state)]);
  179. view_tie_icon_animation(view, model->icon);
  180. icon_animation_start(model->icon);
  181. },
  182. true);
  183. }
  184. FlippPomodoroTimerView* flipp_pomodoro_view_timer_alloc() {
  185. FlippPomodoroTimerView* timer = malloc(sizeof(FlippPomodoroTimerView));
  186. timer->view = view_alloc();
  187. view_allocate_model(
  188. flipp_pomodoro_view_timer_get_view(timer),
  189. ViewModelTypeLockFree,
  190. sizeof(FlippPomodoroTimerViewModel));
  191. view_set_context(flipp_pomodoro_view_timer_get_view(timer), timer);
  192. view_set_draw_callback(timer->view, flipp_pomodoro_view_timer_draw_callback);
  193. view_set_input_callback(timer->view, flipp_pomodoro_view_timer_input_callback);
  194. with_view_model(
  195. flipp_pomodoro_view_timer_get_view(timer),
  196. FlippPomodoroTimerViewModel * model,
  197. { model->scroll_counter = 0; },
  198. false);
  199. return timer;
  200. };
  201. void flipp_pomodoro_view_timer_set_callback_context(
  202. FlippPomodoroTimerView* timer,
  203. void* callback_ctx) {
  204. furi_assert(timer);
  205. furi_assert(callback_ctx);
  206. timer->callback_context = callback_ctx;
  207. }
  208. void flipp_pomodoro_view_timer_set_on_right_cb(
  209. FlippPomodoroTimerView* timer,
  210. FlippPomodoroTimerViewInputCb right_cb) {
  211. furi_assert(timer);
  212. furi_assert(right_cb);
  213. timer->right_cb = right_cb;
  214. };
  215. void flipp_pomodoro_view_timer_set_on_ok_cb(
  216. FlippPomodoroTimerView* timer,
  217. FlippPomodoroTimerViewInputCb ok_kb) {
  218. furi_assert(ok_kb);
  219. furi_assert(timer);
  220. timer->ok_cb = ok_kb;
  221. }
  222. void flipp_pomodoro_view_timer_set_state(View* view, FlippPomodoroState* state) {
  223. furi_assert(view);
  224. furi_assert(state);
  225. with_view_model(
  226. view,
  227. FlippPomodoroTimerViewModel * model,
  228. {
  229. model->state = state;
  230. model->current_hint = NULL;
  231. },
  232. false);
  233. flipp_pomodoro_view_timer_assign_animation(view);
  234. };
  235. void flipp_pomodoro_view_timer_free(FlippPomodoroTimerView* timer) {
  236. furi_assert(timer);
  237. with_view_model(
  238. timer->view,
  239. FlippPomodoroTimerViewModel * model,
  240. { icon_animation_free(model->icon); },
  241. false);
  242. view_free(timer->view);
  243. free(timer);
  244. };