|
|
@@ -2,80 +2,175 @@
|
|
|
#include <furi_hal.h>
|
|
|
|
|
|
#include <gui/gui.h>
|
|
|
+#include <gui/elements.h>
|
|
|
#include <input/input.h>
|
|
|
|
|
|
/* Magic happens here -- this file is generated by fbt.
|
|
|
* Just set fap_icon_assets in application.fam and #include {APPID}_icons.h */
|
|
|
#include "flipp_pomodoro_icons.h"
|
|
|
|
|
|
+typedef enum {
|
|
|
+ TimerTickType,
|
|
|
+ InputEventType,
|
|
|
+} ActionType;
|
|
|
+
|
|
|
+typedef struct {
|
|
|
+ ActionType type;
|
|
|
+ void* payload;
|
|
|
+} Action;
|
|
|
+
|
|
|
+/**
|
|
|
+ * Flipp Pomodoro state management
|
|
|
+*/
|
|
|
+
|
|
|
+typedef enum {
|
|
|
+ Work,
|
|
|
+ Rest,
|
|
|
+} PomodoroStage;
|
|
|
+
|
|
|
typedef struct {
|
|
|
- uint8_t x, y;
|
|
|
-} ImagePosition;
|
|
|
+ uint8_t seconds;
|
|
|
+ uint8_t minutes;
|
|
|
+ uint8_t hours;
|
|
|
+ uint32_t total_seconds;
|
|
|
+} TimeDifference;
|
|
|
|
|
|
-static ImagePosition image_position = {.x = 0, .y = 0};
|
|
|
+typedef struct {
|
|
|
+ PomodoroStage stage;
|
|
|
+ uint32_t started_at_timestamp;
|
|
|
+} FlippPomodoroState;
|
|
|
+
|
|
|
+/// @brief Calculates difference between two provided timestamps
|
|
|
+/// @param begin
|
|
|
+/// @param end
|
|
|
+/// @return
|
|
|
+static TimeDifference get_timestamp_difference_seconds(uint32_t begin, uint32_t end) {
|
|
|
+ const uint32_t duration_seconds = end - begin;
|
|
|
+ return (TimeDifference){.total_seconds=duration_seconds};
|
|
|
+}
|
|
|
+
|
|
|
+static void flipp_pomodoro__toggle_stage(FlippPomodoroState* state) {
|
|
|
+ state->stage = state->stage == Work ? Rest : Work;
|
|
|
+ state->started_at_timestamp = furi_hal_rtc_get_timestamp();
|
|
|
+}
|
|
|
+
|
|
|
+static char* flipp_pomodoro__next_stage_label(FlippPomodoroState* state) {
|
|
|
+ return state->stage == Work ? "To rest" : "To work";
|
|
|
+};
|
|
|
+
|
|
|
+static TimeDifference flipp_pomodoro__stage_duration(FlippPomodoroState* state) {
|
|
|
+ const uint32_t now = furi_hal_rtc_get_timestamp();
|
|
|
+ return get_timestamp_difference_seconds(state->started_at_timestamp, now);
|
|
|
+}
|
|
|
+
|
|
|
+static void flipp_pomodoro__destroy(FlippPomodoroState* state) {
|
|
|
+ free(state);
|
|
|
+}
|
|
|
+
|
|
|
+static FlippPomodoroState flipp_pomodoro__new() {
|
|
|
+ const uint32_t now = furi_hal_rtc_get_timestamp();
|
|
|
+ const FlippPomodoroState new_state = {.stage=Work, .started_at_timestamp=now};
|
|
|
+ return new_state;
|
|
|
+}
|
|
|
|
|
|
// Screen is 128x64 px
|
|
|
static void app_draw_callback(Canvas* canvas, void* ctx) {
|
|
|
- UNUSED(ctx);
|
|
|
-
|
|
|
+ // WARNING: place no side-effects into rener cycle
|
|
|
canvas_clear(canvas);
|
|
|
- canvas_draw_icon(canvas, image_position.x % 128, image_position.y % 64, &I_dolphin_71x25);
|
|
|
+ FlippPomodoroState* state = ctx;
|
|
|
+
|
|
|
+ FuriString* timer_string = furi_string_alloc();
|
|
|
+
|
|
|
+ const uint32_t now = flipp_pomodoro__stage_duration(state).total_seconds;
|
|
|
+
|
|
|
+ furi_string_printf(timer_string, "%lu", now);
|
|
|
+
|
|
|
+ elements_text_box(canvas, 50, 20, 30, 50, AlignCenter, AlignCenter, furi_string_get_cstr(timer_string), false);
|
|
|
+ elements_button_right(canvas, flipp_pomodoro__next_stage_label(state));
|
|
|
+
|
|
|
+ furi_string_free(timer_string);
|
|
|
+}
|
|
|
+
|
|
|
+static void clock_tick_callback(void* ctx) {
|
|
|
+
|
|
|
+ furi_assert(ctx);
|
|
|
+ FuriMessageQueue* queue = ctx;
|
|
|
+ Action action = {.type = TimerTickType};
|
|
|
+ // It's OK to loose this event if system overloaded
|
|
|
+ furi_message_queue_put(queue, &action, 0);
|
|
|
}
|
|
|
|
|
|
static void app_input_callback(InputEvent* input_event, void* ctx) {
|
|
|
furi_assert(ctx);
|
|
|
|
|
|
+ Action action = {.type=InputEventType, .payload=input_event};
|
|
|
+
|
|
|
FuriMessageQueue* event_queue = ctx;
|
|
|
- furi_message_queue_put(event_queue, input_event, FuriWaitForever);
|
|
|
+ furi_message_queue_put(event_queue, &action, FuriWaitForever);
|
|
|
+};
|
|
|
+
|
|
|
+static bool input_events_reducer (FlippPomodoroState* state, InputEvent* input_event) {
|
|
|
+ bool keep_running = true;
|
|
|
+ if((input_event->type == InputTypePress) || (input_event->type == InputTypeRepeat)) {
|
|
|
+ switch(input_event->key) {
|
|
|
+ case InputKeyRight:
|
|
|
+ flipp_pomodoro__toggle_stage(state);
|
|
|
+ break;
|
|
|
+ case InputKeyBack:
|
|
|
+ keep_running = false;
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return keep_running;
|
|
|
}
|
|
|
|
|
|
int32_t flipp_pomodoro_main(void* p) {
|
|
|
UNUSED(p);
|
|
|
- FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(InputEvent));
|
|
|
+ FuriMessageQueue* event_queue = furi_message_queue_alloc(8, sizeof(Action));
|
|
|
+
|
|
|
+ FlippPomodoroState state = flipp_pomodoro__new();
|
|
|
|
|
|
// Configure view port
|
|
|
ViewPort* view_port = view_port_alloc();
|
|
|
- view_port_draw_callback_set(view_port, app_draw_callback, view_port);
|
|
|
+ view_port_draw_callback_set(view_port, app_draw_callback, &state);
|
|
|
view_port_input_callback_set(view_port, app_input_callback, event_queue);
|
|
|
|
|
|
+ FuriTimer* timer = furi_timer_alloc(clock_tick_callback, FuriTimerTypePeriodic, &event_queue);
|
|
|
+
|
|
|
// Register view port in GUI
|
|
|
Gui* gui = furi_record_open(RECORD_GUI);
|
|
|
gui_add_view_port(gui, view_port, GuiLayerFullscreen);
|
|
|
|
|
|
- InputEvent event;
|
|
|
+ furi_timer_start(timer, 500);
|
|
|
+
|
|
|
+ Action action;
|
|
|
|
|
|
bool running = true;
|
|
|
while(running) {
|
|
|
- if(furi_message_queue_get(event_queue, &event, 100) == FuriStatusOk) {
|
|
|
- if((event.type == InputTypePress) || (event.type == InputTypeRepeat)) {
|
|
|
- switch(event.key) {
|
|
|
- case InputKeyLeft:
|
|
|
- image_position.x -= 2;
|
|
|
- break;
|
|
|
- case InputKeyRight:
|
|
|
- image_position.x += 2;
|
|
|
- break;
|
|
|
- case InputKeyUp:
|
|
|
- image_position.y -= 2;
|
|
|
- break;
|
|
|
- case InputKeyDown:
|
|
|
- image_position.y += 2;
|
|
|
- break;
|
|
|
- default:
|
|
|
- running = false;
|
|
|
- break;
|
|
|
- }
|
|
|
+ if(furi_message_queue_get(event_queue, &action, 200) == FuriStatusOk) {
|
|
|
+ switch (action.type) {
|
|
|
+ case InputEventType:
|
|
|
+ running = input_events_reducer(&state, action.payload);
|
|
|
+ break;
|
|
|
+ case TimerTickType:
|
|
|
+ // TODO: track time is over and make switch
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ break;
|
|
|
}
|
|
|
+ view_port_update(view_port);
|
|
|
}
|
|
|
- view_port_update(view_port);
|
|
|
}
|
|
|
|
|
|
view_port_enabled_set(view_port, false);
|
|
|
gui_remove_view_port(gui, view_port);
|
|
|
view_port_free(view_port);
|
|
|
furi_message_queue_free(event_queue);
|
|
|
-
|
|
|
furi_record_close(RECORD_GUI);
|
|
|
+ furi_timer_free(timer);
|
|
|
+ flipp_pomodoro__destroy(&state);
|
|
|
|
|
|
return 0;
|
|
|
}
|