From 0f5defe78942726687927329e0f2383b9f051490 Mon Sep 17 00:00:00 2001 From: jokomo24 <45131638+jokomo24@users.noreply.github.com> Date: Wed, 18 Sep 2024 02:55:50 +0200 Subject: [PATCH] Face for tracking the menstrual cycle (#250) Authored-by: jokomo --- movement/make/Makefile | 1 + movement/movement_faces.h | 1 + .../complication/menstrual_cycle_face.c | 472 ++++++++++++++++++ .../complication/menstrual_cycle_face.h | 80 +++ 4 files changed, 554 insertions(+) create mode 100644 movement/watch_faces/complication/menstrual_cycle_face.c create mode 100644 movement/watch_faces/complication/menstrual_cycle_face.h diff --git a/movement/make/Makefile b/movement/make/Makefile index 38f684d..3990f9f 100644 --- a/movement/make/Makefile +++ b/movement/make/Makefile @@ -119,6 +119,7 @@ SRCS += \ ../watch_faces/complication/toss_up_face.c \ ../watch_faces/complication/geomancy_face.c \ ../watch_faces/clock/simple_clock_bin_led_face.c \ + ../watch_faces/complication/menstrual_cycle_face.c \ ../watch_faces/complication/flashlight_face.c \ ../watch_faces/clock/decimal_time_face.c \ ../watch_faces/clock/wyoscan_face.c \ diff --git a/movement/movement_faces.h b/movement/movement_faces.h index 5a74df8..7c28f23 100644 --- a/movement/movement_faces.h +++ b/movement/movement_faces.h @@ -94,6 +94,7 @@ #include "geomancy_face.h" #include "dual_timer_face.h" #include "simple_clock_bin_led_face.h" +#include "menstrual_cycle_face.h" #include "flashlight_face.h" #include "decimal_time_face.h" #include "wyoscan_face.h" diff --git a/movement/watch_faces/complication/menstrual_cycle_face.c b/movement/watch_faces/complication/menstrual_cycle_face.c new file mode 100644 index 0000000..812de59 --- /dev/null +++ b/movement/watch_faces/complication/menstrual_cycle_face.c @@ -0,0 +1,472 @@ +/* + * MIT License + * + * Copyright (c) 2023 Joseph Borne Komosa | @jokomo24 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + * Menstrual Cycle Face + * + * Background: + * + * I discovered the Casio F-91W through my partner, appreciated the retro aesthetic of the watch, + * and got one for myself. Soon afterward I discovered the Sensor Watch project and ordered two boards! + * I introduced the Sensor Watch to my partner who inquired whether she could track her menstrual cycle. + * So I decided to implement a menstrual cycle watch face that also calculates the peak fertility window + * using The Calendar Method. While this information may be useful when attempting to achieve or avoid + * pregnancy, it is important to understand that these are rough estimates at best. + * + * How to use: + * + * 1. To begin tracking, go to 'Last Period' page and toggle the alarm button to the number of days since + * the last, most recent, period and hold the alarm button to enter. This will perform the following actions: + * - Store the corresponding date as the 'first' period in order to calculate the total_days_tracked. + * - Turn on the Signal Indicator to signify that tracking has been activated. + * - Deactivate this page and instead show the ticking animation. + * - Adjust the days left in the 'Period in Days' page accordingly. + * - Activate the 'Period Is Here' page and no longer display 'NA'. To prevent accidental user entry, + * the page will display the ticking animation until ten days have passed since the date of the last + * period entered. + * - Activate the 'Peak Fertility' page to begin showing the estimated window, + * as well as display the Alarm Indicator, on this page and on the main 'Period in Days' page, + * whenever the current date falls within the Peak Fertility Window. + * + * 2. Toggle and enter 'y' in the 'Period Is Here' page on the day of every sequential period afterward. + * DO NOT FORGET TO DO SO! + * - If forgotten, the data will become inaccurate and tracking will need to be reset! -> (FIXME, allow one to enter a 'missed' period using the 'Last Period' page). + * This will perform the following actions: + * - Calculate this completed cycle's length and reevaluate the shortest and longest cycle variables. + * - Increment total_cycles by one. + * - Recalculate and save the average cycle for 'Average Cycle' page. + */ + +#include +#include +#include "menstrual_cycle_face.h" +#include "watch.h" +#include "watch_utility.h" + +#define TYPICAL_AVG_CYC 28 +#define SECONDS_PER_DAY 86400 + +#define MENSTRUAL_CYCLE_FACE_NUM_PAGES (6) +enum { + period_in_num_days, + average_cycle, + peak_fertility_window, + period_is_here, + first_period, + reset, +} page_titles_e; +const char menstrual_cycle_face_titles[MENSTRUAL_CYCLE_FACE_NUM_PAGES][11] = { + "Prin day", // Period In Days: Estimated days till the next period occurs + "Av cycle ", // Average Cycle: The average number of days estimated per cycle + "Peak Fert ", // Peak Fertility Window: The first and last day of month (displayed top & bottom right, respectively, once tracking) for the estimated window of fertility + "Prishere ", // Period Is Here: Toggle and enter 'y' on the day the actual period occurs to improve Avg and Fert estimations + "Last Per ", // Last Period: Enter the number of days since the last period to begin tracking from that corresponding date by storing it as the 'first' + " Reset ", // Reset: Toggle and enter 'y' to reset tracking data +}; + +/* Beep function */ +static inline void beep(movement_settings_t *settings) { + if (settings->bit.button_should_sound) + watch_buzzer_play_note(BUZZER_NOTE_E8, 75); +} + +// Calculate the total number of days for which menstrual cycle tracking has been active +static inline uint32_t total_days_tracked(menstrual_cycle_state_t *state) { + + // If tracking has not yet been activated, return 0 + if (!(state->dates.reg)) + return 0; + + // Otherwise, set the start date to the first day of the first tracked cycle + watch_date_time date_time_start; + date_time_start.unit.second = 0; + date_time_start.unit.minute = 0; + date_time_start.unit.hour = 0; + date_time_start.unit.day = state->dates.bit.first_day; + date_time_start.unit.month = state->dates.bit.first_month; + date_time_start.unit.year = state->dates.bit.first_year; + + // Get the current date and time + watch_date_time date_time_now = watch_rtc_get_date_time(); + + // Convert the start date and current date to Unix time + uint32_t unix_start = watch_utility_date_time_to_unix_time(date_time_start, state->utc_offset); + uint32_t unix_now = watch_utility_date_time_to_unix_time(date_time_now, state->utc_offset); + + // Calculate the total number of days and return it + return (unix_now - unix_start) / SECONDS_PER_DAY; +} + +// Calculate the number of days until the next menstrual period +static inline int8_t days_till_period(menstrual_cycle_state_t *state) { + + // Calculate the number of days left until the next period based on the average cycle length and the number of cycles tracked + int8_t days_left = (state->cycles.bit.average_cycle * (state->cycles.bit.total_cycles + 1)) - total_days_tracked(state); + + // If the result is negative, return 0 (i.e., the period is expected to start today or has already started) + return (days_left < 0) ? 0 : days_left; +} + +static inline void reset_tracking(menstrual_cycle_state_t *state) { + + state->dates.bit.first_day = 0; + state->dates.bit.first_month = 0; + state->dates.bit.first_year = 0; + + state->dates.bit.prev_day = 0; + state->dates.bit.prev_month = 0; + state->dates.bit.prev_year = 0; + + state->cycles.bit.shortest_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.longest_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.average_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.total_cycles = 0; + + state->dates.bit.reserved = 0; + state->cycles.bit.reserved = 0; + + watch_store_backup_data(state->dates.reg, state->backup_register_dt); + watch_store_backup_data(state->cycles.reg, state->backup_register_cy); + + watch_clear_indicator(WATCH_INDICATOR_SIGNAL); +} + +/* +Fertility Window based on "The Calendar Method" +Source: https://www.womenshealth.gov/pregnancy/you-get-pregnant/trying-conceive + +The Calendar Method has several steps: + +Step 1: Track the menstrual cycle for 8–12 months. One cycle is from the first day of one + period until the first day of the next period. The average cycle is 28 days, but + it may be as short as 24 days or as long as 38 days. +Step 2: Subtract 18 from the number of days in the shortest menstrual cycle. +Step 3: Subtract 11 from the number of days in the longest menstrual cycle. +Step 4: Using a calendar, mark down the start of the next period (using previous instead). Count ahead by the number + of days calculated in step 2. This is when peak fertility begins. Peak fertility ends + at the number of days calculated in step 3. +NOTE: Right now, the fertility window face displays its estimated window as soon as tracking is activated, although + it is important to keep in mind that The Calendar Method states that peak accuracy of the window will be + reached only after at least 8 months of tracking the menstrual cycle (can make it so that it only displays + after total_days_tracked >= 8 months...but the info is interesting and should already be taken with the understanding that, + in general, it is a rough estimation at best). +*/ +typedef enum Fertile_Window {first_day, last_day} fertile_window; +// Calculate the predicted starting or ending day of peak fertility +static inline uint32_t get_day_pk_fert(menstrual_cycle_state_t *state, fertile_window which_day) { + + // Get the date of the previous period + watch_date_time date_prev_period; + date_prev_period.unit.second = 0; + date_prev_period.unit.minute = 0; + date_prev_period.unit.hour = 0; + date_prev_period.unit.day = state->dates.bit.prev_day; + date_prev_period.unit.month = state->dates.bit.prev_month; + date_prev_period.unit.year = state->dates.bit.prev_year; + + // Convert the previous period date to Unix time + uint32_t unix_prev_period = watch_utility_date_time_to_unix_time(date_prev_period, state->utc_offset); + + // Calculate the Unix time of the predicted peak fertility day based on the length of the shortest/longest cycle + uint32_t unix_pk_date; + switch(which_day) { + case first_day: + unix_pk_date = unix_prev_period + ((state->cycles.bit.shortest_cycle - 18) * SECONDS_PER_DAY); + break; + case last_day: + unix_pk_date = unix_prev_period + ((state->cycles.bit.longest_cycle - 11) * SECONDS_PER_DAY); + break; + } + + // Convert the Unix time of the predicted peak fertility day to a date/time and return the day of the month + return watch_utility_date_time_from_unix_time(unix_pk_date, state->utc_offset).unit.day; +} + +// Determine if today falls within the predicted peak fertility window +static inline bool inside_fert_window(menstrual_cycle_state_t *state) { + + // If tracking has not yet been activated, return false + if (!(state->dates.reg)) + return false; + + // Get the current date/time + watch_date_time date_time_now = watch_rtc_get_date_time(); + + // Check if the current day falls between the first and last predicted peak fertility days + if (get_day_pk_fert(state, first_day) > get_day_pk_fert(state, last_day)) { // We are crossing over the end of the month + if (date_time_now.unit.day >= get_day_pk_fert(state, first_day) || + date_time_now.unit.day <= get_day_pk_fert(state, last_day)) + return true; + } + else if (date_time_now.unit.day >= get_day_pk_fert(state, first_day) && + date_time_now.unit.day <= get_day_pk_fert(state, last_day)) + return true; + // If the current day does not fall within the predicted peak fertility window, return false + return false; +} + +// Update the shortest and longest menstrual cycles based on the previous menstrual cycle +static inline void update_shortest_longest_cycle(menstrual_cycle_state_t *state) { + + // Get the date of the previous menstrual cycle + watch_date_time date_prev_period; + date_prev_period.unit.second = 0; + date_prev_period.unit.minute = 0; + date_prev_period.unit.hour = 0; + date_prev_period.unit.day = state->dates.bit.prev_day; + date_prev_period.unit.month = state->dates.bit.prev_month; + date_prev_period.unit.year = state->dates.bit.prev_year; + + // Convert the date of the previous menstrual cycle to UNIX time + uint32_t unix_prev_period = watch_utility_date_time_to_unix_time(date_prev_period, state->utc_offset); + + // Calculate the length of the current menstrual cycle + uint8_t cycle_length = total_days_tracked(state) - (unix_prev_period / SECONDS_PER_DAY); + + // Update the shortest or longest cycle length if necessary + if (cycle_length < state->cycles.bit.shortest_cycle) + state->cycles.bit.shortest_cycle = cycle_length; + else if (cycle_length > state->cycles.bit.longest_cycle) + state->cycles.bit.longest_cycle = cycle_length; +} + +void menstrual_cycle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr) { + (void) watch_face_index; + (void) settings; + + if (*context_ptr == NULL) { + *context_ptr = malloc(sizeof(menstrual_cycle_state_t)); + memset(*context_ptr, 0, sizeof(menstrual_cycle_state_t)); + menstrual_cycle_state_t *state = ((menstrual_cycle_state_t *)*context_ptr); + + state->dates.bit.first_day = 0; + state->dates.bit.first_month = 0; + state->dates.bit.first_year = 0; + + state->dates.bit.prev_day = 0; + state->dates.bit.prev_month = 0; + state->dates.bit.prev_year = 0; + + state->cycles.bit.shortest_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.longest_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.average_cycle = TYPICAL_AVG_CYC; + state->cycles.bit.total_cycles = 0; + + state->dates.bit.reserved = 0; + state->cycles.bit.reserved = 0; + + state->backup_register_dt = 0; + state->backup_register_cy = 0; + } + + menstrual_cycle_state_t *state = ((menstrual_cycle_state_t *)*context_ptr); + if (!(state->backup_register_dt && state->backup_register_cy)) { + state->backup_register_dt = movement_claim_backup_register(); + state->backup_register_cy = movement_claim_backup_register(); + + if (state->backup_register_dt && state->backup_register_cy) { + watch_store_backup_data(state->dates.reg, state->backup_register_dt); + watch_store_backup_data(state->cycles.reg, state->backup_register_cy); + } + } + else { + state->dates.reg = watch_get_backup_data(state->backup_register_dt); + state->cycles.reg = watch_get_backup_data(state->backup_register_cy); + } +} + +void menstrual_cycle_face_activate(movement_settings_t *settings, void *context) { + (void) settings; + menstrual_cycle_state_t *state = (menstrual_cycle_state_t *)context; + state->period_today = 0; + state->current_page = 0; + state->reset_tracking = 0; + state->utc_offset = movement_timezone_offsets[settings->bit.time_zone] * 60; + movement_request_tick_frequency(4); // we need to manually blink some pixels +} + +bool menstrual_cycle_face_loop(movement_event_t event, movement_settings_t *settings, void *context) { + menstrual_cycle_state_t *state = (menstrual_cycle_state_t *)context; + watch_date_time date_period; + uint8_t current_page = state->current_page; + uint8_t first_day_fert; + uint8_t last_day_fert; + uint32_t unix_now; + uint32_t unix_prev_period; + switch (event.event_type) { + case EVENT_TICK: + case EVENT_ACTIVATE: + // Do nothing; handled below. + break; + case EVENT_MODE_BUTTON_UP: + movement_move_to_next_face(); + return false; + case EVENT_LIGHT_BUTTON_DOWN: + current_page = (current_page + 1) % MENSTRUAL_CYCLE_FACE_NUM_PAGES; + state->current_page = current_page; + state->days_prev_period = 0; + watch_clear_indicator(WATCH_INDICATOR_BELL); + if (watch_tick_animation_is_running()) + watch_stop_tick_animation(); + break; + case EVENT_ALARM_LONG_PRESS: + switch (current_page) { + case period_in_num_days: + break; + case average_cycle: + break; + case peak_fertility_window: + break; + case period_is_here: + if (state->period_today && total_days_tracked(state)) { + // Calculate before updating date of last period + update_shortest_longest_cycle(state); + // Update the date of last period after calulating the, now previous, cycle length + date_period = watch_rtc_get_date_time(); + state->dates.bit.prev_day = date_period.unit.day; + state->dates.bit.prev_month = date_period.unit.month; + state->dates.bit.prev_year = date_period.unit.year; + // Calculate new cycle average + state->cycles.bit.total_cycles += 1; + state->cycles.bit.average_cycle = total_days_tracked(state) / state->cycles.bit.total_cycles; + // Store the new data + watch_store_backup_data(state->dates.reg, state->backup_register_dt); + watch_store_backup_data(state->cycles.reg, state->backup_register_cy); + state->period_today = !(state->period_today); + beep(settings); + } + break; + case first_period: + // If tracking has not yet been activated + if (!(state->dates.reg)) { + unix_now = watch_utility_date_time_to_unix_time(watch_rtc_get_date_time(), state->utc_offset); + unix_prev_period = unix_now - (state->days_prev_period * SECONDS_PER_DAY); + date_period = watch_utility_date_time_from_unix_time(unix_prev_period, state->utc_offset); + state->dates.bit.first_day = date_period.unit.day; + state->dates.bit.first_month = date_period.unit.month; + state->dates.bit.first_year = date_period.unit.year; + state->dates.bit.prev_day = date_period.unit.day; + state->dates.bit.prev_month = date_period.unit.month; + state->dates.bit.prev_year = date_period.unit.year; + watch_store_backup_data(state->dates.reg, state->backup_register_dt); + beep(settings); + } + break; + case reset: + if (state->reset_tracking) { + reset_tracking(state); + state->reset_tracking = !(state->reset_tracking); + beep(settings); + } + break; + } + break; + case EVENT_ALARM_BUTTON_UP: + switch (current_page) { + case period_in_num_days: + break; + case average_cycle: + break; + case peak_fertility_window: + break; + case period_is_here: + if (total_days_tracked(state)) + state->period_today = !(state->period_today); + break; + case first_period: + if (!(state->dates.reg)) + state->days_prev_period = (state->days_prev_period > 99) ? 0 : state->days_prev_period + 1; // Cycle through pages to quickly reset to 0 + break; + case reset: + state->reset_tracking = !(state->reset_tracking); + break; + } + break; + case EVENT_TIMEOUT: + movement_move_to_face(0); + break; + default: + return movement_default_loop_handler(event, settings); + } + + watch_display_string((char *)menstrual_cycle_face_titles[current_page], 0); + if (state->dates.reg) + watch_set_indicator(WATCH_INDICATOR_SIGNAL); // signal that we are now in a tracking state + + char buf[13]; + switch (current_page) { + case period_in_num_days: + sprintf(buf, "%2d", days_till_period(state)); + if (inside_fert_window(state)) + watch_set_indicator(WATCH_INDICATOR_BELL); + watch_display_string(buf, 4); + break; + case average_cycle: + sprintf(buf, "%2d", state->cycles.bit.average_cycle); + watch_display_string(buf, 2); + break; + case peak_fertility_window: + if (event.subsecond % 5 && state->dates.reg) { // blink active for 3 quarter-seconds + first_day_fert = get_day_pk_fert(state, first_day); + last_day_fert = get_day_pk_fert(state, last_day); + sprintf(buf, "Fr%2d To %2d", first_day_fert, last_day_fert); // From: first day | To: last day + if (inside_fert_window(state)) + watch_set_indicator(WATCH_INDICATOR_BELL); + watch_display_string(buf, 0); + } + break; + case period_is_here: + if (event.subsecond % 5) { // blink active for 3 quarter-seconds + if (!(state->dates.reg)) + watch_display_string("NA", 8); // Not Applicable: Do not allow period entry until tracking is activated... + else if (state->period_today) + watch_display_string("y", 9); + else + watch_display_string("n", 9); + } + break; + case first_period: + if (state->dates.reg) { + if (!watch_tick_animation_is_running()) + watch_start_tick_animation(500); // Tracking activated + } + else if (event.subsecond % 5) { // blink active for 3 quarter-seconds + sprintf(buf, "%2d", state->days_prev_period); + watch_display_string(buf, 8); + } + break; + case reset: + // blink active for 3 quarter-seconds + if (event.subsecond % 5 && state->reset_tracking) + watch_display_string("y", 9); + else if (event.subsecond % 5) + watch_display_string("n", 9); + break; + } + return true; +} + +void menstrual_cycle_face_resign(movement_settings_t *settings, void *context) { + (void) settings; + (void) context; +} \ No newline at end of file diff --git a/movement/watch_faces/complication/menstrual_cycle_face.h b/movement/watch_faces/complication/menstrual_cycle_face.h new file mode 100644 index 0000000..73adaf8 --- /dev/null +++ b/movement/watch_faces/complication/menstrual_cycle_face.h @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2023 Joseph Borne Komosa | @jokomo24 + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifndef MENSTRUAL_CYCLE_FACE_H_ +#define MENSTRUAL_CYCLE_FACE_H_ + +#include "movement.h" + +typedef struct { + // Store the date of the 'first' and the total cycles since to calulate and store the average menstrual cycle. + // Store the date of the previous, most recent, period to calculate the cycle length. + // Store the shortest and longest cycle to calculate the fertility window for The Calender Method. + // NOTE: Not thrilled about using two registers, but could not find a way to perform The Calender Method + // without requiring both the 'first' and 'prev' dates. + union { + struct { + uint8_t first_day : 5; + uint8_t first_month : 4; + uint8_t first_year : 6; // 0-63 (representing 2020-2083) + uint8_t prev_day : 5; + uint8_t prev_month : 4; + uint8_t prev_year : 6; // 0-63 (representing 2020-2083) + uint8_t reserved : 2; // left over bit space + } bit; + uint32_t reg; // Tracking's been activated if > 0 + } dates; + union { + struct { + uint8_t shortest_cycle : 6; // For step 2 of The Calender Method + uint8_t longest_cycle : 6; // For step 3 of The Calender Method + uint8_t average_cycle : 6; // The average menstrual cycle lasts 28 days, but normal cycles can vary from 21 to 35 days + uint16_t total_cycles : 11; // The total cycles (periods) entered since the start of tracking + uint8_t reserved : 3; // left over bit space + } bit; + uint32_t reg; + } cycles; + uint8_t backup_register_dt; + uint8_t backup_register_cy; + uint8_t current_page; + uint8_t days_prev_period; + int32_t utc_offset; + bool period_today; + bool reset_tracking; +} menstrual_cycle_state_t; + +void menstrual_cycle_face_setup(movement_settings_t *settings, uint8_t watch_face_index, void ** context_ptr); +void menstrual_cycle_face_activate(movement_settings_t *settings, void *context); +bool menstrual_cycle_face_loop(movement_event_t event, movement_settings_t *settings, void *context); +void menstrual_cycle_face_resign(movement_settings_t *settings, void *context); + +#define menstrual_cycle_face ((const watch_face_t){ \ + menstrual_cycle_face_setup, \ + menstrual_cycle_face_activate, \ + menstrual_cycle_face_loop, \ + menstrual_cycle_face_resign, \ + NULL, \ +}) + +#endif // MENSTRUAL_CYCLE_FACE_H_ \ No newline at end of file