diff --git a/sphaira/include/ui/menus/appstore.hpp b/sphaira/include/ui/menus/appstore.hpp index 7bd3ecb..47f761f 100644 --- a/sphaira/include/ui/menus/appstore.hpp +++ b/sphaira/include/ui/menus/appstore.hpp @@ -108,8 +108,11 @@ private: std::shared_ptr m_details{}; std::shared_ptr m_changelog{}; std::shared_ptr m_detail_changelog{}; + std::unique_ptr m_manifest_list{}; bool m_show_changlog{}; + bool m_show_file_list{}; + ImageDownloadState m_file_list_state{}; }; enum Filter { diff --git a/sphaira/source/download.cpp b/sphaira/source/download.cpp index 8e2972d..7b5a754 100644 --- a/sphaira/source/download.cpp +++ b/sphaira/source/download.cpp @@ -332,7 +332,7 @@ struct ThreadQueue { } auto Add(const Api& api, bool is_upload = false) -> bool { - if (api.GetUrl().empty() || api.GetPath().empty() || !api.GetOnComplete()) { + if (api.GetUrl().empty() || !api.GetOnComplete()) { return false; } diff --git a/sphaira/source/ui/menus/appstore.cpp b/sphaira/source/ui/menus/appstore.cpp index 68106aa..c2b651f 100644 --- a/sphaira/source/ui/menus/appstore.cpp +++ b/sphaira/source/ui/menus/appstore.cpp @@ -16,6 +16,7 @@ #include "hasher.hpp" #include "threaded_file_transfer.hpp" #include "nro.hpp" +#include "web.hpp" #include #include @@ -144,6 +145,12 @@ auto BuildBannerUrl(const Entry& e) -> std::string { return out; } +auto BuildManifestUrl(const Entry& e) -> std::string { + char out[0x100]; + std::snprintf(out, sizeof(out), "%s/packages/%s/manifest.install", URL_BASE, e.name.c_str()); + return out; +} + auto BuildZipUrl(const Entry& e) -> std::string { char out[0x100]; std::snprintf(out, sizeof(out), "%s/zips/%s.zip", URL_BASE, e.name.c_str()); @@ -634,12 +641,15 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu) } }}), std::make_pair(Button::X, Action{"Options"_i18n, [this](){ - auto sidebar = std::make_shared("Options"_i18n, Sidebar::Side::RIGHT); - sidebar->Add(std::make_shared("More by Author"_i18n, [this](){ + auto options = std::make_shared("Options"_i18n, Sidebar::Side::RIGHT); + ON_SCOPE_EXIT(App::Push(options)); + + options->Add(std::make_shared("More by Author"_i18n, [this](){ m_menu.SetAuthor(); SetPop(); }, true)); - sidebar->Add(std::make_shared("Leave Feedback"_i18n, [this](){ + + options->Add(std::make_shared("Leave Feedback"_i18n, [this](){ std::string out; if (R_SUCCEEDED(swkbd::ShowText(out)) && !out.empty()) { const auto post = "name=" "switch_user" "&package=" + m_entry.name + "&message=" + out; @@ -660,10 +670,44 @@ EntryMenu::EntryMenu(Entry& entry, const LazyImage& default_icon, Menu& menu) }); } }, true)); - App::Push(sidebar); + + if (App::IsApplication() && !m_entry.url.empty()) { + options->Add(std::make_shared("Visit Website"_i18n, [this](){ + WebShow(m_entry.url); + })); + } }}), std::make_pair(Button::B, Action{"Back"_i18n, [this](){ SetPop(); + }}), + std::make_pair(Button::L2, Action{"Files"_i18n, [this](){ + m_show_file_list ^= 1; + + if (m_show_file_list && !m_manifest_list && m_file_list_state == ImageDownloadState::None) { + m_file_list_state = ImageDownloadState::Progress; + const auto path = BuildManifestCachePath(m_entry); + std::vector data; + + if (R_SUCCEEDED(fs::read_entire_file(path, data))) { + m_file_list_state = ImageDownloadState::Done; + data.push_back('\0'); + m_manifest_list = std::make_unique((const char*)data.data(), 0, 374, 250, 768, 18); + } else { + curl::Api().ToMemoryAsync( + curl::Url{BuildManifestUrl(m_entry)}, + curl::StopToken{this->GetToken()}, + curl::OnComplete{[this](auto& result){ + if (result.success) { + m_file_list_state = ImageDownloadState::Done; + result.data.push_back('\0'); + m_manifest_list = std::make_unique((const char*)result.data.data(), 0, 374, 250, 768, 18); + } else { + m_file_list_state = ImageDownloadState::Failed; + } + }} + ); + } + } }}) ); @@ -712,7 +756,13 @@ EntryMenu::~EntryMenu() { void EntryMenu::Update(Controller* controller, TouchInfo* touch) { MenuBase::Update(controller, touch); - m_detail_changelog->Update(controller, touch); + if (m_show_file_list) { + if (m_manifest_list) { + m_manifest_list->Update(controller, touch); + } + } else { + m_detail_changelog->Update(controller, touch); + } } void EntryMenu::Draw(NVGcontext* vg, Theme* theme) { @@ -767,12 +817,24 @@ void EntryMenu::Draw(NVGcontext* vg, Theme* theme) { y -= block.h + 18; } - m_detail_changelog->Draw(vg, theme); + if (m_show_file_list) { + if (m_manifest_list) { + m_manifest_list->Draw(vg, theme); + } else if (m_file_list_state == ImageDownloadState::Progress) { + gfx::drawText(vg, 110, 374, 18, theme->GetColour(ThemeEntryID_TEXT), "Loading..."_i18n.c_str()); + } else if (m_file_list_state == ImageDownloadState::Failed) { + gfx::drawText(vg, 110, 374, 18, theme->GetColour(ThemeEntryID_TEXT), "Failed to download manifest"_i18n.c_str()); + } + } else { + m_detail_changelog->Draw(vg, theme); + } } void EntryMenu::ShowChangelogAction() { std::function func = std::bind(&EntryMenu::ShowChangelogAction, this); m_show_changlog ^= 1; + m_show_file_list = false; + if (m_show_changlog) { SetAction(Button::L, Action{"Details"_i18n, func}); diff --git a/sphaira/source/ui/menus/menu_base.cpp b/sphaira/source/ui/menus/menu_base.cpp index a78fcbc..2c665f5 100644 --- a/sphaira/source/ui/menus/menu_base.cpp +++ b/sphaira/source/ui/menus/menu_base.cpp @@ -107,7 +107,7 @@ void MenuBase::Draw(NVGcontext* vg, Theme* theme) { gfx::drawTextArgs(vg, 80, start_y, 28.f, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT), m_title.c_str()); m_scroll_title_sub_heading.Draw(vg, true, title_sub_x, start_y, text_w - title_sub_x, 16, NVG_ALIGN_LEFT | NVG_ALIGN_BOTTOM, theme->GetColour(ThemeEntryID_TEXT_INFO), m_title_sub_heading.c_str()); - m_scroll_sub_heading.Draw(vg, true, 80, 685, text_w - 80, 18, NVG_ALIGN_LEFT, theme->GetColour(ThemeEntryID_TEXT), m_sub_heading.c_str()); + m_scroll_sub_heading.Draw(vg, true, 80, 685, text_w - 160, 18, NVG_ALIGN_LEFT, theme->GetColour(ThemeEntryID_TEXT), m_sub_heading.c_str()); } void MenuBase::SetTitle(std::string title) { diff --git a/sphaira/source/ui/nvg_util.cpp b/sphaira/source/ui/nvg_util.cpp index 9dfec9d..74f0ac0 100644 --- a/sphaira/source/ui/nvg_util.cpp +++ b/sphaira/source/ui/nvg_util.cpp @@ -296,8 +296,8 @@ void drawScrollbar(NVGcontext* vg, const Theme* theme, float x, float y, float h if (entry_total > max_entry_display) { const float sb_h = 1.f / (float)entry_total * h; const float sb_y = SCROLL; - gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), false); - gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(max_entry_display) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), false); + gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), 5); + gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(max_entry_display) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), 5); } } @@ -318,8 +318,8 @@ void drawScrollbar2(NVGcontext* vg, const Theme* theme, float x, float y, float if (count > page) { const float sb_h = 1.f / (float)count * h; const float sb_y = index_off; - gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), false); - gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(page) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), false); + gfx::drawRect(vg, x, y, scc2, h, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND), 5); + gfx::drawRect(vg, x + scw, y + scw + sb_h * sb_y, scc2 - scw * 2, sb_h * float(page) - scw * 2, theme->GetColour(ThemeEntryID_SCROLLBAR), 5); } } diff --git a/sphaira/source/ui/scrollable_text.cpp b/sphaira/source/ui/scrollable_text.cpp index 2a1e491..0e4a30f 100644 --- a/sphaira/source/ui/scrollable_text.cpp +++ b/sphaira/source/ui/scrollable_text.cpp @@ -87,16 +87,8 @@ void ScrollableText::Draw(NVGcontext* vg, Theme* theme) { // const Vec4 banner_vec(70, line_vec.y + 20, 848.f, 208.f); const Vec4 banner_vec(70, line_vec.y + 20, m_end_w + (110.0F), 208.f); - // only draw scrollbar if needed - if ((m_bounds[3] - m_bounds[1]) > m_clip_y) { - const auto scrollbar_size = m_clip_y; - const auto max_index = (m_bounds[3] - m_bounds[1]) / m_step; - const auto sb_h = 1.f / max_index * scrollbar_size; - const auto in_clip = m_clip_y / m_step - 1; - const auto sb_y = m_index; - gfx::drawRect(vg, banner_vec.w, m_y_off_base, 10, scrollbar_size, theme->GetColour(ThemeEntryID_SCROLLBAR_BACKGROUND)); - gfx::drawRect(vg, banner_vec.w+2, m_y_off_base + sb_h * sb_y, 10-4, sb_h + (sb_h * in_clip) - 4, theme->GetColour(ThemeEntryID_SCROLLBAR)); - } + const auto max_index = (m_bounds[3] - m_bounds[1]) / m_step; + gfx::drawScrollbar2(vg, theme, banner_vec.w + 25, m_y_off_base, m_clip_y, m_index, max_index, 1, m_clip_y / m_step - 1); nvgSave(vg); nvgIntersectScissor(vg, 0, m_y_off_base - m_font_size, 1280, m_clip_y + m_font_size); // clip