mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-05-20 04:54:45 -07:00
new app
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
- Known Issues: `Chess`
|
- Known Issues: `Chess`
|
||||||
- Last Synced/Checked [Unleashed/xMasterX](https://github.com/DarkFlippers/unleashed-firmware), changes in [changelog](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/CHANGELOG.md): `2022-10-14 20:25 GMT`
|
- Last Synced/Checked [Unleashed/xMasterX](https://github.com/DarkFlippers/unleashed-firmware), changes in [changelog](https://github.com/RogueMaster/flipperzero-firmware-wPlugins/blob/420/CHANGELOG.md): `2022-10-14 20:25 GMT`
|
||||||
- Last Synced/Checked [OFW](https://github.com/flipperdevices/flipperzero-firmware), changes in [commits](https://github.com/flipperdevices/flipperzero-firmware/commits/dev): `2022-10-14 20:25 GMT`
|
- Last Synced/Checked [OFW](https://github.com/flipperdevices/flipperzero-firmware), changes in [commits](https://github.com/flipperdevices/flipperzero-firmware/commits/dev): `2022-10-14 20:25 GMT`
|
||||||
|
- Added: [Pomodoro Timer (By sbrin)](https://github.com/sbrin/flipperzero_pomodoro)
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary><B>TO DO / REMOVED</b></summary><br/>
|
<summary><B>TO DO / REMOVED</b></summary><br/>
|
||||||
|
|||||||
121
applications/plugins/pomodoro/LICENSE
Normal file
121
applications/plugins/pomodoro/LICENSE
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
Creative Commons Legal Code
|
||||||
|
|
||||||
|
CC0 1.0 Universal
|
||||||
|
|
||||||
|
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||||
|
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||||
|
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||||
|
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||||
|
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||||
|
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||||
|
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||||
|
HEREUNDER.
|
||||||
|
|
||||||
|
Statement of Purpose
|
||||||
|
|
||||||
|
The laws of most jurisdictions throughout the world automatically confer
|
||||||
|
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||||
|
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||||
|
authorship and/or a database (each, a "Work").
|
||||||
|
|
||||||
|
Certain owners wish to permanently relinquish those rights to a Work for
|
||||||
|
the purpose of contributing to a commons of creative, cultural and
|
||||||
|
scientific works ("Commons") that the public can reliably and without fear
|
||||||
|
of later claims of infringement build upon, modify, incorporate in other
|
||||||
|
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||||
|
and for any purposes, including without limitation commercial purposes.
|
||||||
|
These owners may contribute to the Commons to promote the ideal of a free
|
||||||
|
culture and the further production of creative, cultural and scientific
|
||||||
|
works, or to gain reputation or greater distribution for their Work in
|
||||||
|
part through the use and efforts of others.
|
||||||
|
|
||||||
|
For these and/or other purposes and motivations, and without any
|
||||||
|
expectation of additional consideration or compensation, the person
|
||||||
|
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||||
|
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||||
|
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||||
|
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||||
|
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||||
|
|
||||||
|
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||||
|
protected by copyright and related or neighboring rights ("Copyright and
|
||||||
|
Related Rights"). Copyright and Related Rights include, but are not
|
||||||
|
limited to, the following:
|
||||||
|
|
||||||
|
i. the right to reproduce, adapt, distribute, perform, display,
|
||||||
|
communicate, and translate a Work;
|
||||||
|
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||||
|
iii. publicity and privacy rights pertaining to a person's image or
|
||||||
|
likeness depicted in a Work;
|
||||||
|
iv. rights protecting against unfair competition in regards to a Work,
|
||||||
|
subject to the limitations in paragraph 4(a), below;
|
||||||
|
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||||
|
in a Work;
|
||||||
|
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||||
|
European Parliament and of the Council of 11 March 1996 on the legal
|
||||||
|
protection of databases, and under any national implementation
|
||||||
|
thereof, including any amended or successor version of such
|
||||||
|
directive); and
|
||||||
|
vii. other similar, equivalent or corresponding rights throughout the
|
||||||
|
world based on applicable law or treaty, and any national
|
||||||
|
implementations thereof.
|
||||||
|
|
||||||
|
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||||
|
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||||
|
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||||
|
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||||
|
of action, whether now known or unknown (including existing as well as
|
||||||
|
future claims and causes of action), in the Work (i) in all territories
|
||||||
|
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||||
|
treaty (including future time extensions), (iii) in any current or future
|
||||||
|
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||||
|
including without limitation commercial, advertising or promotional
|
||||||
|
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||||
|
member of the public at large and to the detriment of Affirmer's heirs and
|
||||||
|
successors, fully intending that such Waiver shall not be subject to
|
||||||
|
revocation, rescission, cancellation, termination, or any other legal or
|
||||||
|
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||||
|
as contemplated by Affirmer's express Statement of Purpose.
|
||||||
|
|
||||||
|
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||||
|
be judged legally invalid or ineffective under applicable law, then the
|
||||||
|
Waiver shall be preserved to the maximum extent permitted taking into
|
||||||
|
account Affirmer's express Statement of Purpose. In addition, to the
|
||||||
|
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||||
|
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||||
|
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||||
|
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||||
|
maximum duration provided by applicable law or treaty (including future
|
||||||
|
time extensions), (iii) in any current or future medium and for any number
|
||||||
|
of copies, and (iv) for any purpose whatsoever, including without
|
||||||
|
limitation commercial, advertising or promotional purposes (the
|
||||||
|
"License"). The License shall be deemed effective as of the date CC0 was
|
||||||
|
applied by Affirmer to the Work. Should any part of the License for any
|
||||||
|
reason be judged legally invalid or ineffective under applicable law, such
|
||||||
|
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||||
|
of the License, and in such case Affirmer hereby affirms that he or she
|
||||||
|
will not (i) exercise any of his or her remaining Copyright and Related
|
||||||
|
Rights in the Work or (ii) assert any associated claims and causes of
|
||||||
|
action with respect to the Work, in either case contrary to Affirmer's
|
||||||
|
express Statement of Purpose.
|
||||||
|
|
||||||
|
4. Limitations and Disclaimers.
|
||||||
|
|
||||||
|
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||||
|
surrendered, licensed or otherwise affected by this document.
|
||||||
|
b. Affirmer offers the Work as-is and makes no representations or
|
||||||
|
warranties of any kind concerning the Work, express, implied,
|
||||||
|
statutory or otherwise, including without limitation warranties of
|
||||||
|
title, merchantability, fitness for a particular purpose, non
|
||||||
|
infringement, or the absence of latent or other defects, accuracy, or
|
||||||
|
the present or absence of errors, whether or not discoverable, all to
|
||||||
|
the greatest extent permissible under applicable law.
|
||||||
|
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||||
|
that may apply to the Work or any use thereof, including without
|
||||||
|
limitation any person's Copyright and Related Rights in the Work.
|
||||||
|
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||||
|
consents, permissions or other rights required for any use of the
|
||||||
|
Work.
|
||||||
|
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||||
|
party to this document and has no duty or obligation with respect to
|
||||||
|
this CC0 or use of the Work.
|
||||||
34
applications/plugins/pomodoro/README.md
Normal file
34
applications/plugins/pomodoro/README.md
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# flipperzero_pomodoro
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Pomodoro timer application for Flipper Zero
|
||||||
|
|
||||||
|
Three timers available:
|
||||||
|
|
||||||
|
- classic 25 min work, 5 min rest
|
||||||
|
- long 50 min work, 10 min rest
|
||||||
|
- sprint 10 min work, 2 min rest
|
||||||
|
|
||||||
|
With tomato counter
|
||||||
|
|
||||||
|
Plays sound alerts
|
||||||
|
|
||||||
|
Has built-in clocks
|
||||||
|
|
||||||
|
Screenshots:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
Compatible with firmware v. F81999EA from 14 Oct. 2022
|
||||||
14
applications/plugins/pomodoro/application.fam
Normal file
14
applications/plugins/pomodoro/application.fam
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
App(
|
||||||
|
appid="Pomodoro_Timer",
|
||||||
|
name="Pomodoro Timer",
|
||||||
|
apptype=FlipperAppType.EXTERNAL,
|
||||||
|
entry_point="pomodoro_app",
|
||||||
|
stack_size=1 * 1024,
|
||||||
|
cdefines=["APP_POMODORO"],
|
||||||
|
requires=[
|
||||||
|
"gui",
|
||||||
|
],
|
||||||
|
order=10,
|
||||||
|
fap_icon="pomodoro_timer.png",
|
||||||
|
fap_category="Tools",
|
||||||
|
)
|
||||||
BIN
applications/plugins/pomodoro/misc/1.png
Normal file
BIN
applications/plugins/pomodoro/misc/1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
applications/plugins/pomodoro/misc/2.png
Normal file
BIN
applications/plugins/pomodoro/misc/2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
applications/plugins/pomodoro/misc/3.png
Normal file
BIN
applications/plugins/pomodoro/misc/3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
applications/plugins/pomodoro/misc/4.png
Normal file
BIN
applications/plugins/pomodoro/misc/4.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 13 KiB |
BIN
applications/plugins/pomodoro/misc/5.png
Normal file
BIN
applications/plugins/pomodoro/misc/5.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
164
applications/plugins/pomodoro/pomodoro.c
Normal file
164
applications/plugins/pomodoro/pomodoro.c
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
#include "pomodoro.h"
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
#define TAG "PomodoroApp"
|
||||||
|
|
||||||
|
enum PomodoroDebugSubmenuIndex {
|
||||||
|
PomodoroSubmenuIndex10,
|
||||||
|
PomodoroSubmenuIndex25,
|
||||||
|
PomodoroSubmenuIndex50,
|
||||||
|
};
|
||||||
|
|
||||||
|
void pomodoro_submenu_callback(void* context, uint32_t index) {
|
||||||
|
furi_assert(context);
|
||||||
|
Pomodoro* app = context;
|
||||||
|
if(index == PomodoroSubmenuIndex10) {
|
||||||
|
app->view_id = PomodoroView10;
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView10);
|
||||||
|
}
|
||||||
|
if(index == PomodoroSubmenuIndex25) {
|
||||||
|
app->view_id = PomodoroView25;
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView25);
|
||||||
|
}
|
||||||
|
if(index == PomodoroSubmenuIndex50) {
|
||||||
|
app->view_id = PomodoroView50;
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroView50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pomodoro_dialog_callback(DialogExResult result, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
Pomodoro* app = context;
|
||||||
|
if(result == DialogExResultLeft) {
|
||||||
|
view_dispatcher_stop(app->view_dispatcher);
|
||||||
|
} else if(result == DialogExResultRight) {
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id); // Show last view
|
||||||
|
} else if(result == DialogExResultCenter) {
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, PomodoroViewSubmenu);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t pomodoro_exit_confirm_view(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
return PomodoroViewExitConfirm;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t pomodoro_exit(void* context) {
|
||||||
|
UNUSED(context);
|
||||||
|
return VIEW_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
Pomodoro* pomodoro_app_alloc() {
|
||||||
|
Pomodoro* app = malloc(sizeof(Pomodoro));
|
||||||
|
|
||||||
|
// Gui
|
||||||
|
app->gui = furi_record_open(RECORD_GUI);
|
||||||
|
|
||||||
|
// Notifications
|
||||||
|
app->notifications = furi_record_open(RECORD_NOTIFICATION);
|
||||||
|
|
||||||
|
// View dispatcher
|
||||||
|
app->view_dispatcher = view_dispatcher_alloc();
|
||||||
|
view_dispatcher_enable_queue(app->view_dispatcher);
|
||||||
|
view_dispatcher_attach_to_gui(app->view_dispatcher, app->gui, ViewDispatcherTypeFullscreen);
|
||||||
|
|
||||||
|
// Submenu view
|
||||||
|
app->submenu = submenu_alloc();
|
||||||
|
submenu_add_item(
|
||||||
|
app->submenu,
|
||||||
|
"Classic: 25 work 5 rest",
|
||||||
|
PomodoroSubmenuIndex25,
|
||||||
|
pomodoro_submenu_callback,
|
||||||
|
app);
|
||||||
|
submenu_add_item(
|
||||||
|
app->submenu,
|
||||||
|
"Long: 50 work 10 rest",
|
||||||
|
PomodoroSubmenuIndex50,
|
||||||
|
pomodoro_submenu_callback,
|
||||||
|
app);
|
||||||
|
submenu_add_item(
|
||||||
|
app->submenu,
|
||||||
|
"Sprint: 10 work 2 rest",
|
||||||
|
PomodoroSubmenuIndex10,
|
||||||
|
pomodoro_submenu_callback,
|
||||||
|
app);
|
||||||
|
view_set_previous_callback(submenu_get_view(app->submenu), pomodoro_exit);
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, PomodoroViewSubmenu, submenu_get_view(app->submenu));
|
||||||
|
|
||||||
|
// Dialog view
|
||||||
|
app->dialog = dialog_ex_alloc();
|
||||||
|
dialog_ex_set_result_callback(app->dialog, pomodoro_dialog_callback);
|
||||||
|
dialog_ex_set_context(app->dialog, app);
|
||||||
|
dialog_ex_set_left_button_text(app->dialog, "Exit");
|
||||||
|
dialog_ex_set_right_button_text(app->dialog, "Stay");
|
||||||
|
dialog_ex_set_center_button_text(app->dialog, "Menu");
|
||||||
|
dialog_ex_set_header(app->dialog, "Close Current App?", 16, 12, AlignLeft, AlignTop);
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, PomodoroViewExitConfirm, dialog_ex_get_view(app->dialog));
|
||||||
|
|
||||||
|
// 25 minutes view
|
||||||
|
app->pomodoro_25 = pomodoro_25_alloc();
|
||||||
|
view_set_previous_callback(pomodoro_25_get_view(app->pomodoro_25), pomodoro_exit_confirm_view);
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, PomodoroView25, pomodoro_25_get_view(app->pomodoro_25));
|
||||||
|
|
||||||
|
// 50 minutes view
|
||||||
|
app->pomodoro_50 = pomodoro_50_alloc();
|
||||||
|
view_set_previous_callback(pomodoro_50_get_view(app->pomodoro_50), pomodoro_exit_confirm_view);
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, PomodoroView50, pomodoro_50_get_view(app->pomodoro_50));
|
||||||
|
|
||||||
|
// 10 minutes view
|
||||||
|
app->pomodoro_10 = pomodoro_10_alloc();
|
||||||
|
view_set_previous_callback(pomodoro_10_get_view(app->pomodoro_10), pomodoro_exit_confirm_view);
|
||||||
|
view_dispatcher_add_view(
|
||||||
|
app->view_dispatcher, PomodoroView10, pomodoro_10_get_view(app->pomodoro_10));
|
||||||
|
|
||||||
|
// TODO switch to menu after Media is done
|
||||||
|
app->view_id = PomodoroViewSubmenu;
|
||||||
|
view_dispatcher_switch_to_view(app->view_dispatcher, app->view_id);
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pomodoro_app_free(Pomodoro* app) {
|
||||||
|
furi_assert(app);
|
||||||
|
|
||||||
|
// Reset notification
|
||||||
|
notification_internal_message(app->notifications, &sequence_reset_blue);
|
||||||
|
|
||||||
|
// Free views
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewSubmenu);
|
||||||
|
submenu_free(app->submenu);
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, PomodoroViewExitConfirm);
|
||||||
|
dialog_ex_free(app->dialog);
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView25);
|
||||||
|
pomodoro_25_free(app->pomodoro_25);
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView50);
|
||||||
|
pomodoro_50_free(app->pomodoro_50);
|
||||||
|
view_dispatcher_remove_view(app->view_dispatcher, PomodoroView10);
|
||||||
|
pomodoro_10_free(app->pomodoro_10);
|
||||||
|
view_dispatcher_free(app->view_dispatcher);
|
||||||
|
|
||||||
|
// Close records
|
||||||
|
furi_record_close(RECORD_GUI);
|
||||||
|
app->gui = NULL;
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
|
app->notifications = NULL;
|
||||||
|
|
||||||
|
// Free rest
|
||||||
|
free(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t pomodoro_app(void* p) {
|
||||||
|
UNUSED(p);
|
||||||
|
// Switch profile to Hid
|
||||||
|
Pomodoro* app = pomodoro_app_alloc();
|
||||||
|
|
||||||
|
view_dispatcher_run(app->view_dispatcher);
|
||||||
|
|
||||||
|
pomodoro_app_free(app);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
34
applications/plugins/pomodoro/pomodoro.h
Normal file
34
applications/plugins/pomodoro/pomodoro.h
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <furi.h>
|
||||||
|
#include <gui/gui.h>
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include <gui/view_dispatcher.h>
|
||||||
|
#include <notification/notification.h>
|
||||||
|
|
||||||
|
#include <gui/modules/submenu.h>
|
||||||
|
#include <gui/modules/dialog_ex.h>
|
||||||
|
#include "pomodoro_timer.h"
|
||||||
|
#include "views/pomodoro_10.h"
|
||||||
|
#include "views/pomodoro_25.h"
|
||||||
|
#include "views/pomodoro_50.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
Gui* gui;
|
||||||
|
NotificationApp* notifications;
|
||||||
|
ViewDispatcher* view_dispatcher;
|
||||||
|
Submenu* submenu;
|
||||||
|
DialogEx* dialog;
|
||||||
|
PomodoroTimer* pomodoro_10;
|
||||||
|
PomodoroTimer* pomodoro_25;
|
||||||
|
PomodoroTimer* pomodoro_50;
|
||||||
|
uint32_t view_id;
|
||||||
|
} Pomodoro;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PomodoroViewSubmenu,
|
||||||
|
PomodoroView10,
|
||||||
|
PomodoroView25,
|
||||||
|
PomodoroView50,
|
||||||
|
PomodoroViewExitConfirm,
|
||||||
|
} PomodoroView;
|
||||||
241
applications/plugins/pomodoro/pomodoro_timer.c
Normal file
241
applications/plugins/pomodoro/pomodoro_timer.c
Normal file
@@ -0,0 +1,241 @@
|
|||||||
|
#include "pomodoro_timer.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
const NotificationSequence sequence_finish = {
|
||||||
|
&message_display_backlight_on,
|
||||||
|
&message_green_255,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_c5,
|
||||||
|
&message_delay_100,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_e5,
|
||||||
|
&message_delay_100,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_g5,
|
||||||
|
&message_delay_100,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_b5,
|
||||||
|
&message_delay_250,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_c6,
|
||||||
|
&message_delay_250,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_sound_off,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
const NotificationSequence sequence_rest = {
|
||||||
|
&message_display_backlight_on,
|
||||||
|
&message_red_255,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_c6,
|
||||||
|
&message_delay_100,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_b5,
|
||||||
|
&message_delay_100,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_g5,
|
||||||
|
&message_delay_100,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_e5,
|
||||||
|
&message_delay_100,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_vibro_on,
|
||||||
|
&message_note_c5,
|
||||||
|
&message_delay_250,
|
||||||
|
&message_vibro_off,
|
||||||
|
&message_sound_off,
|
||||||
|
NULL,
|
||||||
|
};
|
||||||
|
|
||||||
|
void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event) {
|
||||||
|
with_view_model(
|
||||||
|
pomodoro_timer->view,
|
||||||
|
PomodoroTimerModel * model,
|
||||||
|
{
|
||||||
|
if(event->type == InputTypePress) {
|
||||||
|
if(event->key == InputKeyOk) {
|
||||||
|
model->ok_pressed = true;
|
||||||
|
} else if(event->key == InputKeyLeft) {
|
||||||
|
model->reset_pressed = true;
|
||||||
|
} else if(event->key == InputKeyBack) {
|
||||||
|
model->back_pressed = true;
|
||||||
|
}
|
||||||
|
} else if(event->type == InputTypeRelease) {
|
||||||
|
if(event->key == InputKeyOk) {
|
||||||
|
model->ok_pressed = false;
|
||||||
|
|
||||||
|
// START/STOP TIMER
|
||||||
|
FuriHalRtcDateTime curr_dt;
|
||||||
|
furi_hal_rtc_get_datetime(&curr_dt);
|
||||||
|
uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||||
|
|
||||||
|
// STARTED -> PAUSED
|
||||||
|
if(model->timer_running) {
|
||||||
|
// Update stopped seconds
|
||||||
|
model->timer_stopped_seconds =
|
||||||
|
current_timestamp - model->timer_start_timestamp;
|
||||||
|
} else if(!model->time_passed) {
|
||||||
|
// INITIAL -> STARTED
|
||||||
|
model->timer_start_timestamp = current_timestamp;
|
||||||
|
model->rest_running = false;
|
||||||
|
} else {
|
||||||
|
// PAUSED -> STARTED
|
||||||
|
model->timer_start_timestamp =
|
||||||
|
current_timestamp - model->timer_stopped_seconds;
|
||||||
|
}
|
||||||
|
model->timer_running = !model->timer_running;
|
||||||
|
} else if(event->key == InputKeyLeft) {
|
||||||
|
if(!model->timer_running) {
|
||||||
|
furi_record_close(RECORD_NOTIFICATION);
|
||||||
|
model->timer_stopped_seconds = 0;
|
||||||
|
model->timer_start_timestamp = 0;
|
||||||
|
model->time_passed = 0;
|
||||||
|
model->timer_running = false;
|
||||||
|
}
|
||||||
|
model->reset_pressed = false;
|
||||||
|
} else if(event->key == InputKeyBack) {
|
||||||
|
model->back_pressed = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest) {
|
||||||
|
furi_assert(context);
|
||||||
|
PomodoroTimerModel* model = context;
|
||||||
|
FuriHalRtcDateTime curr_dt;
|
||||||
|
furi_hal_rtc_get_datetime(&curr_dt);
|
||||||
|
uint32_t current_timestamp = furi_hal_rtc_datetime_to_timestamp(&curr_dt);
|
||||||
|
|
||||||
|
// Header
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
elements_multiline_text_aligned(canvas, 0, 0, AlignLeft, AlignTop, "Pomodoro");
|
||||||
|
|
||||||
|
canvas_draw_icon(canvas, 68, 1, &I_Pin_back_arrow_10x8);
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
elements_multiline_text_aligned(canvas, 127, 1, AlignRight, AlignTop, "Hold to exit");
|
||||||
|
|
||||||
|
// Start/Pause/Continue
|
||||||
|
int txt_main_y = 34;
|
||||||
|
canvas_draw_icon(canvas, 63, 23, &I_Space_65x18); // button
|
||||||
|
if(model->ok_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, 66, 25, 60, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
if(model->timer_running) {
|
||||||
|
model->time_passed = current_timestamp - model->timer_start_timestamp;
|
||||||
|
elements_multiline_text_aligned(canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Pause");
|
||||||
|
canvas_draw_box(canvas, 71, 27, 2, 8);
|
||||||
|
canvas_draw_box(canvas, 75, 27, 2, 8);
|
||||||
|
} else {
|
||||||
|
if(model->time_passed) {
|
||||||
|
elements_multiline_text_aligned(
|
||||||
|
canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Continue");
|
||||||
|
} else {
|
||||||
|
elements_multiline_text_aligned(
|
||||||
|
canvas, 83, txt_main_y, AlignLeft, AlignBottom, "Start");
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 70, 26, &I_Ok_btn_9x9); // OK icon
|
||||||
|
}
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
|
||||||
|
// Reset
|
||||||
|
if(!model->timer_running && model->time_passed) {
|
||||||
|
canvas_draw_icon(canvas, 63, 46, &I_Space_65x18);
|
||||||
|
if(model->reset_pressed) {
|
||||||
|
elements_slightly_rounded_box(canvas, 66, 48, 60, 13);
|
||||||
|
canvas_set_color(canvas, ColorWhite);
|
||||||
|
}
|
||||||
|
canvas_draw_icon(canvas, 72, 50, &I_ButtonLeft_4x7);
|
||||||
|
elements_multiline_text_aligned(canvas, 83, 57, AlignLeft, AlignBottom, "Reset");
|
||||||
|
canvas_set_color(canvas, ColorBlack);
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[64];
|
||||||
|
|
||||||
|
// Time to work
|
||||||
|
int total_time_left = (max_seconds - (uint32_t)model->time_passed);
|
||||||
|
int minutes_left = total_time_left / 60;
|
||||||
|
int seconds_left = total_time_left % 60;
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
|
||||||
|
// Play sound
|
||||||
|
if(total_time_left == 0 && !model->sound_playing) {
|
||||||
|
model->sound_playing = true;
|
||||||
|
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_finish);
|
||||||
|
}
|
||||||
|
if(total_time_left < 0) {
|
||||||
|
model->timer_running = false;
|
||||||
|
model->time_passed = 0;
|
||||||
|
model->sound_playing = false;
|
||||||
|
|
||||||
|
model->rest_running = true;
|
||||||
|
model->rest_start_timestamp = current_timestamp;
|
||||||
|
seconds_left = 0;
|
||||||
|
model->counter += 1;
|
||||||
|
}
|
||||||
|
if(!model->rest_running) {
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02d:%02d", minutes_left, seconds_left);
|
||||||
|
canvas_draw_str(canvas, 0, 39, buffer);
|
||||||
|
}
|
||||||
|
if(model->timer_running) {
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
elements_multiline_text_aligned(canvas, 0, 50, AlignLeft, AlignTop, "Time to work");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Time to rest
|
||||||
|
if(model->rest_running && !model->timer_running) {
|
||||||
|
canvas_set_font(canvas, FontBigNumbers);
|
||||||
|
int rest_passed = current_timestamp - model->rest_start_timestamp;
|
||||||
|
int rest_total_time_left = (max_seconds_rest - rest_passed);
|
||||||
|
int rest_minutes_left = rest_total_time_left / 60;
|
||||||
|
int rest_seconds_left = rest_total_time_left % 60;
|
||||||
|
|
||||||
|
// Play sound
|
||||||
|
if(rest_total_time_left == 0 && !model->sound_playing) {
|
||||||
|
model->sound_playing = true;
|
||||||
|
notification_message(furi_record_open(RECORD_NOTIFICATION), &sequence_rest);
|
||||||
|
}
|
||||||
|
if(rest_total_time_left < 0) {
|
||||||
|
rest_seconds_left = 0;
|
||||||
|
model->rest_running = false;
|
||||||
|
model->sound_playing = false;
|
||||||
|
}
|
||||||
|
snprintf(buffer, sizeof(buffer), "%02d:%02d", rest_minutes_left, rest_seconds_left);
|
||||||
|
canvas_draw_str(canvas, 0, 60, buffer);
|
||||||
|
|
||||||
|
canvas_set_font(canvas, FontPrimary);
|
||||||
|
elements_multiline_text_aligned(canvas, 0, 27, AlignLeft, AlignTop, "Have a rest");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clocks
|
||||||
|
canvas_set_font(canvas, FontSecondary);
|
||||||
|
snprintf(
|
||||||
|
buffer,
|
||||||
|
sizeof(buffer),
|
||||||
|
"%02ld:%02ld:%02ld",
|
||||||
|
((uint32_t)current_timestamp % (60 * 60 * 24)) / (60 * 60),
|
||||||
|
((uint32_t)current_timestamp % (60 * 60)) / 60,
|
||||||
|
(uint32_t)current_timestamp % 60);
|
||||||
|
canvas_draw_str(canvas, 0, 20, buffer);
|
||||||
|
|
||||||
|
// Tomato counter
|
||||||
|
if(model->counter > 5) {
|
||||||
|
model->counter = 1;
|
||||||
|
}
|
||||||
|
for(int i = 0; i < model->counter; ++i) {
|
||||||
|
canvas_draw_disc(canvas, 122 - i * 10, 15, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
33
applications/plugins/pomodoro/pomodoro_timer.h
Normal file
33
applications/plugins/pomodoro/pomodoro_timer.h
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
|
||||||
|
typedef struct PomodoroTimer PomodoroTimer;
|
||||||
|
|
||||||
|
struct PomodoroTimer {
|
||||||
|
View* view;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct PomodoroTimerModel PomodoroTimerModel;
|
||||||
|
|
||||||
|
struct PomodoroTimerModel {
|
||||||
|
bool ok_pressed;
|
||||||
|
bool reset_pressed;
|
||||||
|
bool back_pressed;
|
||||||
|
bool connected;
|
||||||
|
bool timer_running;
|
||||||
|
bool rest_running;
|
||||||
|
bool sound_playing;
|
||||||
|
uint32_t timer_start_timestamp;
|
||||||
|
uint32_t timer_stopped_seconds;
|
||||||
|
uint32_t time_passed;
|
||||||
|
uint32_t rest_start_timestamp;
|
||||||
|
int counter;
|
||||||
|
};
|
||||||
|
|
||||||
|
void pomodoro_timer_process(PomodoroTimer* pomodoro_timer, InputEvent* event);
|
||||||
|
|
||||||
|
void pomodoro_draw_callback(Canvas* canvas, void* context, int max_seconds, int max_seconds_rest);
|
||||||
BIN
applications/plugins/pomodoro/pomodoro_timer.png
Normal file
BIN
applications/plugins/pomodoro/pomodoro_timer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 306 B |
46
applications/plugins/pomodoro/views/pomodoro_10.c
Normal file
46
applications/plugins/pomodoro/views/pomodoro_10.c
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include "../pomodoro_timer.h"
|
||||||
|
#include "pomodoro_10.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
static void pomodoro_10_draw_callback(Canvas* canvas, void* context) {
|
||||||
|
int max_seconds = 60 * 10;
|
||||||
|
int max_seconds_rest = 60 * 2;
|
||||||
|
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool pomodoro_10_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
PomodoroTimer* pomodoro_10 = context;
|
||||||
|
|
||||||
|
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
pomodoro_timer_process(pomodoro_10, event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PomodoroTimer* pomodoro_10_alloc() {
|
||||||
|
PomodoroTimer* pomodoro_10 = malloc(sizeof(PomodoroTimer));
|
||||||
|
pomodoro_10->view = view_alloc();
|
||||||
|
view_set_context(pomodoro_10->view, pomodoro_10);
|
||||||
|
view_allocate_model(pomodoro_10->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
|
||||||
|
view_set_draw_callback(pomodoro_10->view, pomodoro_10_draw_callback);
|
||||||
|
view_set_input_callback(pomodoro_10->view, pomodoro_10_input_callback);
|
||||||
|
|
||||||
|
return pomodoro_10;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pomodoro_10_free(PomodoroTimer* pomodoro_10) {
|
||||||
|
furi_assert(pomodoro_10);
|
||||||
|
view_free(pomodoro_10->view);
|
||||||
|
free(pomodoro_10);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10) {
|
||||||
|
furi_assert(pomodoro_10);
|
||||||
|
return pomodoro_10->view;
|
||||||
|
}
|
||||||
10
applications/plugins/pomodoro/views/pomodoro_10.h
Normal file
10
applications/plugins/pomodoro/views/pomodoro_10.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include "../pomodoro_timer.h"
|
||||||
|
|
||||||
|
PomodoroTimer* pomodoro_10_alloc();
|
||||||
|
|
||||||
|
void pomodoro_10_free(PomodoroTimer* pomodoro_10);
|
||||||
|
|
||||||
|
View* pomodoro_10_get_view(PomodoroTimer* pomodoro_10);
|
||||||
46
applications/plugins/pomodoro/views/pomodoro_25.c
Normal file
46
applications/plugins/pomodoro/views/pomodoro_25.c
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include "../pomodoro_timer.h"
|
||||||
|
#include "pomodoro_25.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
static void pomodoro_25_draw_callback(Canvas* canvas, void* context) {
|
||||||
|
int max_seconds = 60 * 25;
|
||||||
|
int max_seconds_rest = 60 * 5;
|
||||||
|
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool pomodoro_25_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
PomodoroTimer* pomodoro_25 = context;
|
||||||
|
|
||||||
|
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
pomodoro_timer_process(pomodoro_25, event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PomodoroTimer* pomodoro_25_alloc() {
|
||||||
|
PomodoroTimer* pomodoro_25 = malloc(sizeof(PomodoroTimer));
|
||||||
|
pomodoro_25->view = view_alloc();
|
||||||
|
view_set_context(pomodoro_25->view, pomodoro_25);
|
||||||
|
view_allocate_model(pomodoro_25->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
|
||||||
|
view_set_draw_callback(pomodoro_25->view, pomodoro_25_draw_callback);
|
||||||
|
view_set_input_callback(pomodoro_25->view, pomodoro_25_input_callback);
|
||||||
|
|
||||||
|
return pomodoro_25;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pomodoro_25_free(PomodoroTimer* pomodoro_25) {
|
||||||
|
furi_assert(pomodoro_25);
|
||||||
|
view_free(pomodoro_25->view);
|
||||||
|
free(pomodoro_25);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25) {
|
||||||
|
furi_assert(pomodoro_25);
|
||||||
|
return pomodoro_25->view;
|
||||||
|
}
|
||||||
10
applications/plugins/pomodoro/views/pomodoro_25.h
Normal file
10
applications/plugins/pomodoro/views/pomodoro_25.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include "../pomodoro_timer.h"
|
||||||
|
|
||||||
|
PomodoroTimer* pomodoro_25_alloc();
|
||||||
|
|
||||||
|
void pomodoro_25_free(PomodoroTimer* pomodoro_25);
|
||||||
|
|
||||||
|
View* pomodoro_25_get_view(PomodoroTimer* pomodoro_25);
|
||||||
46
applications/plugins/pomodoro/views/pomodoro_50.c
Normal file
46
applications/plugins/pomodoro/views/pomodoro_50.c
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
#include "../pomodoro_timer.h"
|
||||||
|
#include "pomodoro_50.h"
|
||||||
|
#include <furi.h>
|
||||||
|
#include <furi_hal.h>
|
||||||
|
#include <gui/elements.h>
|
||||||
|
#include <notification/notification_messages.h>
|
||||||
|
|
||||||
|
static void pomodoro_50_draw_callback(Canvas* canvas, void* context) {
|
||||||
|
int max_seconds = 60 * 50;
|
||||||
|
int max_seconds_rest = 60 * 10;
|
||||||
|
pomodoro_draw_callback(canvas, context, max_seconds, max_seconds_rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool pomodoro_50_input_callback(InputEvent* event, void* context) {
|
||||||
|
furi_assert(context);
|
||||||
|
PomodoroTimer* pomodoro_50 = context;
|
||||||
|
|
||||||
|
if(event->type == InputTypeLong && event->key == InputKeyBack) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
pomodoro_timer_process(pomodoro_50, event);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PomodoroTimer* pomodoro_50_alloc() {
|
||||||
|
PomodoroTimer* pomodoro_50 = malloc(sizeof(PomodoroTimer));
|
||||||
|
pomodoro_50->view = view_alloc();
|
||||||
|
view_set_context(pomodoro_50->view, pomodoro_50);
|
||||||
|
view_allocate_model(pomodoro_50->view, ViewModelTypeLocking, sizeof(PomodoroTimerModel));
|
||||||
|
view_set_draw_callback(pomodoro_50->view, pomodoro_50_draw_callback);
|
||||||
|
view_set_input_callback(pomodoro_50->view, pomodoro_50_input_callback);
|
||||||
|
|
||||||
|
return pomodoro_50;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pomodoro_50_free(PomodoroTimer* pomodoro_50) {
|
||||||
|
furi_assert(pomodoro_50);
|
||||||
|
view_free(pomodoro_50->view);
|
||||||
|
free(pomodoro_50);
|
||||||
|
}
|
||||||
|
|
||||||
|
View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50) {
|
||||||
|
furi_assert(pomodoro_50);
|
||||||
|
return pomodoro_50->view;
|
||||||
|
}
|
||||||
10
applications/plugins/pomodoro/views/pomodoro_50.h
Normal file
10
applications/plugins/pomodoro/views/pomodoro_50.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <gui/view.h>
|
||||||
|
#include "../pomodoro_timer.h"
|
||||||
|
|
||||||
|
PomodoroTimer* pomodoro_50_alloc();
|
||||||
|
|
||||||
|
void pomodoro_50_free(PomodoroTimer* pomodoro_50);
|
||||||
|
|
||||||
|
View* pomodoro_50_get_view(PomodoroTimer* pomodoro_50);
|
||||||
Reference in New Issue
Block a user