diff --git a/source/main.c b/source/main.c index 90e4d8d..7b0cff9 100644 --- a/source/main.c +++ b/source/main.c @@ -189,6 +189,7 @@ int main(int argc, char **argv) { cb_notifications notifications; cb_search search; cb_profile profile; + cb_thread thread; cb_wolfram_context_init(&wolfram); cb_auth_init(&auth); auth_backend = cb_wolfram_auth_backend(); @@ -196,13 +197,16 @@ int main(int argc, char **argv) { cb_notifications_backend notes_backend = cb_wolfram_notifications_backend(); cb_search_backend search_backend = cb_wolfram_search_backend(); cb_profile_backend profile_backend = cb_wolfram_profile_backend(); + cb_thread_backend threadb = cb_wolfram_thread_backend(); cb_timeline_init(&timeline); cb_compose_init(&compose, 0); cb_login_form_init(&login); cb_notifications_init(¬ifications); cb_search_init(&search); cb_profile_init(&profile); + cb_thread_init(&thread); nav_bind_timeline(&timeline, &compose, &backend, &wolfram); + nav_bind_thread(&thread, &threadb, &wolfram); nav_bind_discovery(¬ifications, &search, &profile, ¬es_backend, &search_backend, &profile_backend, &wolfram); nav_bind_auth(&auth, &login, &auth_backend, @@ -260,6 +264,7 @@ int main(int argc, char **argv) { cb_notifications_free(¬ifications); cb_search_free(&search); cb_profile_free(&profile); + cb_thread_free(&thread); cb_auth_free(&auth); cb_wolfram_context_free(&wolfram); if (network_init_done && network_init_status == WF_OK) diff --git a/source/navigation/nav.c b/source/navigation/nav.c index eb76c66..dc8f8ff 100644 --- a/source/navigation/nav.c +++ b/source/navigation/nav.c @@ -14,6 +14,7 @@ #include "../app/notifications.h" #include "../app/search.h" #include "../app/profile.h" +#include "../app/thread.h" static nav_state_t nav; static u8 nav_prev_tab; @@ -32,6 +33,9 @@ static const cb_notifications_backend *notifications_backend; static const cb_search_backend *search_backend; static const cb_profile_backend *profile_backend; static void *discovery_context; +static cb_thread *thread_ctrl; +static const cb_thread_backend *thread_backend; +static void *thread_context; static void draw_quad(f32 x, f32 y, f32 w, f32 h, GXColor col) { GX_SetChanMatColor(GX_COLOR0A0, col); @@ -111,22 +115,39 @@ static void render_feed(void) { } static void render_thread(void) { - const cb_post *post = cb_timeline_selected(feed); - GXColor author = {245, 245, 245, 255}; - GXColor body = {195, 195, 200, 255}; - draw_quad(0, CONTENT_Y_TOP, 640, CONTENT_HEIGHT, (GXColor){10, 12, 16, 255}); - if (!post) { - render_empty("This post is no longer available."); + size_t first; + size_t end; + size_t i; + GXColor normal = {18, 20, 26, 255}; + GXColor selected = {22, 56, 82, 255}; + GXColor author = {235, 235, 235, 255}; + GXColor body = {185, 185, 190, 255}; + GXColor counts = {115, 150, 175, 255}; + + if (!thread_ctrl || !thread_ctrl->loaded) { + render_empty("Loading thread. Press B to go back."); + return; + } + if (!thread_ctrl->count) { + render_empty("This thread is unavailable."); return; } - cb_avatar_draw(post->avatar_url, 24, CONTENT_Y_TOP + 24, 64, - (GXColor){255, 255, 255, 255}); - draw_clipped_text(100, CONTENT_Y_TOP + 28, - post->display_name ? post->display_name : post->author, 42, author); - draw_clipped_text(100, CONTENT_Y_TOP + 100, post->text, 56, body); - draw_clipped_text(24, CONTENT_Y_TOP + 112, - "A: Reply +: Follow 1: Like 2: Repost", 70, - (GXColor){100, 165, 210, 255}); + first = (thread_ctrl->selected / 4) * 4; + end = first + 4 < thread_ctrl->count ? first + 4 : thread_ctrl->count; + for (i = first; i < end; i++) { + const cb_post *post = &thread_ctrl->posts[i]; + f32 y = CONTENT_Y_TOP + (f32)(i - first) * 88.0f; + char meta[72]; + draw_quad(8, y + 3, 624, 82, i == thread_ctrl->selected ? selected : normal); + cb_avatar_draw(post->avatar_url, 18, y + 10, 64, + (GXColor){255, 255, 255, 255}); + draw_clipped_text(92, y + 10, post->display_name ? post->display_name : post->author, + 34, author); + draw_clipped_text(92, y + 34, post->text, 66, body); + snprintf(meta, sizeof(meta), "Replies %u Reposts %u Likes %u", + post->reply_count, post->repost_count, post->like_count); + draw_clipped_text(92, y + 59, meta, 58, counts); + } } static void render_compose(void) { @@ -375,6 +396,13 @@ void nav_bind_discovery(cb_notifications *notes, cb_search *search_ctrl, discovery_context = context; } +void nav_bind_thread(cb_thread *thread_c, const cb_thread_backend *backend, + void *context) { + thread_ctrl = thread_c; + thread_backend = backend; + thread_context = context; +} + static void submit_login(void) { if (!login_form || !authentication || !authentication_backend || !authentication_path) return; @@ -421,7 +449,7 @@ void nav_handle_input(u32 pressed) { cb_compose_submit(draft, feed, feed_backend, feed_context) == CB_APP_OK) nav_pop(); if (pressed & WPAD_BUTTON_B) nav_pop(); - } else if (screen == SCREEN_FEED || screen == SCREEN_THREAD) { + } else if (screen == SCREEN_FEED) { if (pressed & WPAD_BUTTON_UP) cb_timeline_move(feed, -1); if (pressed & WPAD_BUTTON_DOWN) cb_timeline_move(feed, 1); if (pressed & WPAD_BUTTON_1) @@ -433,20 +461,37 @@ void nav_handle_input(u32 pressed) { nav_push(SCREEN_COMPOSE); } if (pressed & WPAD_BUTTON_A && cb_timeline_selected(feed)) { - if (screen == SCREEN_FEED) nav_push(SCREEN_THREAD); - else { - cb_compose_init(draft, 1); - nav_push(SCREEN_COMPOSE); - } + const cb_post *picked = cb_timeline_selected(feed); + nav_push(SCREEN_THREAD); + if (thread_ctrl && thread_backend) + cb_thread_load(thread_ctrl, thread_backend, thread_context, + picked->uri); } - if (pressed & WPAD_BUTTON_PLUS && screen == SCREEN_FEED && feed && + if (pressed & WPAD_BUTTON_PLUS && feed && feed_backend && feed_backend->fetch_timeline) { if (feed->has_more) cb_timeline_load_more(feed, feed_backend, feed_context); else cb_timeline_refresh(feed, feed_backend, feed_context); cb_avatar_prefetch_feed(feed, (cb_wolfram_context *)feed_context); } - if (pressed & WPAD_BUTTON_PLUS && screen == SCREEN_THREAD) - cb_timeline_follow_selected(feed, feed_backend, feed_context); + if (pressed & WPAD_BUTTON_B) nav_pop(); + } else if (screen == SCREEN_THREAD) { + if (pressed & WPAD_BUTTON_UP) cb_thread_move(thread_ctrl, -1); + if (pressed & WPAD_BUTTON_DOWN) cb_thread_move(thread_ctrl, 1); + if (pressed & WPAD_BUTTON_1) + cb_thread_like_selected(thread_ctrl, thread_backend, thread_context); + if (pressed & WPAD_BUTTON_2) + cb_thread_repost_selected(thread_ctrl, thread_backend, thread_context); + if (pressed & WPAD_BUTTON_MINUS) { + cb_compose_init(draft, 0); + nav_push(SCREEN_COMPOSE); + } + if (pressed & WPAD_BUTTON_A && cb_thread_selected(thread_ctrl)) { + cb_compose_init(draft, 1); + nav_push(SCREEN_COMPOSE); + } + if (pressed & WPAD_BUTTON_PLUS && thread_ctrl && thread_backend && + thread_ctrl->root_uri) + cb_thread_follow_selected(thread_ctrl, thread_backend, thread_context); if (pressed & WPAD_BUTTON_B) nav_pop(); } else if (screen == SCREEN_NOTIFICATIONS) { if (pressed & WPAD_BUTTON_UP) cb_notifications_move(notifications, -1); diff --git a/source/navigation/nav.h b/source/navigation/nav.h index 81f0bc3..bce081f 100644 --- a/source/navigation/nav.h +++ b/source/navigation/nav.h @@ -15,6 +15,7 @@ #include "../app/notifications.h" #include "../app/search.h" #include "../app/profile.h" +#include "../app/thread.h" /* layout constants (pixels) */ #define TAB_BAR_HEIGHT 40 @@ -60,6 +61,12 @@ void nav_bind_discovery(cb_notifications *notes, cb_search *search_ctrl, const cb_profile_backend *profile_backend, void *context); +/* Bind the conversation/thread controller and its transport adapter. The + * pointed-to objects must outlive the navigation loop. The context is the + * shared wolfram context already bound to the timeline. */ +void nav_bind_thread(cb_thread *thread_ctrl, const cb_thread_backend *backend, + void *context); + /* handle one frame of input; called before render */ void nav_handle_input(u32 pressed);