diff --git a/src/main.c b/src/main.c index 177637b..29ed917 100644 --- a/src/main.c +++ b/src/main.c @@ -26,6 +26,7 @@ #define COUNTDOWN_TIMER_SNOOZE_DELAY 60000 // milliseconds #define TIMER_MIN_LENGTH 5000 // milliseconds #define TIMELINE_MIN_LENGTH 900000 // milliseconds +#define TIMER_SORT_MODE_PERSIST_KEY 9938472 #define INACTIVITY_THRESHOLD 900000 // length of time before refresh throttling in milliseconds #define INACTIVE_REFRESH_DELAY 1000 // ms between frames after throttling #define REFRESH_DELAY 1000 // ms between periodic redraws @@ -47,10 +48,36 @@ static SettingWindow *s_setting_window = NULL; static PopupWindow *s_popup_window = NULL; static uint8_t s_countdown_timers_count = 0; static CountdownTimer *s_countdown_timers[COUNTDOWN_TIMERS_MAX] = {}; +static uint8_t s_timer_view_indices[COUNTDOWN_TIMERS_MAX] = {}; +static uint8_t s_timer_sort_mode = 0; // 0=created at, 1=duration static int32_t s_countdown_timer_id_max = 0; static AppTimer *s_app_timer = NULL; static int64_t s_last_activity = 0; +static void rebuild_timer_view_indices(void) { + for (uint8_t i = 0; i < s_countdown_timers_count; i++) { + s_timer_view_indices[i] = i; + } + + if (s_timer_sort_mode == 0) { + return; + } + + // Stable sort by duration (shortest -> longest), using created order as tiebreaker. + for (uint8_t i = 0; i < s_countdown_timers_count; i++) { + for (uint8_t j = 0; j + 1 < s_countdown_timers_count - i; j++) { + const uint8_t a_i = s_timer_view_indices[j]; + const uint8_t b_i = s_timer_view_indices[j + 1]; + const int64_t a = countdown_timer_get_duration(s_countdown_timers[a_i]); + const int64_t b = countdown_timer_get_duration(s_countdown_timers[b_i]); + if (a > b) { + s_timer_view_indices[j] = b_i; + s_timer_view_indices[j + 1] = a_i; + } + } + } +} + static uint16_t prv_get_next_refresh_delay(void) { if (popup_window_get_topmost_window(s_popup_window)) { return POPUP_REFRESH_DELAY; @@ -131,6 +158,11 @@ static void prv_sort_timers_by_recency(void) { */ static void prv_promote_timer(CountdownTimer *countdown_timer) { + if (s_timer_sort_mode != 0) { + rebuild_timer_view_indices(); + return; + } + int16_t index = countdown_timer_list_get_timer_index(s_countdown_timers, s_countdown_timers_count, countdown_timer); if (index > 0) { @@ -139,10 +171,10 @@ static void prv_promote_timer(CountdownTimer *countdown_timer) { s_countdown_timers[0] = countdown_timer; } prv_sort_timers_by_recency(); + rebuild_timer_view_indices(); } - /******************************************************************************* * CALLBACKS */ @@ -163,7 +195,10 @@ static void app_timer_callback(void *data) { if (countdown_timer != NULL) { // a timer just expired and is now paused; re-sort so it drops below any // still-running timers - prv_sort_timers_by_recency(); + if (s_timer_sort_mode == 0) { + prv_sort_timers_by_recency(); + } + rebuild_timer_view_indices(); // deep refresh the DetailWindow in case it was that timer detail_window_deep_refresh(s_detail_window); // show timer confirmation window @@ -273,6 +308,7 @@ static void setting_window_complete_callback(int64_t duration, void *context) { &s_countdown_timers_count, countdown_timer); countdown_timer_start(countdown_timer); // update visuals + rebuild_timer_view_indices(); menu_window_reload_data(s_menu_window); menu_window_refresh(s_menu_window); detail_window_set_countdown_timer(s_detail_window, countdown_timer); @@ -287,6 +323,7 @@ static void setting_window_complete_callback(int64_t duration, void *context) { } else { countdown_timer_update(countdown_timer, duration, true); countdown_timer_start(countdown_timer); + rebuild_timer_view_indices(); detail_window_deep_refresh(s_detail_window); setting_window_pop(setting_window, true); // deal with timeline @@ -378,6 +415,7 @@ static void detail_window_delete_timer_callback(CountdownTimer *countdown_timer, s_countdown_timers_count, countdown_timer); countdown_timer_destroy(countdown_timer); countdown_timer_list_remove(s_countdown_timers, &s_countdown_timers_count, timer_index); + rebuild_timer_view_indices(); // reload MenuWindow data (no idea why, but this must be called twice or when the last timer // is deleted, the "+" cell is stuck at the short cell height) menu_window_reload_data(s_menu_window); @@ -420,7 +458,7 @@ static void detail_window_delete_timer_callback(CountdownTimer *countdown_timer, static CountdownTimer *menu_window_get_timer_callback(uint8_t index, void *context) { if (index < s_countdown_timers_count) { - return s_countdown_timers[index]; + return s_countdown_timers[s_timer_view_indices[index]]; } // error handling APP_LOG(APP_LOG_LEVEL_ERROR, "Attempted to access timer outside array bounds"); @@ -438,6 +476,10 @@ static uint8_t menu_window_get_timer_count_callback(void *context) { return s_countdown_timers_count; } +static uint8_t menu_window_get_sort_mode_callback(void *context) { + return s_timer_sort_mode; +} + /* @@ -449,9 +491,24 @@ static void menu_window_click_callback(uint8_t index, void *context) { if (index == 0) { setting_window_set_timer(s_setting_window, NULL); setting_window_push(s_setting_window, true); + } else if (index == s_countdown_timers_count + 1) { + // toggle sort mode (created at <-> duration) + s_timer_sort_mode = !s_timer_sort_mode; + persist_write_int(TIMER_SORT_MODE_PERSIST_KEY, s_timer_sort_mode); + if (s_timer_sort_mode == 0) { + prv_sort_timers_by_recency(); + } + rebuild_timer_view_indices(); + menu_window_reload_data(s_menu_window); + menu_window_refresh(s_menu_window); } else { // show timer in detail window - detail_window_set_countdown_timer(s_detail_window, s_countdown_timers[index - 1]); + const uint8_t view_index = index - 1; + if (view_index >= s_countdown_timers_count) { + return; + } + detail_window_set_countdown_timer(s_detail_window, + s_countdown_timers[s_timer_view_indices[view_index]]); detail_window_push(s_detail_window, true); detail_window_deep_refresh(s_detail_window); if (s_app_timer != NULL) { @@ -484,15 +541,23 @@ static void initialize(void) { if (persist_exists(COUNTDOWN_TIMER_ID_PERSIST_KEY)) { s_countdown_timer_id_max = persist_read_int(COUNTDOWN_TIMER_ID_PERSIST_KEY); } - // open the restored list with the most recently used timer on top - prv_sort_timers_by_recency(); + if (persist_exists(TIMER_SORT_MODE_PERSIST_KEY)) { + s_timer_sort_mode = persist_read_int(TIMER_SORT_MODE_PERSIST_KEY) ? 1 : 0; + } + if (s_timer_sort_mode == 0) { + // open the restored list with the most recently used timer on top + prv_sort_timers_by_recency(); + } // cancel wakeup wakeup_cancel_all(); + rebuild_timer_view_indices(); + // create menu window MenuWindowCallbacks menu_callbacks = { .get_timer = menu_window_get_timer_callback, .get_timer_count = menu_window_get_timer_count_callback, + .get_sort_mode = menu_window_get_sort_mode_callback, .clicked = menu_window_click_callback, }; s_menu_window = menu_window_create(menu_callbacks, true); diff --git a/src/menu_window.c b/src/menu_window.c index e9695f9..d2f4add 100644 --- a/src/menu_window.c +++ b/src/menu_window.c @@ -81,7 +81,10 @@ static uint16_t menu_get_num_sections_callback(MenuLayer *menu_layer, void *cont static uint16_t menu_get_num_rows_callback(MenuLayer *menu_layer, uint16_t section_index, void *context) { MenuWindow *menu_window = (MenuWindow*)context; - return menu_window->callbacks.get_timer_count(context) + 1; + const uint8_t timer_count = menu_window->callbacks.get_timer_count(context); + // rows: [0] "+" add, [1..timer_count] timers, [timer_count+1] sort toggle + // Hide the sort row unless there are at least two timers to reorder. + return (timer_count < 2) ? timer_count + 1 : timer_count + 2; } @@ -164,10 +167,18 @@ static void menu_draw_row_callback(GContext* ctx, const Layer *cell_layer, MenuI void *context) { // get properties MenuWindow *menu_window = (MenuWindow *) context; + const uint8_t timer_count = menu_window->callbacks.get_timer_count(context); // draw contents, with "+" in first cell if (cell_index->row == 0) { menu_cell_draw(ctx, cell_layer, "+", NULL, 0, fonts_get_system_font(FONT_KEY_GOTHIC_28), true, GColorBlack, GColorWhite); + } else if (cell_index->row == timer_count + 1) { + const uint8_t sort_mode = menu_window->callbacks.get_sort_mode ? + menu_window->callbacks.get_sort_mode(context) : 0; + const char *title = sort_mode ? "Sort: Duration" : "Sort: Created"; + menu_cell_draw(ctx, cell_layer, (char*)title, NULL, 0, + fonts_get_system_font(FONT_KEY_GOTHIC_24_BOLD), + true, GColorBlack, GColorWhite); } else { CountdownTimer *countdown_timer = menu_window->callbacks.get_timer(cell_index->row - 1, context); diff --git a/src/menu_window.h b/src/menu_window.h index 5bec82c..fb3bd82 100644 --- a/src/menu_window.h +++ b/src/menu_window.h @@ -48,6 +48,18 @@ typedef CountdownTimer* (*MenuWindowGetTimer)(uint8_t index, void *context); typedef uint8_t (*MenuWindowGetTimerCount)(void *context); +/* + * Callback: MenuWindowGetSortMode + * ---------------------------------- + * gets the current timer sorting mode for the menu list + * + * returns: + * 0 = created at (added order) + * 1 = duration (shortest to longest) + */ + +typedef uint8_t (*MenuWindowGetSortMode)(void *context); + /* @@ -69,6 +81,7 @@ typedef void (*MenuWindowClickCallback)(uint8_t index, void *context); typedef struct MenuWindowCallbacks { MenuWindowGetTimer get_timer; MenuWindowGetTimerCount get_timer_count; + MenuWindowGetSortMode get_sort_mode; MenuWindowClickCallback clicked; } MenuWindowCallbacks;