#include <pebble.h>
#include <string.h>
#include "fastforge.h"
enum {
MAIN_MENU_INDEX_START_NEW = 0,
MAIN_MENU_INDEX_CURRENT_TIMER = 1,
MAIN_MENU_INDEX_STOP_CURRENT = 2,
MAIN_MENU_INDEX_HISTORY = 3,
MAIN_MENU_INDEX_STATS = 4,
MAIN_MENU_INDEX_SCIENCE = 5,
MAIN_MENU_INDEX_SETTINGS = 6,
MAIN_MENU_INDEX_BACKUP = 7,
MAIN_MENU_ITEM_COUNT = 8
};
enum {
PRESET_MENU_INDEX_DEFAULT = 0,
PRESET_MENU_INDEX_16H = 1,
PRESET_MENU_INDEX_18H = 2,
PRESET_MENU_INDEX_20H = 3,
PRESET_MENU_INDEX_24H = 4,
PRESET_MENU_INDEX_36H = 5,
PRESET_MENU_ITEM_COUNT = 6
};
typedef enum {
EDIT_FIELD_START = 0,
EDIT_FIELD_END = 1
} EditField;
FastEntry history[MAX_FASTS];
int history_count = 0;
FastEntry current_fast = {0};
uint16_t global_target_minutes = DEFAULT_TARGET_MINUTES;
StreakData streak_data = {0};
AppTimer *alarm_timer = NULL;
time_t target_time = 0;
static Window *s_menu_window;
static Window *s_timer_window;
static Window *s_goal_window;
static Window *s_presets_window;
static Window *s_stats_window;
static Window *s_detail_window;
static Window *s_history_window;
static Window *s_history_edit_window;
static SimpleMenuLayer *s_main_menu_layer;
static SimpleMenuLayer *s_presets_menu_layer;
static MenuLayer *s_history_menu_layer;
static SimpleMenuSection s_main_menu_sections[1];
static SimpleMenuSection s_presets_menu_sections[1];
static SimpleMenuItem s_main_menu_items[MAIN_MENU_ITEM_COUNT];
static SimpleMenuItem s_presets_menu_items[PRESET_MENU_ITEM_COUNT];
static TextLayer *s_title_layer;
static TextLayer *s_timer_layer;
static TextLayer *s_detail_layer;
static TextLayer *s_stage_layer;
static TextLayer *s_hint_layer;
static Layer *s_progress_layer;
static Layer *s_goal_background_layer;
static TextLayer *s_goal_title_layer;
static TextLayer *s_goal_time_layer;
static TextLayer *s_goal_stage_layer;
static TextLayer *s_goal_hint_layer;
static TextLayer *s_placeholder_title_layer;
static TextLayer *s_placeholder_body_layer;
static TextLayer *s_placeholder_hint_layer;
static TextLayer *s_stats_title_layer;
static TextLayer *s_stats_body_layer;
static TextLayer *s_stats_hint_layer;
static TextLayer *s_history_edit_title_layer;
static TextLayer *s_history_edit_start_layer;
static TextLayer *s_history_edit_end_layer;
static TextLayer *s_history_edit_duration_layer;
static TextLayer *s_history_edit_stage_layer;
static TextLayer *s_history_edit_hint_layer;
static char s_title_text[24];
static char s_timer_text[16];
static char s_detail_text[48];
static char s_stage_text[32];
static char s_goal_time_text[24];
static char s_goal_stage_text[24];
static char s_menu_stop_subtitle[32];
static char s_placeholder_title_text[24];
static char s_placeholder_body_text[160];
static char s_placeholder_hint_text[24];
static char s_stats_body_text[160];
static char s_history_row_titles[MAX_FASTS][24];
static char s_history_row_subtitles[MAX_FASTS][40];
static char s_history_edit_title_text[24];
static char s_history_edit_start_text[32];
static char s_history_edit_end_text[32];
static char s_history_edit_duration_text[32];
static char s_history_edit_stage_text[24];
static char s_history_edit_hint_text[40];
static int s_history_edit_index = -1;
static FastEntry s_history_edit_draft = {0};
static EditField s_history_edit_field = EDIT_FIELD_START;
static bool s_history_edit_dirty = false;
static const uint32_t s_alarm_vibe[] = {200, 100, 200, 100, 400, 100, 200};
static VibePattern s_alarm_pattern = {
.durations = s_alarm_vibe,
.num_segments = ARRAY_LENGTH(s_alarm_vibe),
};
static void refresh_timer_view(void);
static void refresh_goal_window_content(void);
static void sync_main_menu_state(void);
static void refresh_stats_window_content(void);
static void history_menu_reload(void);
static bool safe_push_window(Window *window, bool animated) {
if (!window) {
return false;
}
if (window_stack_contains_window(window)) {
return false;
}
window_stack_push(window, animated);
return true;
}
static int clamp_history_count(int value) {
if (value < 0) {
return 0;
}
if (value > MAX_FASTS) {
return MAX_FASTS;
}
return value;
}
static void normalize_loaded_data(void) {
if (global_target_minutes == 0) {
global_target_minutes = DEFAULT_TARGET_MINUTES;
}
if (current_fast.start_time != 0 && current_fast.target_minutes == 0) {
current_fast.target_minutes = global_target_minutes;
}
if (current_fast.end_time != 0 && current_fast.end_time < current_fast.start_time) {
current_fast.end_time = 0;
}
}
void save_all_data(void) {
persist_write_int(KEY_HISTORY_COUNT, history_count);
persist_write_data(KEY_HISTORY_DATA, history, sizeof(FastEntry) * history_count);
persist_write_data(KEY_CURRENT_FAST, ¤t_fast, sizeof(FastEntry));
persist_write_int(KEY_TARGET_MIN, global_target_minutes);
persist_write_data(KEY_STREAK_DATA, &streak_data, sizeof(StreakData));
}
void load_all_data(void) {
history_count = 0;
memset(history, 0, sizeof(history));
memset(¤t_fast, 0, sizeof(current_fast));
global_target_minutes = DEFAULT_TARGET_MINUTES;
memset(&streak_data, 0, sizeof(streak_data));
if (persist_exists(KEY_HISTORY_COUNT)) {
history_count = clamp_history_count(persist_read_int(KEY_HISTORY_COUNT));
}
if (history_count > 0 && persist_exists(KEY_HISTORY_DATA)) {
const int expected_size = sizeof(FastEntry) * history_count;
const int read_size = persist_read_data(KEY_HISTORY_DATA, history, expected_size);
if (read_size != expected_size) {
history_count = 0;
memset(history, 0, sizeof(history));
}
}
if (persist_exists(KEY_CURRENT_FAST)) {
const int read_size = persist_read_data(KEY_CURRENT_FAST, ¤t_fast, sizeof(FastEntry));
if (read_size != (int)sizeof(FastEntry)) {
memset(¤t_fast, 0, sizeof(current_fast));
}
}
if (persist_exists(KEY_TARGET_MIN)) {
const int read_target = persist_read_int(KEY_TARGET_MIN);
if (read_target > 0 && read_target <= UINT16_MAX) {
global_target_minutes = (uint16_t)read_target;
}
}
if (persist_exists(KEY_STREAK_DATA)) {
const int read_size = persist_read_data(KEY_STREAK_DATA, &streak_data, sizeof(StreakData));
if (read_size != (int)sizeof(StreakData)) {
memset(&streak_data, 0, sizeof(streak_data));
}
}
normalize_loaded_data();
}
bool fast_is_running(void) {
return current_fast.start_time != 0;
}
static void format_hhmmss(time_t seconds, char *buffer, size_t size) {
if (seconds < 0) {
seconds = 0;
}
snprintf(buffer, size, "%02d:%02d:%02d",
(int)(seconds / 3600),
(int)((seconds % 3600) / 60),
(int)(seconds % 60));
}
static void format_duration_hours_minutes(time_t seconds, char *buffer, size_t size) {
if (seconds < 0) {
seconds = 0;
}
int hours = (int)(seconds / 3600);
int minutes = (int)((seconds % 3600) / 60);
snprintf(buffer, size, "%dh %02dm", hours, minutes);
}
static time_t entry_duration_seconds(const FastEntry *entry) {
if (!entry || entry->start_time == 0 || entry->end_time <= entry->start_time) {
return 0;
}
return entry->end_time - entry->start_time;
}
static void format_entry_datetime(time_t timestamp, char *buffer, size_t size) {
if (!buffer || size == 0) {
return;
}
if (timestamp <= 0) {
snprintf(buffer, size, "--");
return;
}
|