├── .gitignore ├── misc ├── 1.png ├── 2.png ├── 3.png ├── 4.png └── 5.png ├── .github └── FUNDING.yml ├── dist └── pomodoro.fap ├── pomodoro_timer.png ├── assets ├── Ok_btn_9x9.png ├── Space_65x18.png ├── ButtonLeft_4x7.png └── Pin_back_arrow_10x8.png ├── views ├── pomodoro_10.h ├── pomodoro_25.h ├── pomodoro_50.h ├── pomodoro_52.h ├── pomodoro_10.c ├── pomodoro_25.c ├── pomodoro_50.c └── pomodoro_52.c ├── application.fam ├── pomodoro_timer.h ├── pomodoro.h ├── README.md ├── pomodoro_timer_icon.svg ├── pomodoro.c ├── LICENSE └── pomodoro_timer.c /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /misc/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/misc/1.png -------------------------------------------------------------------------------- /misc/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/misc/2.png -------------------------------------------------------------------------------- /misc/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/misc/3.png -------------------------------------------------------------------------------- /misc/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/misc/4.png -------------------------------------------------------------------------------- /misc/5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/misc/5.png -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: sbrin 4 | -------------------------------------------------------------------------------- /dist/pomodoro.fap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/dist/pomodoro.fap -------------------------------------------------------------------------------- /pomodoro_timer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/pomodoro_timer.png -------------------------------------------------------------------------------- /assets/Ok_btn_9x9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/assets/Ok_btn_9x9.png -------------------------------------------------------------------------------- /assets/Space_65x18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/assets/Space_65x18.png -------------------------------------------------------------------------------- /assets/ButtonLeft_4x7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/assets/ButtonLeft_4x7.png -------------------------------------------------------------------------------- /assets/Pin_back_arrow_10x8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sbrin/flipperzero_pomodoro/HEAD/assets/Pin_back_arrow_10x8.png -------------------------------------------------------------------------------- /views/pomodoro_10.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../pomodoro_timer.h" 5 | 6 | PomodoroTimer* pomodoro_10_alloc(); 7 | 8 | void pomodoro_10_free(PomodoroTimer* pomodoro_10); 9 | 10 | View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10); 11 | -------------------------------------------------------------------------------- /views/pomodoro_25.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../pomodoro_timer.h" 5 | 6 | PomodoroTimer* pomodoro_25_alloc(); 7 | 8 | void pomodoro_25_free(PomodoroTimer* pomodoro_25); 9 | 10 | View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25); 11 | -------------------------------------------------------------------------------- /views/pomodoro_50.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../pomodoro_timer.h" 5 | 6 | PomodoroTimer* pomodoro_50_alloc(); 7 | 8 | void pomodoro_50_free(PomodoroTimer* pomodoro_50); 9 | 10 | View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50); 11 | -------------------------------------------------------------------------------- /views/pomodoro_52.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "../pomodoro_timer.h" 5 | 6 | PomodoroTimer* pomodoro_52_alloc(); 7 | 8 | void pomodoro_52_free(PomodoroTimer* pomodoro_52); 9 | 10 | View* pomodoro_52_get_view(PomodoroTimer* pomodoro_52); 11 | -------------------------------------------------------------------------------- /application.fam: -------------------------------------------------------------------------------- 1 | App( 2 | appid="pomodoro", 3 | apptype=FlipperAppType.EXTERNAL, 4 | name="Pomodoro Timer", 5 | entry_point="pomodoro_app", 6 | stack_size=1 * 1024, 7 | cdefines=["APP_POMODORO"], 8 | requires=[ 9 | "gui", 10 | ], 11 | order=10, 12 | fap_icon="pomodoro_timer.png", 13 | fap_category="Tools", 14 | fap_icon_assets="assets", 15 | ) 16 | -------------------------------------------------------------------------------- /pomodoro_timer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | typedef struct PomodoroTimer PomodoroTimer; 9 | 10 | struct PomodoroTimer { 11 | View* view; 12 | }; 13 | 14 | typedef struct PomodoroTimerModel PomodoroTimerModel; 15 | 16 | struct PomodoroTimerModel { 17 | bool ok_pressed; 18 | bool reset_pressed; 19 | bool back_pressed; 20 | bool connected; 21 | bool timer_running; 22 | bool rest_running; 23 | bool sound_playing; 24 | uint32_t timer_start_timestamp; 25 | uint32_t timer_stopped_seconds; 26 | uint32_t time_passed; 27 | uint32_t rest_start_timestamp; 28 | int counter; 29 | }; 30 | 31 | void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event); 32 | 33 | void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest); 34 | -------------------------------------------------------------------------------- /pomodoro.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include "pomodoro_timer.h" 12 | #include "views/pomodoro_10.h" 13 | #include "views/pomodoro_25.h" 14 | #include "views/pomodoro_50.h" 15 | #include "views/pomodoro_52.h" 16 | 17 | typedef struct { 18 | Gui* gui; 19 | NotificationApp* notifications; 20 | ViewDispatcher* view_dispatcher; 21 | Submenu* submenu; 22 | DialogEx* dialog; 23 | PomodoroTimer* pomodoro_10; 24 | PomodoroTimer* pomodoro_25; 25 | PomodoroTimer* pomodoro_50; 26 | PomodoroTimer* pomodoro_52; 27 | uint32_t view_id; 28 | } Pomodoro; 29 | 30 | typedef enum { 31 | PomodoroViewSubmenu, 32 | PomodoroView10, 33 | PomodoroView25, 34 | PomodoroView50, 35 | PomodoroView52, 36 | PomodoroViewExitConfirm, 37 | } PomodoroView; 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flipperzero_pomodoro 2 | 3 | The Pomodoro Technique is a time management method developed by Francesco Cirillo in the late 1980s.[1] It uses a kitchen timer to break work into intervals, typically 25 minutes in length, separated by short breaks. Each interval is known as a pomodoro, from the Italian word for tomato, after the tomato-shaped kitchen timer Cirillo used as a university student. 4 | 5 | Flipper Zero is a portable Tamagotchi-like multi-functional device developed for interaction with access control systems. The device is able to read, copy, and emulate radio-frequency (RFID) tags, radio remotes, and digital access keys. 6 | 7 | ## Pomodoro timer application for Flipper Zero 8 | 9 | Now available in Flipper Apps: https://lab.flipper.net/apps/pomodoro_timer 10 | 11 | Three timers available: 12 | 13 | - classic 25 min work, 5 min rest 14 | - long 50 min work, 10 min rest 15 | - sprint 10 min work, 2 min rest 16 | 17 | With tomato counter 18 | 19 | Plays sound alerts 20 | 21 | Has built-in clocks 22 | 23 | Screenshots: 24 | 25 | ![](./misc/1.png) 26 | 27 | ![](./misc/2.png) 28 | 29 | ![](./misc/3.png) 30 | 31 | ![](./misc/4.png) 32 | 33 | ![](./misc/5.png) 34 | 35 | 36 | Compatible with firmware v. 0.80.1 from 28 Mar. 2023 37 | -------------------------------------------------------------------------------- /views/pomodoro_10.c: -------------------------------------------------------------------------------- 1 | #include "../pomodoro_timer.h" 2 | #include "pomodoro_10.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void pomodoro_10_draw_callback(Canvas* canvas, void* context) { 9 | int max_seconds = 60 * 10; 10 | int max_seconds_rest = 60 * 2; 11 | pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest); 12 | } 13 | 14 | static bool pomodoro_10_input_callback(InputEvent* event, void* context) { 15 | furi_assert(context); 16 | PomodoroTimer* pomodoro_10 = context; 17 | 18 | if(event->type == InputTypeLong && event->key == InputKeyBack) { 19 | return false; 20 | } else { 21 | pomodoro_timer_process(pomodoro_10, event); 22 | return true; 23 | } 24 | } 25 | 26 | PomodoroTimer* pomodoro_10_alloc() { 27 | PomodoroTimer* pomodoro_10 = malloc(sizeof(PomodoroTimer)); 28 | pomodoro_10->view = view_alloc(); 29 | view_set_context(pomodoro_10->view, pomodoro_10); 30 | view_allocate_model(pomodoro_10->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel)); 31 | view_set_draw_callback(pomodoro_10->view, pomodoro_10_draw_callback); 32 | view_set_input_callback(pomodoro_10->view, pomodoro_10_input_callback); 33 | 34 | return pomodoro_10; 35 | } 36 | 37 | void pomodoro_10_free(PomodoroTimer* pomodoro_10) { 38 | furi_assert(pomodoro_10); 39 | view_free(pomodoro_10->view); 40 | free(pomodoro_10); 41 | } 42 | 43 | View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10) { 44 | furi_assert(pomodoro_10); 45 | return pomodoro_10->view; 46 | } 47 | -------------------------------------------------------------------------------- /views/pomodoro_25.c: -------------------------------------------------------------------------------- 1 | #include "../pomodoro_timer.h" 2 | #include "pomodoro_25.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void pomodoro_25_draw_callback(Canvas* canvas, void* context) { 9 | int max_seconds = 60 * 25; 10 | int max_seconds_rest = 60 * 5; 11 | pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest); 12 | } 13 | 14 | static bool pomodoro_25_input_callback(InputEvent* event, void* context) { 15 | furi_assert(context); 16 | PomodoroTimer* pomodoro_25 = context; 17 | 18 | if(event->type == InputTypeLong && event->key == InputKeyBack) { 19 | return false; 20 | } else { 21 | pomodoro_timer_process(pomodoro_25, event); 22 | return true; 23 | } 24 | } 25 | 26 | PomodoroTimer* pomodoro_25_alloc() { 27 | PomodoroTimer* pomodoro_25 = malloc(sizeof(PomodoroTimer)); 28 | pomodoro_25->view = view_alloc(); 29 | view_set_context(pomodoro_25->view, pomodoro_25); 30 | view_allocate_model(pomodoro_25->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel)); 31 | view_set_draw_callback(pomodoro_25->view, pomodoro_25_draw_callback); 32 | view_set_input_callback(pomodoro_25->view, pomodoro_25_input_callback); 33 | 34 | return pomodoro_25; 35 | } 36 | 37 | void pomodoro_25_free(PomodoroTimer* pomodoro_25) { 38 | furi_assert(pomodoro_25); 39 | view_free(pomodoro_25->view); 40 | free(pomodoro_25); 41 | } 42 | 43 | View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25) { 44 | furi_assert(pomodoro_25); 45 | return pomodoro_25->view; 46 | } 47 | -------------------------------------------------------------------------------- /views/pomodoro_50.c: -------------------------------------------------------------------------------- 1 | #include "../pomodoro_timer.h" 2 | #include "pomodoro_50.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void pomodoro_50_draw_callback(Canvas* canvas, void* context) { 9 | int max_seconds = 60 * 50; 10 | int max_seconds_rest = 60 * 10; 11 | pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest); 12 | } 13 | 14 | static bool pomodoro_50_input_callback(InputEvent* event, void* context) { 15 | furi_assert(context); 16 | PomodoroTimer* pomodoro_50 = context; 17 | 18 | if(event->type == InputTypeLong && event->key == InputKeyBack) { 19 | return false; 20 | } else { 21 | pomodoro_timer_process(pomodoro_50, event); 22 | return true; 23 | } 24 | } 25 | 26 | PomodoroTimer* pomodoro_50_alloc() { 27 | PomodoroTimer* pomodoro_50 = malloc(sizeof(PomodoroTimer)); 28 | pomodoro_50->view = view_alloc(); 29 | view_set_context(pomodoro_50->view, pomodoro_50); 30 | view_allocate_model(pomodoro_50->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel)); 31 | view_set_draw_callback(pomodoro_50->view, pomodoro_50_draw_callback); 32 | view_set_input_callback(pomodoro_50->view, pomodoro_50_input_callback); 33 | 34 | return pomodoro_50; 35 | } 36 | 37 | void pomodoro_50_free(PomodoroTimer* pomodoro_50) { 38 | furi_assert(pomodoro_50); 39 | view_free(pomodoro_50->view); 40 | free(pomodoro_50); 41 | } 42 | 43 | View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50) { 44 | furi_assert(pomodoro_50); 45 | return pomodoro_50->view; 46 | } 47 | -------------------------------------------------------------------------------- /views/pomodoro_52.c: -------------------------------------------------------------------------------- 1 | #include "../pomodoro_timer.h" 2 | #include "pomodoro_52.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static void pomodoro_52_draw_callback(Canvas* canvas, void* context) { 9 | int max_seconds = 60 * 52; 10 | int max_seconds_rest = 60 * 17; 11 | pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest); 12 | } 13 | 14 | static bool pomodoro_52_input_callback(InputEvent* event, void* context) { 15 | furi_assert(context); 16 | PomodoroTimer* pomodoro_52 = context; 17 | 18 | if(event->type == InputTypeLong && event->key == InputKeyBack) { 19 | return false; 20 | } else { 21 | pomodoro_timer_process(pomodoro_52, event); 22 | return true; 23 | } 24 | } 25 | 26 | PomodoroTimer* pomodoro_52_alloc() { 27 | PomodoroTimer* pomodoro_52 = malloc(sizeof(PomodoroTimer)); 28 | pomodoro_52->view = view_alloc(); 29 | view_set_context(pomodoro_52->view, pomodoro_52); 30 | view_allocate_model(pomodoro_52->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel)); 31 | view_set_draw_callback(pomodoro_52->view, pomodoro_52_draw_callback); 32 | view_set_input_callback(pomodoro_52->view, pomodoro_52_input_callback); 33 | 34 | return pomodoro_52; 35 | } 36 | 37 | void pomodoro_52_free(PomodoroTimer* pomodoro_52) { 38 | furi_assert(pomodoro_52); 39 | view_free(pomodoro_52->view); 40 | free(pomodoro_52); 41 | } 42 | 43 | View* pomodoro_52_get_view(PomodoroTimer* pomodoro_52) { 44 | furi_assert(pomodoro_52); 45 | return pomodoro_52->view; 46 | } 47 | -------------------------------------------------------------------------------- /pomodoro_timer_icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /pomodoro.c: -------------------------------------------------------------------------------- 1 | #include "pomodoro.h" 2 | #include 3 | 4 | #define TAG "PomodoroApp" 5 | 6 | enum PomodoroDebugSubmenuIndex { 7 | PomodoroSubmenuIndex10, 8 | PomodoroSubmenuIndex25, 9 | PomodoroSubmenuIndex50, 10 | PomodoroSubmenuIndex52, 11 | }; 12 | 13 | void pomodoro_submenu_callback(void* context, uint32_t index) { 14 | furi_assert(context); 15 | Pomodoro* app = context; 16 | if(index == PomodoroSubmenuIndex10) { 17 | app->view_id = PomodoroView10; 18 | view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView10); 19 | } 20 | if(index == PomodoroSubmenuIndex25) { 21 | app->view_id = PomodoroView25; 22 | view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView25); 23 | } 24 | if(index == PomodoroSubmenuIndex50) { 25 | app->view_id = PomodoroView50; 26 | view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView50); 27 | } 28 | if(index == PomodoroSubmenuIndex52) { 29 | app->view_id = PomodoroView52; 30 | view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView52); 31 | } 32 | } 33 | 34 | void pomodoro_dialog_callback(DialogExResult result, void* context) { 35 | furi_assert(context); 36 | Pomodoro* app = context; 37 | if(result == DialogExResultLeft) { 38 | view_dispatcher_stop(app->view_dispatcher); 39 | } else if(result == DialogExResultRight) { 40 | view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view 41 | } else if(result == DialogExResultCenter) { 42 | view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroViewSubmenu); 43 | } 44 | } 45 | 46 | uint32_t pomodoro_exit_confirm_view(void* context) { 47 | UNUSED(context); 48 | return PomodoroViewExitConfirm; 49 | } 50 | 51 | uint32_t pomodoro_exit(void* context) { 52 | UNUSED(context); 53 | return VIEW_NONE; 54 | } 55 | 56 | Pomodoro* pomodoro_app_alloc() { 57 | Pomodoro* app = malloc(sizeof(Pomodoro)); 58 | 59 | // Gui 60 | app->gui = furi_record_open(RECORD_GUI); 61 | 62 | // Notifications 63 | app->notifications = furi_record_open(RECORD_NOTIFICATION); 64 | 65 | // View dispatcher 66 | app->view_dispatcher = view_dispatcher_alloc(); 67 | view_dispatcher_enable_queue(app->view_dispatcher); 68 | view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen); 69 | 70 | // Submenu view 71 | app->submenu = submenu_alloc(); 72 | submenu_add_item( 73 | app->submenu, 74 | "Classic: 25 work 5 rest", 75 | PomodoroSubmenuIndex25, 76 | pomodoro_submenu_callback, 77 | app); 78 | submenu_add_item( 79 | app->submenu, 80 | "Long: 50 work 10 rest", 81 | PomodoroSubmenuIndex50, 82 | pomodoro_submenu_callback, 83 | app); 84 | submenu_add_item( 85 | app->submenu, 86 | "Sprint: 10 work 2 rest", 87 | PomodoroSubmenuIndex10, 88 | pomodoro_submenu_callback, 89 | app); 90 | submenu_add_item( 91 | app->submenu, 92 | "5217: 52 work 17 rest", 93 | PomodoroSubmenuIndex52, 94 | pomodoro_submenu_callback, 95 | app); 96 | view_set_previous_callback(submenu_get_view(app->submenu), pomodoro_exit); 97 | view_dispatcher_add_view( 98 | app->view_dispatcher, PomodoroViewSubmenu, submenu_get_view(app->submenu)); 99 | 100 | // Dialog view 101 | app->dialog = dialog_ex_alloc(); 102 | dialog_ex_set_result_callback(app->dialog, pomodoro_dialog_callback); 103 | dialog_ex_set_context(app->dialog, app); 104 | dialog_ex_set_left_button_text(app->dialog, "Exit"); 105 | dialog_ex_set_right_button_text(app->dialog, "Stay"); 106 | dialog_ex_set_center_button_text(app->dialog, "Menu"); 107 | dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop); 108 | view_dispatcher_add_view( 109 | app->view_dispatcher, PomodoroViewExitConfirm, dialog_ex_get_view(app->dialog)); 110 | 111 | // 25 minutes view 112 | app->pomodoro_25 = pomodoro_25_alloc(); 113 | view_set_previous_callback(pomodoro_25_get_view(app->pomodoro_25), pomodoro_exit_confirm_view); 114 | view_dispatcher_add_view( 115 | app->view_dispatcher, PomodoroView25, pomodoro_25_get_view(app->pomodoro_25)); 116 | 117 | // 50 minutes view 118 | app->pomodoro_50 = pomodoro_50_alloc(); 119 | view_set_previous_callback(pomodoro_50_get_view(app->pomodoro_50), pomodoro_exit_confirm_view); 120 | view_dispatcher_add_view( 121 | app->view_dispatcher, PomodoroView50, pomodoro_50_get_view(app->pomodoro_50)); 122 | 123 | // 10 minutes view 124 | app->pomodoro_10 = pomodoro_10_alloc(); 125 | view_set_previous_callback(pomodoro_10_get_view(app->pomodoro_10), pomodoro_exit_confirm_view); 126 | view_dispatcher_add_view( 127 | app->view_dispatcher, PomodoroView10, pomodoro_10_get_view(app->pomodoro_10)); 128 | 129 | // 52 minutes view 130 | app->pomodoro_52 = pomodoro_52_alloc(); 131 | view_set_previous_callback(pomodoro_52_get_view(app->pomodoro_52), pomodoro_exit_confirm_view); 132 | view_dispatcher_add_view( 133 | app->view_dispatcher, PomodoroView52, pomodoro_52_get_view(app->pomodoro_52)); 134 | 135 | // TODO switch to menu after Media is done 136 | app->view_id = PomodoroViewSubmenu; 137 | view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); 138 | 139 | return app; 140 | } 141 | 142 | void pomodoro_app_free(Pomodoro* app) { 143 | furi_assert(app); 144 | 145 | // Reset notification 146 | notification_internal_message(app->notifications, &sequence_reset_blue); 147 | 148 | // Free views 149 | view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewSubmenu); 150 | submenu_free(app->submenu); 151 | view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewExitConfirm); 152 | dialog_ex_free(app->dialog); 153 | view_dispatcher_remove_view(app->view_dispatcher, PomodoroView25); 154 | pomodoro_25_free(app->pomodoro_25); 155 | view_dispatcher_remove_view(app->view_dispatcher, PomodoroView50); 156 | pomodoro_50_free(app->pomodoro_50); 157 | view_dispatcher_remove_view(app->view_dispatcher, PomodoroView10); 158 | pomodoro_10_free(app->pomodoro_10); 159 | view_dispatcher_remove_view(app->view_dispatcher, PomodoroView52); 160 | pomodoro_52_free(app->pomodoro_52); 161 | view_dispatcher_free(app->view_dispatcher); 162 | 163 | // Close records 164 | furi_record_close(RECORD_GUI); 165 | app->gui = NULL; 166 | furi_record_close(RECORD_NOTIFICATION); 167 | app->notifications = NULL; 168 | 169 | // Free rest 170 | free(app); 171 | } 172 | 173 | int32_t pomodoro_app(void* p) { 174 | UNUSED(p); 175 | // Switch profile to Hid 176 | Pomodoro* app = pomodoro_app_alloc(); 177 | 178 | view_dispatcher_run(app->view_dispatcher); 179 | 180 | pomodoro_app_free(app); 181 | 182 | return 0; 183 | } 184 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | -------------------------------------------------------------------------------- /pomodoro_timer.c: -------------------------------------------------------------------------------- 1 | #include "pomodoro_timer.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "pomodoro_icons.h" 7 | 8 | const NotificationSequence sequence_finish = { 9 | &message_display_backlight_on, 10 | &message_green_255, 11 | &message_vibro_on, 12 | &message_note_c5, 13 | &message_delay_100, 14 | &message_vibro_off, 15 | &message_vibro_on, 16 | &message_note_e5, 17 | &message_delay_100, 18 | &message_vibro_off, 19 | &message_vibro_on, 20 | &message_note_g5, 21 | &message_delay_100, 22 | &message_vibro_off, 23 | &message_vibro_on, 24 | &message_note_b5, 25 | &message_delay_250, 26 | &message_vibro_off, 27 | &message_vibro_on, 28 | &message_note_c6, 29 | &message_delay_250, 30 | &message_vibro_off, 31 | &message_sound_off, 32 | NULL, 33 | }; 34 | 35 | const NotificationSequence sequence_rest = { 36 | &message_display_backlight_on, 37 | &message_red_255, 38 | &message_vibro_on, 39 | &message_note_c6, 40 | &message_delay_100, 41 | &message_vibro_off, 42 | &message_vibro_on, 43 | &message_note_b5, 44 | &message_delay_100, 45 | &message_vibro_off, 46 | &message_vibro_on, 47 | &message_note_g5, 48 | &message_delay_100, 49 | &message_vibro_off, 50 | &message_vibro_on, 51 | &message_note_e5, 52 | &message_delay_100, 53 | &message_vibro_off, 54 | &message_vibro_on, 55 | &message_note_c5, 56 | &message_delay_250, 57 | &message_vibro_off, 58 | &message_sound_off, 59 | NULL, 60 | }; 61 | 62 | void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event) { 63 | with_view_model( 64 | pomodoro_timer->view, 65 | PomodoroTimerModel * model, 66 | { 67 | if(event->type == InputTypePress) { 68 | if(event->key == InputKeyOk) { 69 | model->ok_pressed = true; 70 | } else if(event->key == InputKeyLeft) { 71 | model->reset_pressed = true; 72 | } else if(event->key == InputKeyBack) { 73 | model->back_pressed = true; 74 | } 75 | } else if(event->type == InputTypeRelease) { 76 | if(event->key == InputKeyOk) { 77 | model->ok_pressed = false; 78 | 79 | // START/STOP TIMER 80 | FuriHalRtcDateTime curr_dt; 81 | furi_hal_rtc_get_datetime(&curr_dt); 82 | uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt); 83 | 84 | // STARTED -> PAUSED 85 | if(model->timer_running) { 86 | // Update stopped seconds 87 | model->timer_stopped_seconds = 88 | current_timestamp - model->timer_start_timestamp; 89 | } else if(!model->time_passed) { 90 | // INITIAL -> STARTED 91 | model->timer_start_timestamp = current_timestamp; 92 | model->rest_running = false; 93 | } else { 94 | // PAUSED -> STARTED 95 | model->timer_start_timestamp = 96 | current_timestamp - model->timer_stopped_seconds; 97 | } 98 | model->timer_running = !model->timer_running; 99 | } else if(event->key == InputKeyLeft) { 100 | if(!model->timer_running) { 101 | furi_record_close(RECORD_NOTIFICATION); 102 | model->timer_stopped_seconds = 0; 103 | model->timer_start_timestamp = 0; 104 | model->time_passed = 0; 105 | model->timer_running = false; 106 | } 107 | model->reset_pressed = false; 108 | } else if(event->key == InputKeyBack) { 109 | model->back_pressed = false; 110 | } 111 | } 112 | }, 113 | true); 114 | } 115 | 116 | void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest) { 117 | furi_assert(context); 118 | PomodoroTimerModel* model = context; 119 | FuriHalRtcDateTime curr_dt; 120 | furi_hal_rtc_get_datetime(&curr_dt); 121 | uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt); 122 | 123 | // Header 124 | canvas_set_font(canvas, FontPrimary); 125 | elements_multiline_text_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Pomodoro"); 126 | 127 | canvas_draw_icon(canvas, 68, 1, &I_Pin_back_arrow_10x8); 128 | canvas_set_font(canvas, FontSecondary); 129 | elements_multiline_text_aligned(canvas, 127, 1, AlignRight, AlignTop, "Hold to exit"); 130 | 131 | // Start/Pause/Continue 132 | int txt_main_y = 34; 133 | canvas_draw_icon(canvas, 63, 23, &I_Space_65x18); // button 134 | if(model->ok_pressed) { 135 | elements_slightly_rounded_box(canvas, 66, 25, 60, 13); 136 | canvas_set_color(canvas, ColorWhite); 137 | } 138 | if(model->timer_running) { 139 | model->time_passed = current_timestamp - model->timer_start_timestamp; 140 | elements_multiline_text_aligned(canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Pause"); 141 | canvas_draw_box(canvas, 71, 27, 2, 8); 142 | canvas_draw_box(canvas, 75, 27, 2, 8); 143 | } else { 144 | if(model->time_passed) { 145 | elements_multiline_text_aligned( 146 | canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Continue"); 147 | } else { 148 | elements_multiline_text_aligned( 149 | canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Start"); 150 | } 151 | canvas_draw_icon(canvas, 70, 26, &I_Ok_btn_9x9); // OK icon 152 | } 153 | canvas_set_color(canvas, ColorBlack); 154 | 155 | // Reset 156 | if(!model->timer_running && model->time_passed) { 157 | canvas_draw_icon(canvas, 63, 46, &I_Space_65x18); 158 | if(model->reset_pressed) { 159 | elements_slightly_rounded_box(canvas, 66, 48, 60, 13); 160 | canvas_set_color(canvas, ColorWhite); 161 | } 162 | canvas_draw_icon(canvas, 72, 50, &I_ButtonLeft_4x7); 163 | elements_multiline_text_aligned(canvas, 83, 57, AlignLeft, AlignBottom, "Reset"); 164 | canvas_set_color(canvas, ColorBlack); 165 | } 166 | 167 | char buffer[64]; 168 | 169 | // Time to work 170 | int total_time_left = (max_seconds - (uint32_t)model->time_passed); 171 | int minutes_left = total_time_left / 60; 172 | int seconds_left = total_time_left % 60; 173 | canvas_set_font(canvas, FontBigNumbers); 174 | 175 | // Play sound 176 | if(total_time_left == 0 && !model->sound_playing) { 177 | model->sound_playing = true; 178 | notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_finish); 179 | } 180 | if(total_time_left < 0) { 181 | model->timer_running = false; 182 | model->time_passed = 0; 183 | model->sound_playing = false; 184 | 185 | model->rest_running = true; 186 | model->rest_start_timestamp = current_timestamp; 187 | seconds_left = 0; 188 | model->counter += 1; 189 | } 190 | if(!model->rest_running) { 191 | snprintf(buffer, sizeof(buffer), "%02d:%02d", minutes_left, seconds_left); 192 | canvas_draw_str(canvas, 0, 39, buffer); 193 | } 194 | if(model->timer_running) { 195 | canvas_set_font(canvas, FontPrimary); 196 | elements_multiline_text_aligned(canvas, 0, 50, AlignLeft, AlignTop, "Time to work"); 197 | } 198 | 199 | // Time to rest 200 | if(model->rest_running && !model->timer_running) { 201 | canvas_set_font(canvas, FontBigNumbers); 202 | int rest_passed = current_timestamp - model->rest_start_timestamp; 203 | int rest_total_time_left = (max_seconds_rest - rest_passed); 204 | int rest_minutes_left = rest_total_time_left / 60; 205 | int rest_seconds_left = rest_total_time_left % 60; 206 | 207 | // Play sound 208 | if(rest_total_time_left == 0 && !model->sound_playing) { 209 | model->sound_playing = true; 210 | notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_rest); 211 | } 212 | if(rest_total_time_left < 0) { 213 | rest_seconds_left = 0; 214 | model->rest_running = false; 215 | model->sound_playing = false; 216 | } 217 | snprintf(buffer, sizeof(buffer), "%02d:%02d", rest_minutes_left, rest_seconds_left); 218 | canvas_draw_str(canvas, 0, 60, buffer); 219 | 220 | canvas_set_font(canvas, FontPrimary); 221 | elements_multiline_text_aligned(canvas, 0, 27, AlignLeft, AlignTop, "Have a rest"); 222 | } 223 | 224 | // Clocks 225 | canvas_set_font(canvas, FontSecondary); 226 | snprintf( 227 | buffer, 228 | sizeof(buffer), 229 | "%02ld:%02ld:%02ld", 230 | ((uint32_t)current_timestamp % (60 * 60 * 24)) / (60 * 60), 231 | ((uint32_t)current_timestamp % (60 * 60)) / 60, 232 | (uint32_t)current_timestamp % 60); 233 | canvas_draw_str(canvas, 0, 20, buffer); 234 | 235 | // Tomato counter 236 | if(model->counter > 5) { 237 | model->counter = 1; 238 | } 239 | for(int i = 0; i < model->counter; ++i) { 240 | canvas_draw_disc(canvas, 122 - i * 10, 15, 4); 241 | } 242 | } 243 | --------------------------------------------------------------------------------