flipp_pomodoro_timer_view.c 8.5 KB

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