mirror of
https://git.wetfence.xyz/RandyTheSilly/jellyPod.git
synced 2026-10-26 23:39:56 -04:00
400 lines
13 KiB
C
400 lines
13 KiB
C
#include "custom_status_bar.h"
|
|
#include "sdk_mod.h"
|
|
#include <pebble.h>
|
|
|
|
typedef enum {
|
|
NAV_SCOPE_MAIN = 0,
|
|
NAV_SCOPE_SEARCH = 1,
|
|
NAV_SCOPE_ARTISTS = 2,
|
|
NAV_SCOPE_ALBUMS = 3,
|
|
NAV_SCOPE_ALBUM_TRACKS = 4,
|
|
} NavScope;
|
|
|
|
// TODO track last selected artist and album to restore when going deeper again
|
|
// TODO write to disk to restore exit point on re-open
|
|
typedef struct {
|
|
bool using_search;
|
|
char last_subscope[NAV_SCOPE_ALBUM_TRACKS + 1][255]; // subscope each scope was last entered with
|
|
uint16_t scroll_row[NAV_SCOPE_ALBUM_TRACKS + 1]; // last selected row per scope
|
|
} History;
|
|
|
|
// constants
|
|
#define MAX_ARTISTS 2048 // TODO paginate or push as high as possible
|
|
#define MAX_TRACKS_PER_ALBUM 255
|
|
#define BAR_HEIGHT 20
|
|
#ifdef PBL_RECT
|
|
#define MARGIN_X 4
|
|
#define MARGIN_Y 4
|
|
#else
|
|
#define MARGIN_X 0
|
|
#if PBL_DISPLAY_WIDTH < 260
|
|
#define MARGIN_Y 4
|
|
#else
|
|
#define MARGIN_Y 5
|
|
#endif
|
|
#endif
|
|
static const GRect s_menu_grect = GRect(MARGIN_X, BAR_HEIGHT + MARGIN_Y, PBL_DISPLAY_WIDTH - (MARGIN_X * 2), PBL_DISPLAY_HEIGHT - (MARGIN_Y * 2));
|
|
static const char *s_main_menu_options[4] = {"Shuffle Songs", "Shuffle Artists", "> All Artists", "> Search Artists"};
|
|
|
|
// layer-age
|
|
static Window *s_main_window;
|
|
static CustomStatusBarLayer *s_status_bar_layer;
|
|
static Layer *s_divider_layer;
|
|
static MenuLayer *s_menu_layer;
|
|
static ClickConfigProvider s_base_click_config_provider;
|
|
static void *s_base_click_config_context;
|
|
|
|
// globals
|
|
static bool s_pkjs_ready = false;
|
|
static History s_nav_history;
|
|
static NavScope s_current_menu_scope = NAV_SCOPE_MAIN;
|
|
static char s_pending_subscope[255];
|
|
static const char *s_current_menu_vanity[MAX_ARTISTS];
|
|
static uint16_t s_current_menu_seq_ids[MAX_TRACKS_PER_ALBUM];
|
|
static uint16_t s_menu_items_count = 0;
|
|
|
|
// resources
|
|
static GFont s_font_chicago;
|
|
|
|
static const char *get_current_scope_subscope() {
|
|
switch (s_current_menu_scope) {
|
|
case NAV_SCOPE_ARTISTS:
|
|
case NAV_SCOPE_ALBUMS:
|
|
case NAV_SCOPE_ALBUM_TRACKS:
|
|
return s_pending_subscope;
|
|
default:
|
|
return "";
|
|
}
|
|
}
|
|
|
|
////////////////////// PKJS //////////////////////
|
|
|
|
static void outbox_failed_handler(DictionaryIterator *iter, AppMessageResult reason, void *context) {
|
|
APP_LOG(APP_LOG_LEVEL_ERROR, "outbox failed: %d", (int)reason);
|
|
}
|
|
|
|
static void inbox_received_handler(DictionaryIterator *iter, void *context) {
|
|
Tuple *t_ready = dict_find(iter, MESSAGE_KEY_PKJS_READY);
|
|
if (t_ready) {
|
|
s_pkjs_ready = (bool)t_ready->value->uint8;
|
|
APP_LOG(APP_LOG_LEVEL_DEBUG, "PKJS READY STATE CHANGE: %d", s_pkjs_ready);
|
|
return;
|
|
}
|
|
|
|
// handle incoming menu list items
|
|
Tuple *t_new_scope = dict_find(iter, MESSAGE_KEY_BIDIR_MENU_NAV_SCOPE);
|
|
if (t_new_scope) {
|
|
for (uint16_t i = 0; i < MAX_ARTISTS; i++) {
|
|
Tuple *t_seq_ids = dict_find(iter, MESSAGE_KEY_IN_SEQ_ID_LIST + i);
|
|
if (t_seq_ids) {
|
|
s_current_menu_seq_ids[i] = t_seq_ids->value->uint16;
|
|
}
|
|
Tuple *t_vanity = dict_find(iter, MESSAGE_KEY_IN_VANITY_LIST + i);
|
|
if (t_vanity) {
|
|
s_current_menu_vanity[i] = t_vanity->value->cstring;
|
|
} else {
|
|
s_menu_items_count = i;
|
|
break;
|
|
}
|
|
}
|
|
NavScope last_menu_scope = s_current_menu_scope;
|
|
s_current_menu_scope = t_new_scope->value->uint8;
|
|
bool moving_deeper = true;
|
|
if (s_current_menu_scope - last_menu_scope < 0) {
|
|
moving_deeper = false;
|
|
}
|
|
if (moving_deeper && last_menu_scope == NAV_SCOPE_SEARCH) {
|
|
s_nav_history.using_search = true; // set when actually selecting an item from the search menu
|
|
} else if (!moving_deeper && s_current_menu_scope == NAV_SCOPE_SEARCH) {
|
|
s_nav_history.using_search = false; // reset when returning to search menu
|
|
}
|
|
// restore the row the user left off on (unless moving deeper into a different subscope)
|
|
uint16_t scroll_row = s_nav_history.scroll_row[s_current_menu_scope];
|
|
if (moving_deeper) {
|
|
const char *subscope = get_current_scope_subscope();
|
|
if (strcmp(subscope, s_nav_history.last_subscope[s_current_menu_scope]) != 0) {
|
|
scroll_row = 0;
|
|
snprintf(s_nav_history.last_subscope[s_current_menu_scope], sizeof(s_nav_history.last_subscope[s_current_menu_scope]), "%s", subscope);
|
|
}
|
|
}
|
|
s_nav_history.scroll_row[s_current_menu_scope] = scroll_row;
|
|
menu_layer_reload_data(s_menu_layer);
|
|
menu_layer_set_selected_index(s_menu_layer, MenuIndex(0, scroll_row), MenuRowAlignTop, false);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void to_pkjs_play_from_track(uint16_t seqId) {
|
|
if (!s_pkjs_ready) {
|
|
return;
|
|
}
|
|
|
|
DictionaryIterator *out;
|
|
AppMessageResult result = app_message_outbox_begin(&out);
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
dict_write_uint16(out, MESSAGE_KEY_OUT_PLAY_SEQ_ID, seqId);
|
|
|
|
result = app_message_outbox_send();
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void to_pkjs_shuffle_in_scope(NavScope scope, bool use_subscope, bool use_groups) {
|
|
if (!s_pkjs_ready) {
|
|
return;
|
|
}
|
|
|
|
DictionaryIterator *out;
|
|
AppMessageResult result = app_message_outbox_begin(&out);
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
if (use_subscope) {
|
|
dict_write_cstring(out, MESSAGE_KEY_OUT_MENU_NAV_SUBSCOPE, s_nav_history.last_subscope[scope]);
|
|
}
|
|
dict_write_uint8(out, MESSAGE_KEY_BIDIR_MENU_NAV_SCOPE, scope);
|
|
dict_write_uint8(out, MESSAGE_KEY_OUT_SHUFFLE_MODE, use_groups ? 2 : 1);
|
|
|
|
result = app_message_outbox_send();
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void to_pkjs_move_deeper(NavScope scope, uint16_t *subscope_index) {
|
|
if (!s_pkjs_ready) {
|
|
return;
|
|
}
|
|
|
|
DictionaryIterator *out;
|
|
AppMessageResult result = app_message_outbox_begin(&out);
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
|
|
const char *subscope = subscope_index ? s_current_menu_vanity[*subscope_index] : "";
|
|
snprintf(s_pending_subscope, sizeof(s_pending_subscope), "%s", subscope);
|
|
if (subscope_index) {
|
|
dict_write_cstring(out, MESSAGE_KEY_OUT_MENU_NAV_SUBSCOPE, subscope);
|
|
}
|
|
dict_write_uint8(out, MESSAGE_KEY_BIDIR_MENU_NAV_SCOPE, scope);
|
|
|
|
result = app_message_outbox_send();
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void to_pkjs_move_back() {
|
|
if (!s_pkjs_ready) {
|
|
return;
|
|
}
|
|
|
|
DictionaryIterator *out;
|
|
AppMessageResult result = app_message_outbox_begin(&out);
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
|
|
if ((s_current_menu_scope == NAV_SCOPE_ARTISTS && !s_nav_history.using_search) || s_current_menu_scope == NAV_SCOPE_SEARCH) {
|
|
// return to main menu
|
|
s_current_menu_scope = NAV_SCOPE_MAIN;
|
|
menu_layer_reload_data(s_menu_layer);
|
|
menu_layer_set_selected_index(s_menu_layer, MenuIndex(0, s_nav_history.scroll_row[NAV_SCOPE_MAIN]), MenuRowAlignTop, false);
|
|
} else {
|
|
// otherwise determine last subscope and go to previous scope
|
|
switch (s_current_menu_scope) {
|
|
case NAV_SCOPE_ARTISTS: // will only run if search is enabled
|
|
dict_write_uint8(out, MESSAGE_KEY_BIDIR_MENU_NAV_SCOPE, NAV_SCOPE_SEARCH);
|
|
break;
|
|
case NAV_SCOPE_ALBUMS:
|
|
if (s_nav_history.using_search) {
|
|
dict_write_cstring(out, MESSAGE_KEY_OUT_MENU_NAV_SUBSCOPE, s_nav_history.last_subscope[NAV_SCOPE_ARTISTS]);
|
|
}
|
|
dict_write_uint8(out, MESSAGE_KEY_BIDIR_MENU_NAV_SCOPE, NAV_SCOPE_ARTISTS);
|
|
break;
|
|
case NAV_SCOPE_ALBUM_TRACKS:
|
|
dict_write_cstring(out, MESSAGE_KEY_OUT_MENU_NAV_SUBSCOPE, s_nav_history.last_subscope[NAV_SCOPE_ALBUMS]);
|
|
dict_write_uint8(out, MESSAGE_KEY_BIDIR_MENU_NAV_SCOPE, NAV_SCOPE_ALBUMS);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
result = app_message_outbox_send();
|
|
if (result != APP_MSG_OK) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
////////////////////// MENU LAYER CALLBACKS //////////////////////
|
|
|
|
static uint16_t menu_get_num_rows(MenuLayer *menu_layer, uint16_t section_index, void *context) {
|
|
switch (s_current_menu_scope) {
|
|
case NAV_SCOPE_MAIN:
|
|
return 4;
|
|
case NAV_SCOPE_ALBUMS:
|
|
return s_menu_items_count + 2;
|
|
default:
|
|
return s_menu_items_count;
|
|
}
|
|
}
|
|
|
|
static void menu_draw_row(GContext *ctx, const Layer *cell_layer, MenuIndex *cell_index, void *context) {
|
|
switch (s_current_menu_scope) {
|
|
case NAV_SCOPE_MAIN:
|
|
custom_menu_cell_title_draw(ctx, cell_layer, s_main_menu_options[cell_index->row], NULL, s_font_chicago, MARGIN_X);
|
|
break;
|
|
case NAV_SCOPE_ALBUMS:
|
|
switch (cell_index->row) {
|
|
case 0:
|
|
custom_menu_cell_title_draw(ctx, cell_layer, "Shuffle Songs", NULL, s_font_chicago, MARGIN_X);
|
|
break;
|
|
case 1:
|
|
custom_menu_cell_title_draw(ctx, cell_layer, "Shuffle Albums", NULL, s_font_chicago, MARGIN_X);
|
|
break;
|
|
default:
|
|
custom_menu_cell_title_draw(ctx, cell_layer, s_current_menu_vanity[cell_index->row - 2], NULL, s_font_chicago, MARGIN_X);
|
|
}
|
|
break;
|
|
default:
|
|
custom_menu_cell_title_draw(ctx, cell_layer, s_current_menu_vanity[cell_index->row], NULL, s_font_chicago, MARGIN_X);
|
|
}
|
|
}
|
|
|
|
static void menu_select_click(struct MenuLayer *menu_layer, MenuIndex *clicked_index, void *context) {
|
|
s_nav_history.scroll_row[s_current_menu_scope] = clicked_index->row;
|
|
switch (s_current_menu_scope) {
|
|
case NAV_SCOPE_MAIN:
|
|
switch (clicked_index->row) {
|
|
case 0:
|
|
to_pkjs_shuffle_in_scope(s_current_menu_scope, false, false);
|
|
break;
|
|
case 1:
|
|
to_pkjs_shuffle_in_scope(s_current_menu_scope, false, true);
|
|
break;
|
|
case 2:
|
|
to_pkjs_move_deeper(NAV_SCOPE_ARTISTS, NULL);
|
|
break;
|
|
default:
|
|
to_pkjs_move_deeper(NAV_SCOPE_SEARCH, NULL);
|
|
}
|
|
return;
|
|
case NAV_SCOPE_SEARCH:
|
|
case NAV_SCOPE_ARTISTS:
|
|
to_pkjs_move_deeper(s_current_menu_scope + 1, &clicked_index->row);
|
|
break;
|
|
case NAV_SCOPE_ALBUMS:
|
|
switch (clicked_index->row) {
|
|
case 0:
|
|
to_pkjs_shuffle_in_scope(s_current_menu_scope, true, false);
|
|
break;
|
|
case 1:
|
|
to_pkjs_shuffle_in_scope(s_current_menu_scope, true, true);
|
|
break;
|
|
default: {
|
|
uint16_t album_row = clicked_index->row - 2;
|
|
to_pkjs_move_deeper(s_current_menu_scope + 1, &album_row);
|
|
}
|
|
}
|
|
break;
|
|
case NAV_SCOPE_ALBUM_TRACKS:
|
|
to_pkjs_play_from_track(s_current_menu_seq_ids[clicked_index->row]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void menu_selection_changed(struct MenuLayer *menu_layer, MenuIndex new_index, MenuIndex old_index, void *context) {
|
|
speaker_play_tone(200, 10, 80, SpeakerWaveformSine);
|
|
}
|
|
|
|
static void menu_back_click_handler(ClickRecognizerRef recognizer, void *context) {
|
|
if (s_current_menu_scope == NAV_SCOPE_MAIN) {
|
|
window_stack_pop(true);
|
|
} else {
|
|
to_pkjs_move_back();
|
|
}
|
|
}
|
|
|
|
static void menu_click_config_provider(void *context) {
|
|
s_base_click_config_provider(s_base_click_config_context);
|
|
window_single_click_subscribe(BUTTON_ID_BACK, menu_back_click_handler);
|
|
}
|
|
|
|
////////////////////// UI //////////////////////
|
|
|
|
static void divider_layer_update_proc(Layer *layer, GContext *ctx) {
|
|
graphics_context_set_fill_color(ctx, GColorBlack);
|
|
graphics_fill_rect(ctx, layer_get_bounds(layer), 0, GCornerNone);
|
|
}
|
|
|
|
static void main_window_load() {
|
|
Layer *main_window_layer = window_get_root_layer(s_main_window);
|
|
|
|
s_menu_layer = menu_layer_create(s_menu_grect);
|
|
menu_layer_set_callbacks(s_menu_layer, NULL, (MenuLayerCallbacks){.get_num_rows = menu_get_num_rows, .draw_row = menu_draw_row, .select_click = menu_select_click, .selection_changed = menu_selection_changed});
|
|
menu_layer_set_click_config_onto_window(s_menu_layer, s_main_window);
|
|
s_base_click_config_provider = window_get_click_config_provider(s_main_window);
|
|
s_base_click_config_context = window_get_click_config_context(s_main_window);
|
|
window_set_click_config_provider_with_context(s_main_window, menu_click_config_provider, s_base_click_config_context);
|
|
|
|
#ifdef PBL_ROUND
|
|
menu_layer_set_center_focused(s_menu_layer, false);
|
|
#endif
|
|
layer_add_child(main_window_layer, menu_layer_get_layer(s_menu_layer));
|
|
|
|
s_status_bar_layer = custom_status_bar_layer_create(BAR_HEIGHT, GColorWhite, 15);
|
|
custom_status_bar_layer_set_text(s_status_bar_layer, CSB_TEXT_CENTER, "jellyPod");
|
|
custom_status_bar_layer_set_text_font(s_status_bar_layer, CSB_TEXT_CENTER, s_font_chicago);
|
|
#ifdef PBL_RECT
|
|
custom_status_bar_layer_set_text(s_status_bar_layer, CSB_TEXT_LEFT, " | |");
|
|
custom_status_bar_layer_set_text(s_status_bar_layer, CSB_TEXT_RIGHT, "[// ]- ");
|
|
#endif
|
|
layer_add_child(main_window_layer, s_status_bar_layer);
|
|
|
|
s_divider_layer = layer_create(GRect(MARGIN_X, BAR_HEIGHT + (MARGIN_Y / 2), PBL_DISPLAY_WIDTH - (MARGIN_X * 2), 1));
|
|
layer_set_update_proc(s_divider_layer, divider_layer_update_proc);
|
|
layer_add_child(main_window_layer, s_divider_layer);
|
|
}
|
|
|
|
static void main_window_unload() {
|
|
layer_destroy(s_divider_layer);
|
|
custom_status_bar_layer_destroy(s_status_bar_layer);
|
|
menu_layer_destroy(s_menu_layer);
|
|
}
|
|
|
|
////////////////////// SETUP //////////////////////
|
|
|
|
static void init() {
|
|
// initialize pkjs
|
|
app_message_register_inbox_received(inbox_received_handler);
|
|
app_message_register_outbox_failed(outbox_failed_handler);
|
|
app_message_open(app_message_inbox_size_maximum(), 255);
|
|
|
|
// load font
|
|
s_font_chicago = fonts_load_custom_font(resource_get_handle(RESOURCE_ID_FONT_CHICAGO_16));
|
|
|
|
// create and push window
|
|
s_main_window = window_create();
|
|
window_set_window_handlers(
|
|
s_main_window,
|
|
(WindowHandlers){.load = main_window_load, .unload = main_window_unload});
|
|
window_stack_push(s_main_window, false);
|
|
app_touch_navigation_enable(true);
|
|
}
|
|
|
|
static void deinit() {
|
|
window_destroy(s_main_window);
|
|
fonts_unload_custom_font(s_font_chicago);
|
|
}
|
|
|
|
int main(void) {
|
|
init();
|
|
app_event_loop();
|
|
deinit();
|
|
}
|