themezer: prompt user to download ThemeInjector if not installed on launch.
This commit is contained in:
@@ -69,12 +69,24 @@ private:
|
|||||||
void Sort();
|
void Sort();
|
||||||
void UpdateSubheading();
|
void UpdateSubheading();
|
||||||
|
|
||||||
void DownloadEntries();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Entry> m_entries{};
|
std::vector<Entry> m_entries{};
|
||||||
s64 m_index{};
|
s64 m_index{};
|
||||||
std::unique_ptr<List> m_list{};
|
std::unique_ptr<List> m_list{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// creates a popup box on another thread.
|
||||||
|
void DownloadEntries(const Entry& entry);
|
||||||
|
|
||||||
|
// parses the params into entry struct and calls DonwloadEntries
|
||||||
|
bool Download(const std::string& url, const std::vector<AssetEntry>& assets = {}, const std::string& pre_install_message = {}, const std::string& post_install_message = {});
|
||||||
|
|
||||||
|
// calls the above function by pushing the asset to an array.
|
||||||
|
inline bool Download(const std::string& url, const AssetEntry& asset, const std::string& pre_install_message = {}, const std::string& post_install_message = {}) {
|
||||||
|
std::vector<AssetEntry> assets;
|
||||||
|
assets.emplace_back(asset);
|
||||||
|
|
||||||
|
return Download(url, assets, pre_install_message, post_install_message);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::gh
|
} // namespace sphaira::ui::menu::gh
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ private:
|
|||||||
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
||||||
option::OptionLong m_order{INI_SECTION, "order", 0};
|
option::OptionLong m_order{INI_SECTION, "order", 0};
|
||||||
option::OptionBool m_nsfw{INI_SECTION, "nsfw", false};
|
option::OptionBool m_nsfw{INI_SECTION, "nsfw", false};
|
||||||
|
|
||||||
|
bool m_checked_for_nro{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::themezer
|
} // namespace sphaira::ui::menu::themezer
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ Menu::Menu(u32 flags) : MenuBase{"GitHub"_i18n, flags} {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DownloadEntries();
|
DownloadEntries(GetEntry());
|
||||||
}}),
|
}}),
|
||||||
|
|
||||||
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
std::make_pair(Button::B, Action{"Back"_i18n, [this](){
|
||||||
@@ -341,14 +341,14 @@ void Menu::UpdateSubheading() {
|
|||||||
this->SetSubHeading(std::to_string(index) + " / " + std::to_string(m_entries.size()));
|
this->SetSubHeading(std::to_string(index) + " / " + std::to_string(m_entries.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::DownloadEntries() {
|
void DownloadEntries(const Entry& entry) {
|
||||||
// hack
|
// hack
|
||||||
static std::vector<GhApiEntry> gh_entries;
|
static std::vector<GhApiEntry> gh_entries;
|
||||||
gh_entries = {};
|
gh_entries = {};
|
||||||
|
|
||||||
App::Push<ProgressBox>(0, "Downloading "_i18n, GetEntry().repo, [this](auto pbox) -> Result {
|
App::Push<ProgressBox>(0, "Downloading "_i18n, entry.repo, [entry](auto pbox) -> Result {
|
||||||
return DownloadReleaseJsonJson(pbox, GenerateApiUrl(GetEntry()), gh_entries);
|
return DownloadReleaseJsonJson(pbox, GenerateApiUrl(entry), gh_entries);
|
||||||
}, [this](Result rc){
|
}, [entry](Result rc){
|
||||||
App::PushErrorBox(rc, "Failed to download json"_i18n);
|
App::PushErrorBox(rc, "Failed to download json"_i18n);
|
||||||
if (R_FAILED(rc) || gh_entries.empty()) {
|
if (R_FAILED(rc) || gh_entries.empty()) {
|
||||||
return;
|
return;
|
||||||
@@ -370,13 +370,13 @@ void Menu::DownloadEntries() {
|
|||||||
entry_items.emplace_back(str);
|
entry_items.emplace_back(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
App::Push<PopupList>("Select release to download for "_i18n + GetEntry().repo, entry_items, [this](auto op_index){
|
App::Push<PopupList>("Select release to download for "_i18n + entry.repo, entry_items, [entry](auto op_index){
|
||||||
if (!op_index) {
|
if (!op_index) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& gh_entry = gh_entries[*op_index];
|
const auto& gh_entry = gh_entries[*op_index];
|
||||||
const auto& assets = GetEntry().assets;
|
const auto& assets = entry.assets;
|
||||||
PopupList::Items asset_items;
|
PopupList::Items asset_items;
|
||||||
std::vector<const AssetEntry*> asset_ptr;
|
std::vector<const AssetEntry*> asset_ptr;
|
||||||
std::vector<GhApiAsset> api_assets;
|
std::vector<GhApiAsset> api_assets;
|
||||||
@@ -406,7 +406,7 @@ void Menu::DownloadEntries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
App::Push<PopupList>("Select asset to download for "_i18n + GetEntry().repo, asset_items, [this, api_assets, asset_ptr](auto op_index){
|
App::Push<PopupList>("Select asset to download for "_i18n + entry.repo, asset_items, [entry, api_assets, asset_ptr](auto op_index){
|
||||||
if (!op_index) {
|
if (!op_index) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -414,7 +414,7 @@ void Menu::DownloadEntries() {
|
|||||||
const auto index = *op_index;
|
const auto index = *op_index;
|
||||||
const auto& asset_entry = api_assets[index];
|
const auto& asset_entry = api_assets[index];
|
||||||
const AssetEntry* ptr{};
|
const AssetEntry* ptr{};
|
||||||
auto pre_install_message = GetEntry().pre_install_message;
|
auto pre_install_message = entry.pre_install_message;
|
||||||
if (asset_ptr.size()) {
|
if (asset_ptr.size()) {
|
||||||
ptr = asset_ptr[index];
|
ptr = asset_ptr[index];
|
||||||
if (!ptr->pre_install_message.empty()) {
|
if (!ptr->pre_install_message.empty()) {
|
||||||
@@ -422,16 +422,16 @@ void Menu::DownloadEntries() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto func = [this, &asset_entry, ptr](){
|
const auto func = [entry, &asset_entry, ptr](){
|
||||||
App::Push<ProgressBox>(0, "Downloading "_i18n, GetEntry().repo, [this, &asset_entry, ptr](auto pbox) -> Result {
|
App::Push<ProgressBox>(0, "Downloading "_i18n, entry.repo, [entry, &asset_entry, ptr](auto pbox) -> Result {
|
||||||
return DownloadApp(pbox, asset_entry, ptr);
|
return DownloadApp(pbox, asset_entry, ptr);
|
||||||
}, [this, ptr](Result rc){
|
}, [entry, ptr](Result rc){
|
||||||
homebrew::SignalChange();
|
homebrew::SignalChange();
|
||||||
App::PushErrorBox(rc, "Failed to download app!"_i18n);
|
App::PushErrorBox(rc, "Failed to download app!"_i18n);
|
||||||
|
|
||||||
if (R_SUCCEEDED(rc)) {
|
if (R_SUCCEEDED(rc)) {
|
||||||
App::Notify("Downloaded "_i18n + GetEntry().repo);
|
App::Notify("Downloaded "_i18n + entry.repo);
|
||||||
auto post_install_message = GetEntry().post_install_message;
|
auto post_install_message = entry.post_install_message;
|
||||||
if (ptr && !ptr->post_install_message.empty()) {
|
if (ptr && !ptr->post_install_message.empty()) {
|
||||||
post_install_message = ptr->post_install_message;
|
post_install_message = ptr->post_install_message;
|
||||||
}
|
}
|
||||||
@@ -446,7 +446,7 @@ void Menu::DownloadEntries() {
|
|||||||
if (!pre_install_message.empty()) {
|
if (!pre_install_message.empty()) {
|
||||||
App::Push<OptionBox>(
|
App::Push<OptionBox>(
|
||||||
pre_install_message,
|
pre_install_message,
|
||||||
"Back"_i18n, "Download"_i18n, 1, [this, func](auto op_index){
|
"Back"_i18n, "Download"_i18n, 1, [entry, func](auto op_index){
|
||||||
if (op_index && *op_index) {
|
if (op_index && *op_index) {
|
||||||
func();
|
func();
|
||||||
}
|
}
|
||||||
@@ -460,4 +460,30 @@ void Menu::DownloadEntries() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Download(const std::string& url, const std::vector<AssetEntry>& assets, const std::string& pre_install_message, const std::string& post_install_message) {
|
||||||
|
Entry entry{};
|
||||||
|
entry.url = url;
|
||||||
|
entry.assets = assets;
|
||||||
|
entry.pre_install_message = pre_install_message;
|
||||||
|
entry.post_install_message = post_install_message;
|
||||||
|
|
||||||
|
// parse owner and author from url (if needed).
|
||||||
|
if (!entry.url.empty()) {
|
||||||
|
const auto s = entry.url.substr(std::strlen("https://github.com/"));
|
||||||
|
const auto it = s.find('/');
|
||||||
|
if (it != s.npos) {
|
||||||
|
entry.owner = s.substr(0, it);
|
||||||
|
entry.repo = s.substr(it + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check that we have a owner and repo
|
||||||
|
if (entry.owner.empty() || entry.repo.empty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DownloadEntries(entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace sphaira::ui::menu::gh
|
} // namespace sphaira::ui::menu::gh
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
#include "ui/menus/themezer.hpp"
|
#include "ui/menus/themezer.hpp"
|
||||||
|
#include "ui/menus/ghdl.hpp"
|
||||||
#include "ui/progress_box.hpp"
|
#include "ui/progress_box.hpp"
|
||||||
#include "ui/option_box.hpp"
|
#include "ui/option_box.hpp"
|
||||||
#include "ui/sidebar.hpp"
|
#include "ui/sidebar.hpp"
|
||||||
@@ -29,6 +30,13 @@ constexpr fs::FsPath THEME_FOLDER{"/themes/sphaira/"};
|
|||||||
constexpr auto CACHE_PATH = "/switch/sphaira/cache/themezer";
|
constexpr auto CACHE_PATH = "/switch/sphaira/cache/themezer";
|
||||||
constexpr auto URL_BASE = "https://switch.cdn.fortheusers.org";
|
constexpr auto URL_BASE = "https://switch.cdn.fortheusers.org";
|
||||||
|
|
||||||
|
constexpr const char* NRO_URL = "https://github.com/exelix11/SwitchThemeInjector";
|
||||||
|
|
||||||
|
constexpr const char* NRO_PATHS[]{
|
||||||
|
"/switch/NXThemesInstaller.nro",
|
||||||
|
"/switch/Switch_themes_Installer/NXThemesInstaller.nro",
|
||||||
|
};
|
||||||
|
|
||||||
constexpr const char* REQUEST_TARGET[]{
|
constexpr const char* REQUEST_TARGET[]{
|
||||||
"ResidentMenu",
|
"ResidentMenu",
|
||||||
"Entrance",
|
"Entrance",
|
||||||
@@ -54,6 +62,17 @@ constexpr const char* REQUEST_ORDER[]{
|
|||||||
// https://api.themezer.net/?query=query($nsfw:Boolean,$target:String,$page:Int,$limit:Int,$sort:String,$order:String,$query:String,$creators:[String!]){themeList(nsfw:$nsfw,target:$target,page:$page,limit:$limit,sort:$sort,order:$order,query:$query,creators:$creators){id,creator{id,display_name},details{name,description},last_updated,dl_count,like_count,target,preview{original,thumb}}}&variables={"nsfw":false,"target":null,"page":1,"limit":10,"sort":"updated","order":"desc","query":null,"creators":["695065006068334622"]}
|
// https://api.themezer.net/?query=query($nsfw:Boolean,$target:String,$page:Int,$limit:Int,$sort:String,$order:String,$query:String,$creators:[String!]){themeList(nsfw:$nsfw,target:$target,page:$page,limit:$limit,sort:$sort,order:$order,query:$query,creators:$creators){id,creator{id,display_name},details{name,description},last_updated,dl_count,like_count,target,preview{original,thumb}}}&variables={"nsfw":false,"target":null,"page":1,"limit":10,"sort":"updated","order":"desc","query":null,"creators":["695065006068334622"]}
|
||||||
// https://api.themezer.net/?query=query($nsfw:Boolean,$page:Int,$limit:Int,$sort:String,$order:String,$query:String,$creators:[String!]){packList(nsfw:$nsfw,page:$page,limit:$limit,sort:$sort,order:$order,query:$query,creators:$creators){id,creator{id,display_name},details{name,description},last_updated,dl_count,like_count,themes{id,creator{display_name},details{name,description},last_updated,dl_count,like_count,target,preview{original,thumb}}}}&variables={"nsfw":false,"page":1,"limit":10,"sort":"updated","order":"desc","query":null,"creators":["695065006068334622"]}
|
// https://api.themezer.net/?query=query($nsfw:Boolean,$page:Int,$limit:Int,$sort:String,$order:String,$query:String,$creators:[String!]){packList(nsfw:$nsfw,page:$page,limit:$limit,sort:$sort,order:$order,query:$query,creators:$creators){id,creator{id,display_name},details{name,description},last_updated,dl_count,like_count,themes{id,creator{display_name},details{name,description},last_updated,dl_count,like_count,target,preview{original,thumb}}}}&variables={"nsfw":false,"page":1,"limit":10,"sort":"updated","order":"desc","query":null,"creators":["695065006068334622"]}
|
||||||
|
|
||||||
|
auto HasNro() -> bool {
|
||||||
|
fs::FsNativeSd fs;
|
||||||
|
for (auto& path : NRO_PATHS) {
|
||||||
|
if (fs.FileExists(path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// i know, this is cursed
|
// i know, this is cursed
|
||||||
// todo: send actual POST request rather than GET.
|
// todo: send actual POST request rather than GET.
|
||||||
auto apiBuildUrlListInternal(const Config& e, bool is_pack) -> std::string {
|
auto apiBuildUrlListInternal(const Config& e, bool is_pack) -> std::string {
|
||||||
@@ -549,6 +568,28 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
|
|
||||||
void Menu::OnFocusGained() {
|
void Menu::OnFocusGained() {
|
||||||
MenuBase::OnFocusGained();
|
MenuBase::OnFocusGained();
|
||||||
|
|
||||||
|
if (!m_checked_for_nro) {
|
||||||
|
m_checked_for_nro = true;
|
||||||
|
|
||||||
|
// check if we have the nro, if not, then prompt the user to download from the appstore.
|
||||||
|
if (!HasNro()) {
|
||||||
|
App::Push<OptionBox>(
|
||||||
|
"NXthemes_Installer.nro not found, download now?"_i18n,
|
||||||
|
"Back"_i18n, "Download"_i18n, 1, [this](auto op_index){
|
||||||
|
if (op_index && *op_index) {
|
||||||
|
const gh::AssetEntry asset{
|
||||||
|
.name = "NXThemesInstaller.nro",
|
||||||
|
// same path as appstore
|
||||||
|
.path = "/switch/Switch_themes_Installer/NXThemesInstaller.nro",
|
||||||
|
};
|
||||||
|
|
||||||
|
gh::Download(NRO_URL, asset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Menu::InvalidateAllPages() {
|
void Menu::InvalidateAllPages() {
|
||||||
|
|||||||
Reference in New Issue
Block a user