themezer: prompt user to download ThemeInjector if not installed on launch.
This commit is contained in:
@@ -69,12 +69,24 @@ private:
|
||||
void Sort();
|
||||
void UpdateSubheading();
|
||||
|
||||
void DownloadEntries();
|
||||
|
||||
private:
|
||||
std::vector<Entry> m_entries{};
|
||||
s64 m_index{};
|
||||
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
|
||||
|
||||
@@ -169,6 +169,8 @@ private:
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
||||
option::OptionLong m_order{INI_SECTION, "order", 0};
|
||||
option::OptionBool m_nsfw{INI_SECTION, "nsfw", false};
|
||||
|
||||
bool m_checked_for_nro{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::themezer
|
||||
|
||||
@@ -176,7 +176,7 @@ Menu::Menu(u32 flags) : MenuBase{"GitHub"_i18n, flags} {
|
||||
return;
|
||||
}
|
||||
|
||||
DownloadEntries();
|
||||
DownloadEntries(GetEntry());
|
||||
}}),
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
void Menu::DownloadEntries() {
|
||||
void DownloadEntries(const Entry& entry) {
|
||||
// hack
|
||||
static std::vector<GhApiEntry> gh_entries;
|
||||
gh_entries = {};
|
||||
|
||||
App::Push<ProgressBox>(0, "Downloading "_i18n, GetEntry().repo, [this](auto pbox) -> Result {
|
||||
return DownloadReleaseJsonJson(pbox, GenerateApiUrl(GetEntry()), gh_entries);
|
||||
}, [this](Result rc){
|
||||
App::Push<ProgressBox>(0, "Downloading "_i18n, entry.repo, [entry](auto pbox) -> Result {
|
||||
return DownloadReleaseJsonJson(pbox, GenerateApiUrl(entry), gh_entries);
|
||||
}, [entry](Result rc){
|
||||
App::PushErrorBox(rc, "Failed to download json"_i18n);
|
||||
if (R_FAILED(rc) || gh_entries.empty()) {
|
||||
return;
|
||||
@@ -370,13 +370,13 @@ void Menu::DownloadEntries() {
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& gh_entry = gh_entries[*op_index];
|
||||
const auto& assets = GetEntry().assets;
|
||||
const auto& assets = entry.assets;
|
||||
PopupList::Items asset_items;
|
||||
std::vector<const AssetEntry*> asset_ptr;
|
||||
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) {
|
||||
return;
|
||||
}
|
||||
@@ -414,7 +414,7 @@ void Menu::DownloadEntries() {
|
||||
const auto index = *op_index;
|
||||
const auto& asset_entry = api_assets[index];
|
||||
const AssetEntry* ptr{};
|
||||
auto pre_install_message = GetEntry().pre_install_message;
|
||||
auto pre_install_message = entry.pre_install_message;
|
||||
if (asset_ptr.size()) {
|
||||
ptr = asset_ptr[index];
|
||||
if (!ptr->pre_install_message.empty()) {
|
||||
@@ -422,16 +422,16 @@ void Menu::DownloadEntries() {
|
||||
}
|
||||
}
|
||||
|
||||
const auto func = [this, &asset_entry, ptr](){
|
||||
App::Push<ProgressBox>(0, "Downloading "_i18n, GetEntry().repo, [this, &asset_entry, ptr](auto pbox) -> Result {
|
||||
const auto func = [entry, &asset_entry, ptr](){
|
||||
App::Push<ProgressBox>(0, "Downloading "_i18n, entry.repo, [entry, &asset_entry, ptr](auto pbox) -> Result {
|
||||
return DownloadApp(pbox, asset_entry, ptr);
|
||||
}, [this, ptr](Result rc){
|
||||
}, [entry, ptr](Result rc){
|
||||
homebrew::SignalChange();
|
||||
App::PushErrorBox(rc, "Failed to download app!"_i18n);
|
||||
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
App::Notify("Downloaded "_i18n + GetEntry().repo);
|
||||
auto post_install_message = GetEntry().post_install_message;
|
||||
App::Notify("Downloaded "_i18n + entry.repo);
|
||||
auto post_install_message = entry.post_install_message;
|
||||
if (ptr && !ptr->post_install_message.empty()) {
|
||||
post_install_message = ptr->post_install_message;
|
||||
}
|
||||
@@ -446,7 +446,7 @@ void Menu::DownloadEntries() {
|
||||
if (!pre_install_message.empty()) {
|
||||
App::Push<OptionBox>(
|
||||
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) {
|
||||
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
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "ui/menus/themezer.hpp"
|
||||
#include "ui/menus/ghdl.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
#include "ui/option_box.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 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[]{
|
||||
"ResidentMenu",
|
||||
"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,$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
|
||||
// todo: send actual POST request rather than GET.
|
||||
auto apiBuildUrlListInternal(const Config& e, bool is_pack) -> std::string {
|
||||
@@ -549,6 +568,28 @@ void Menu::Draw(NVGcontext* vg, Theme* theme) {
|
||||
|
||||
void Menu::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() {
|
||||
|
||||
Reference in New Issue
Block a user