mirror of
https://github.com/izzy2lost/WeeU.git
synced 2026-07-06 00:19:59 -07:00
Added graphic packs settings
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<application
|
||||
android:name="info.cemu.Cemu.CemuApplication"
|
||||
android:allowBackup="true"
|
||||
@@ -17,11 +19,13 @@
|
||||
<activity
|
||||
android:name=".EmulationActivity"
|
||||
android:launchMode="singleTop"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:screenOrientation="userLandscape" />
|
||||
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:exported="false" />
|
||||
android:exported="false"
|
||||
android:parentActivityName=".MainActivity" />
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@@ -33,5 +37,4 @@
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanAPI.h"
|
||||
#include "Cafe/HW/Latte/Renderer/Vulkan/VulkanRenderer.h"
|
||||
#include "CafeSystemUtils.h"
|
||||
#include "Cafe/CafeSystem.h"
|
||||
#include "Cemu/GuiSystem/GuiSystem.h"
|
||||
#include "GameIconLoader.h"
|
||||
#include "GameTitleLoader.h"
|
||||
@@ -19,24 +20,34 @@
|
||||
|
||||
int mainEmulatorHLE();
|
||||
|
||||
class EmulationState
|
||||
{
|
||||
class EmulationState {
|
||||
GameIconLoader m_gameIconLoader;
|
||||
GameTitleLoader m_gameTitleLoader;
|
||||
std::unordered_map<int64_t, GraphicPackPtr> m_graphicPacks;
|
||||
void fillGraphicPacks()
|
||||
{
|
||||
m_graphicPacks.clear();
|
||||
auto graphicPacks = GraphicPack2::GetGraphicPacks();
|
||||
for (auto&& graphicPack : graphicPacks)
|
||||
{
|
||||
m_graphicPacks[reinterpret_cast<int64_t>(graphicPack.get())] = graphicPack;
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
public:
|
||||
void initializeEmulation()
|
||||
{
|
||||
g_config.Load();
|
||||
FilesystemAndroid::setFilesystemCallbacks(std::make_shared<AndroidFilesystemCallbacks>());
|
||||
NetworkConfig::LoadOnce();
|
||||
mainEmulatorHLE();
|
||||
InputManager::instance().load();
|
||||
InitializeGlobalVulkan();
|
||||
createCemuDirectories();
|
||||
g_config.Load();
|
||||
mainEmulatorHLE();
|
||||
fillGraphicPacks();
|
||||
}
|
||||
|
||||
void initializeActiveSettings(const fs::path &dataPath, const fs::path &cachePath)
|
||||
void initializeActiveSettings(const fs::path& dataPath, const fs::path& cachePath)
|
||||
{
|
||||
ActiveSettings::LoadOnce({}, dataPath, dataPath, cachePath, dataPath);
|
||||
}
|
||||
@@ -51,12 +62,12 @@ class EmulationState
|
||||
VulkanRenderer::GetInstance()->NotifySurfaceChanged(isMainCanvas);
|
||||
}
|
||||
|
||||
void setSurface(JNIEnv *env, jobject surface, bool isMainCanvas)
|
||||
void setSurface(JNIEnv* env, jobject surface, bool isMainCanvas)
|
||||
{
|
||||
auto &windowHandleInfo = isMainCanvas ? GuiSystem::getWindowInfo().canvas_main : GuiSystem::getWindowInfo().canvas_pad;
|
||||
auto& windowHandleInfo = isMainCanvas ? GuiSystem::getWindowInfo().canvas_main : GuiSystem::getWindowInfo().canvas_pad;
|
||||
if (windowHandleInfo.surface)
|
||||
{
|
||||
ANativeWindow_release(static_cast<ANativeWindow *>(windowHandleInfo.surface));
|
||||
ANativeWindow_release(static_cast<ANativeWindow*>(windowHandleInfo.surface));
|
||||
windowHandleInfo.surface = nullptr;
|
||||
}
|
||||
if (surface)
|
||||
@@ -67,7 +78,7 @@ class EmulationState
|
||||
|
||||
void setSurfaceSize(int width, int height, bool isMainCanvas)
|
||||
{
|
||||
auto &windowInfo = GuiSystem::getWindowInfo();
|
||||
auto& windowInfo = GuiSystem::getWindowInfo();
|
||||
if (isMainCanvas)
|
||||
{
|
||||
windowInfo.width = windowInfo.phys_width = width;
|
||||
@@ -80,17 +91,17 @@ class EmulationState
|
||||
}
|
||||
}
|
||||
|
||||
void onKeyEvent(const std::string &deviceDescriptor, const std::string &deviceName, int keyCode, bool isPressed)
|
||||
void onKeyEvent(const std::string& deviceDescriptor, const std::string& deviceName, int keyCode, bool isPressed)
|
||||
{
|
||||
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
|
||||
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
|
||||
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider*>(apiProvider.get());
|
||||
androidControllerProvider->on_key_event(deviceDescriptor, deviceName, keyCode, isPressed);
|
||||
}
|
||||
|
||||
void onAxisEvent(const std::string &deviceDescriptor, const std::string &deviceName, int axisCode, float value)
|
||||
void onAxisEvent(const std::string& deviceDescriptor, const std::string& deviceName, int axisCode, float value)
|
||||
{
|
||||
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
|
||||
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider *>(apiProvider.get());
|
||||
auto androidControllerProvider = dynamic_cast<AndroidControllerProvider*>(apiProvider.get());
|
||||
androidControllerProvider->on_axis_event(deviceDescriptor, deviceName, axisCode, value);
|
||||
}
|
||||
|
||||
@@ -120,7 +131,7 @@ class EmulationState
|
||||
{
|
||||
auto emulatedController = AndroidEmulatedController::getAndroidEmulatedController(
|
||||
i)
|
||||
.getEmulatedController();
|
||||
.getEmulatedController();
|
||||
if (!emulatedController)
|
||||
continue;
|
||||
if (emulatedController->type() != EmulatedController::Type::VPAD)
|
||||
@@ -144,7 +155,7 @@ class EmulationState
|
||||
|
||||
void setEmulatedControllerType(size_t index, EmulatedController::Type type)
|
||||
{
|
||||
auto &androidEmulatedController = AndroidEmulatedController::getAndroidEmulatedController(index);
|
||||
auto& androidEmulatedController = AndroidEmulatedController::getAndroidEmulatedController(index);
|
||||
if (EmulatedController::Type::VPAD <= type && type < EmulatedController::Type::MAX)
|
||||
androidEmulatedController.setType(type);
|
||||
else
|
||||
@@ -166,7 +177,7 @@ class EmulationState
|
||||
|
||||
void setDPI(float dpi)
|
||||
{
|
||||
auto &windowInfo = GuiSystem::getWindowInfo();
|
||||
auto& windowInfo = GuiSystem::getWindowInfo();
|
||||
windowInfo.dpi_scale = windowInfo.pad_dpi_scale = dpi;
|
||||
}
|
||||
|
||||
@@ -180,7 +191,7 @@ class EmulationState
|
||||
return AndroidEmulatedController::getAndroidEmulatedController(index).getMappings();
|
||||
}
|
||||
|
||||
void setControllerMapping(const std::string &deviceDescriptor, const std::string &deviceName, size_t index, uint64 mappingId, uint64 buttonId)
|
||||
void setControllerMapping(const std::string& deviceDescriptor, const std::string& deviceName, size_t index, uint64 mappingId, uint64 buttonId)
|
||||
{
|
||||
auto apiProvider = InputManager::instance().get_api_provider(InputAPI::Android);
|
||||
auto controller = ControllerFactory::CreateController(InputAPI::Android, deviceDescriptor, deviceName);
|
||||
@@ -188,7 +199,7 @@ class EmulationState
|
||||
}
|
||||
void initializeAudioDevices()
|
||||
{
|
||||
auto &config = g_config.data();
|
||||
auto& config = g_config.data();
|
||||
if (!config.tv_device.empty())
|
||||
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb, config.tv_channels, config.tv_volume, true);
|
||||
|
||||
@@ -196,22 +207,22 @@ class EmulationState
|
||||
AndroidAudio::createAudioDevice(IAudioAPI::AudioAPI::Cubeb, config.pad_channels, config.pad_volume, false);
|
||||
}
|
||||
|
||||
void setOnGameTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback> &onGameTitleLoaded)
|
||||
void setOnGameTitleLoaded(const std::shared_ptr<GameTitleLoadedCallback>& onGameTitleLoaded)
|
||||
{
|
||||
m_gameTitleLoader.setOnTitleLoaded(onGameTitleLoaded);
|
||||
}
|
||||
|
||||
const Image &getGameIcon(TitleId titleId)
|
||||
const Image& getGameIcon(TitleId titleId)
|
||||
{
|
||||
return m_gameIconLoader.getGameIcon(titleId);
|
||||
}
|
||||
|
||||
void setOnGameIconLoaded(const std::shared_ptr<class GameIconLoadedCallback> &onGameIconLoaded)
|
||||
void setOnGameIconLoaded(const std::shared_ptr<class GameIconLoadedCallback>& onGameIconLoaded)
|
||||
{
|
||||
m_gameIconLoader.setOnIconLoaded(onGameIconLoaded);
|
||||
}
|
||||
|
||||
void addGamePath(const fs::path &gamePath)
|
||||
void addGamePath(const fs::path& gamePath)
|
||||
{
|
||||
m_gameTitleLoader.addGamePath(gamePath);
|
||||
}
|
||||
@@ -231,4 +242,29 @@ class EmulationState
|
||||
initializeAudioDevices();
|
||||
CafeSystemUtils::startGame(titleId);
|
||||
}
|
||||
|
||||
void refreshGraphicPacks()
|
||||
{
|
||||
if (!CafeSystem::IsTitleRunning())
|
||||
{
|
||||
GraphicPack2::ClearGraphicPacks();
|
||||
GraphicPack2::LoadAll();
|
||||
fillGraphicPacks();
|
||||
}
|
||||
}
|
||||
|
||||
const std::unordered_map<int64_t, GraphicPackPtr>& getGraphicPacks() const
|
||||
{
|
||||
return m_graphicPacks;
|
||||
}
|
||||
|
||||
void setEnabledStateForGraphicPack(int64_t id, bool state)
|
||||
{
|
||||
m_graphicPacks.at(id)->SetEnabled(state);
|
||||
}
|
||||
|
||||
GraphicPackPtr getGraphicPack(int64_t id) const
|
||||
{
|
||||
return m_graphicPacks.at(id);
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,4 @@
|
||||
#include "Cafe/GraphicPack/GraphicPack2.h"
|
||||
#include "AndroidGameIconLoadedCallback.h"
|
||||
#include "AndroidGameTitleLoadedCallback.h"
|
||||
#include "EmulationState.h"
|
||||
@@ -292,3 +293,111 @@ Java_info_cemu_Cemu_NativeLibrary_setAudioDeviceVolume([[maybe_unused]] JNIEnv *
|
||||
deviceVolume = volume;
|
||||
g_config.Save();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_refreshGraphicPacks([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
s_emulationState.refreshGraphicPacks();
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_getGraphicPackIdsAndVirtualPaths(JNIEnv* env, [[maybe_unused]] jclass clazz)
|
||||
{
|
||||
auto graphicPackInfoClass = env->FindClass("info/cemu/Cemu/NativeLibrary$GraphicPackIdAndVirtualPath");
|
||||
auto graphicPackInfoCtorId = env->GetMethodID(graphicPackInfoClass, "<init>", "(JLjava/lang/String;)V");
|
||||
|
||||
auto graphicPacks = s_emulationState.getGraphicPacks();
|
||||
std::vector<jobject> graphicPackInfoJObjects;
|
||||
for (auto&& graphicPack : graphicPacks)
|
||||
{
|
||||
jstring virtualPath = env->NewStringUTF(graphicPack.second->GetVirtualPath().c_str());
|
||||
jlong id = graphicPack.first;
|
||||
jobject jGraphicPack = env->NewObject(graphicPackInfoClass, graphicPackInfoCtorId, id, virtualPath);
|
||||
graphicPackInfoJObjects.push_back(jGraphicPack);
|
||||
}
|
||||
return JNIUtils::createArrayList(env, graphicPackInfoJObjects);
|
||||
}
|
||||
|
||||
jobject getGraphicPresets(JNIEnv* env, GraphicPackPtr graphicPack, int64_t id)
|
||||
{
|
||||
auto graphicPackPresetClass = env->FindClass("info/cemu/Cemu/NativeLibrary$GraphicPackPreset");
|
||||
auto graphicPackPresetCtorId = env->GetMethodID(graphicPackPresetClass, "<init>", "(JLjava/lang/String;Ljava/util/ArrayList;Ljava/lang/String;)V");
|
||||
|
||||
std::vector<std::string> order;
|
||||
auto presets = graphicPack->GetCategorizedPresets(order);
|
||||
|
||||
std::vector<jobject> presetsJobjects;
|
||||
for (const auto& category : order)
|
||||
{
|
||||
const auto& entry = presets[category];
|
||||
// test if any preset is visible and update its status
|
||||
if (std::none_of(entry.cbegin(), entry.cend(), [graphicPack](const auto& p) { return p->visible; }))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
jstring categoryJStr = category.empty() ? nullptr : env->NewStringUTF(category.c_str());
|
||||
std::vector<std::string> presetSelections;
|
||||
std::optional<std::string> activePreset;
|
||||
for (auto& pentry : entry)
|
||||
{
|
||||
if (!pentry->visible)
|
||||
continue;
|
||||
|
||||
presetSelections.push_back(pentry->name);
|
||||
|
||||
if (pentry->active)
|
||||
activePreset = pentry->name;
|
||||
}
|
||||
|
||||
jstring activePresetJstr = nullptr;
|
||||
if (activePreset)
|
||||
activePresetJstr = env->NewStringUTF(activePreset->c_str());
|
||||
else if (!presetSelections.empty())
|
||||
activePresetJstr = env->NewStringUTF(presetSelections.front().c_str());
|
||||
auto presetJObject = env->NewObject(graphicPackPresetClass,
|
||||
graphicPackPresetCtorId,
|
||||
id,
|
||||
categoryJStr,
|
||||
JNIUtils::createJavaStringArrayList(env, presetSelections),
|
||||
activePresetJstr);
|
||||
presetsJobjects.push_back(presetJObject);
|
||||
}
|
||||
return JNIUtils::createArrayList(env, presetsJobjects);
|
||||
}
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_getGraphicPack(JNIEnv* env, [[maybe_unused]] jclass clazz, jlong id)
|
||||
{
|
||||
auto graphicPackClass = env->FindClass("info/cemu/Cemu/NativeLibrary$GraphicPack");
|
||||
auto graphicPackCtorId = env->GetMethodID(graphicPackClass, "<init>", "(JZLjava/lang/String;Ljava/lang/String;Ljava/util/ArrayList;)V");
|
||||
auto graphicPack = s_emulationState.getGraphicPack(id);
|
||||
|
||||
jstring graphicPackName = env->NewStringUTF(graphicPack->GetName().c_str());
|
||||
jstring graphicPackDescription = env->NewStringUTF(graphicPack->GetDescription().c_str());
|
||||
return env->NewObject(graphicPackClass,
|
||||
graphicPackCtorId,
|
||||
id,
|
||||
graphicPack->IsEnabled(),
|
||||
graphicPackName,
|
||||
graphicPackDescription,
|
||||
getGraphicPresets(env, graphicPack, id));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_setGraphicPackActive([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong id, jboolean active)
|
||||
{
|
||||
s_emulationState.setEnabledStateForGraphicPack(id, active);
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_setGraphicPackActivePreset([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong id, jstring category, jstring preset)
|
||||
{
|
||||
std::string presetCategory = category == nullptr ? "" : JNIUtils::JStringToString(env, category);
|
||||
s_emulationState.setGraphicPackActivePreset(id, presetCategory, JNIUtils::JStringToString(env, preset));
|
||||
}
|
||||
|
||||
extern "C" JNIEXPORT jobject JNICALL
|
||||
Java_info_cemu_Cemu_NativeLibrary_getGraphicPackPresets(JNIEnv* env, [[maybe_unused]] jclass clazz, jlong id)
|
||||
{
|
||||
return getGraphicPresets(env, s_emulationState.getGraphicPack(id), id);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.BaseAdapter;
|
||||
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public abstract class BaseSelectionAdapter<T> extends BaseAdapter {
|
||||
|
||||
protected int selectedPosition = 0;
|
||||
|
||||
public BaseSelectionAdapter() {
|
||||
super();
|
||||
}
|
||||
|
||||
public void setSelectedValue(T selectedValue) {
|
||||
selectedPosition = getPositionOf(selectedValue);
|
||||
}
|
||||
|
||||
public abstract int getPositionOf(T value);
|
||||
|
||||
abstract protected void setRadioButtonText(MaterialRadioButton radioButton, int position);
|
||||
|
||||
public abstract T getItem(int position);
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup viewGroup) {
|
||||
if (view == null)
|
||||
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_single_selection_item, viewGroup, false);
|
||||
MaterialRadioButton radioButton = view.findViewById(R.id.single_selection_item_radio_button);
|
||||
radioButton.setEnabled(isEnabled(position));
|
||||
setRadioButtonText(radioButton, position);
|
||||
radioButton.setChecked(position == selectedPosition);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public abstract class BaseSingleSelectionRecyclerViewItem<T> implements RecyclerViewItem {
|
||||
private static class SingleSelectionViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView label;
|
||||
TextView description;
|
||||
|
||||
public SingleSelectionViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
label = itemView.findViewById(R.id.selection_label);
|
||||
description = itemView.findViewById(R.id.selection_description);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemSelectedListener<T> {
|
||||
void onItemSelected(T selectedValue, BaseSingleSelectionRecyclerViewItem<T> selectionRecyclerViewItem);
|
||||
}
|
||||
|
||||
private final SelectionAdapter<T> selectionAdapter;
|
||||
private final String label;
|
||||
private String description;
|
||||
private final OnItemSelectedListener<T> onItemSelectedListener;
|
||||
private SingleSelectionViewHolder singleSelectionViewHolder;
|
||||
private RecyclerView.Adapter<RecyclerView.ViewHolder> recyclerViewAdapter;
|
||||
|
||||
public BaseSingleSelectionRecyclerViewItem(String label, String description, SelectionAdapter<T> selectionAdapter, OnItemSelectedListener<T> onItemSelectedListener) {
|
||||
this.label = label;
|
||||
this.description = description;
|
||||
this.selectionAdapter = selectionAdapter;
|
||||
this.onItemSelectedListener = onItemSelectedListener;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
recyclerViewAdapter.notifyItemChanged(singleSelectionViewHolder.getAdapterPosition());
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
return new SingleSelectionViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_single_selection, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> recyclerViewAdapter) {
|
||||
this.recyclerViewAdapter = recyclerViewAdapter;
|
||||
singleSelectionViewHolder = (SingleSelectionViewHolder) viewHolder;
|
||||
singleSelectionViewHolder.label.setText(label);
|
||||
singleSelectionViewHolder.description.setText(description);
|
||||
singleSelectionViewHolder.itemView.setOnClickListener(view -> {
|
||||
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(viewHolder.itemView.getContext());
|
||||
builder.setTitle(label).setAdapter(selectionAdapter, (dialogInterface, position) -> {
|
||||
if (!selectionAdapter.isEnabled(position))
|
||||
return;
|
||||
T selectedValue = selectionAdapter.getItem(position);
|
||||
selectionAdapter.setSelectedValue(selectedValue);
|
||||
if (onItemSelectedListener != null)
|
||||
onItemSelectedListener.onItemSelected(selectedValue, BaseSingleSelectionRecyclerViewItem.this);
|
||||
|
||||
}).setNegativeButton(R.string.cancel, (dialogInterface, i) -> dialogInterface.dismiss()).show();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,15 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.documentfile.provider.DocumentFile;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FileUtil {
|
||||
private static final String PATH_SEPARATOR_ENCODED = "%2F";
|
||||
@@ -88,4 +86,29 @@ public class FileUtil {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static void delete(Path fileToDelete) {
|
||||
try {
|
||||
if (!Files.exists(fileToDelete)) {
|
||||
return;
|
||||
}
|
||||
if (Files.isRegularFile(fileToDelete)) {
|
||||
Files.delete(fileToDelete);
|
||||
return;
|
||||
}
|
||||
var files = Files.list(fileToDelete);
|
||||
files.forEach(path -> {
|
||||
if (Files.isDirectory(path)) {
|
||||
delete(path);
|
||||
}
|
||||
try {
|
||||
Files.delete(path);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
});
|
||||
files.close();
|
||||
Files.delete(fileToDelete);
|
||||
} catch (IOException ignored) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class GenericRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
|
||||
@@ -17,6 +18,13 @@ public class GenericRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerVie
|
||||
notifyItemInserted(recyclerViewItems.size() - 1);
|
||||
}
|
||||
|
||||
public void removeRecyclerViewItem(RecyclerViewItem recyclerViewItem) {
|
||||
int position = recyclerViewItems.indexOf(recyclerViewItem);
|
||||
if (position == -1) return;
|
||||
recyclerViewItems.remove(position);
|
||||
notifyItemRemoved(position);
|
||||
}
|
||||
|
||||
public void clearRecyclerViewItems() {
|
||||
int itemsCount = recyclerViewItems.size();
|
||||
recyclerViewItems.clear();
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class GraphicPackItemRecyclerViewItem implements RecyclerViewItem {
|
||||
|
||||
private static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView title;
|
||||
ImageView icon;
|
||||
|
||||
public HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
title = itemView.findViewById(R.id.graphic_pack_text);
|
||||
icon = itemView.findViewById(R.id.graphic_pack_icon);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnClickCallback {
|
||||
void onClick();
|
||||
}
|
||||
|
||||
public enum GraphicPackItemType {
|
||||
SECTION,
|
||||
GRAPHIC_PACK,
|
||||
}
|
||||
|
||||
private final String text;
|
||||
private final GraphicPackItemType graphicPackItemType;
|
||||
private final OnClickCallback onClickCallback;
|
||||
|
||||
public GraphicPackItemRecyclerViewItem(String text, GraphicPackItemType graphicPackItemType, OnClickCallback onClickCallback) {
|
||||
this.text = text;
|
||||
this.graphicPackItemType = graphicPackItemType;
|
||||
this.onClickCallback = onClickCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
return new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_graphic_pack_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
|
||||
HeaderViewHolder headerViewHolder = (HeaderViewHolder) viewHolder;
|
||||
headerViewHolder.itemView.setOnClickListener(v -> onClickCallback.onClick());
|
||||
headerViewHolder.title.setText(text);
|
||||
if (graphicPackItemType == GraphicPackItemType.SECTION)
|
||||
headerViewHolder.icon.setImageResource(R.drawable.ic_lists);
|
||||
else if (graphicPackItemType == GraphicPackItemType.GRAPHIC_PACK)
|
||||
headerViewHolder.icon.setImageResource(R.drawable.ic_package_2);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.checkbox.MaterialCheckBox;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
|
||||
public class GraphicPackRecyclerViewItem implements RecyclerViewItem {
|
||||
private static class GraphicPackViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView name;
|
||||
MaterialCheckBox enableCheckBox;
|
||||
TextView description;
|
||||
|
||||
public GraphicPackViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
name = itemView.findViewById(R.id.graphic_pack_name);
|
||||
enableCheckBox = itemView.findViewById(R.id.graphic_pack_enable_checkbox);
|
||||
description = itemView.findViewById(R.id.graphic_pack_description);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private final NativeLibrary.GraphicPack graphicPack;
|
||||
|
||||
public GraphicPackRecyclerViewItem(NativeLibrary.GraphicPack graphicPack) {
|
||||
this.graphicPack = graphicPack;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
return new GraphicPackViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_graphic_pack, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> recyclerViewAdapter) {
|
||||
var graphicPackViewHolder = (GraphicPackViewHolder) viewHolder;
|
||||
graphicPackViewHolder.name.setText(graphicPack.name);
|
||||
if (graphicPack.description != null) {
|
||||
graphicPackViewHolder.description.setText(graphicPack.description);
|
||||
} else {
|
||||
graphicPackViewHolder.description.setText(R.string.graphic_pack_no_description);
|
||||
}
|
||||
graphicPackViewHolder.enableCheckBox.setChecked(graphicPack.isActive());
|
||||
graphicPackViewHolder.enableCheckBox.addOnCheckedStateChangedListener((materialCheckBox, i) -> graphicPack.setActive(materialCheckBox.isChecked()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import info.cemu.Cemu.databinding.FragmentGraphicPacksBinding;
|
||||
|
||||
public class GraphicPacksFragment extends Fragment {
|
||||
private final GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
|
||||
private GraphicPacksRootFragment.GraphicPackViewModel graphicPackPreviousViewModel;
|
||||
private GraphicPacksRootFragment.GraphicPackViewModel graphicPackCurrentViewModel;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
NavController navController = NavHostFragment.findNavController(this);
|
||||
graphicPackPreviousViewModel = new ViewModelProvider(Objects.requireNonNull(navController.getPreviousBackStackEntry()))
|
||||
.get(GraphicPacksRootFragment.GraphicPackViewModel.class);
|
||||
graphicPackCurrentViewModel = new ViewModelProvider(Objects.requireNonNull(navController.getCurrentBackStackEntry()))
|
||||
.get(GraphicPacksRootFragment.GraphicPackViewModel.class);
|
||||
fillData();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
FragmentGraphicPacksBinding binding = FragmentGraphicPacksBinding.inflate(inflater, container, false);
|
||||
binding.graphicPacksRecyclerView.setAdapter(genericRecyclerViewAdapter);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void fillData() {
|
||||
var graphicNode = graphicPackPreviousViewModel.getGraphicPackNode();
|
||||
if (graphicNode instanceof GraphicPacksRootFragment.GraphicPackSectionNode sectionNode) {
|
||||
fillData(sectionNode);
|
||||
} else if (graphicNode instanceof GraphicPacksRootFragment.GraphicPackDataNode dataNode) {
|
||||
fillData(dataNode);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillData(GraphicPacksRootFragment.GraphicPackSectionNode graphicPackSectionNode) {
|
||||
graphicPackSectionNode.children.forEach(node -> {
|
||||
var graphicPackItemType = node instanceof GraphicPacksRootFragment.GraphicPackSectionNode
|
||||
? GraphicPackItemRecyclerViewItem.GraphicPackItemType.SECTION
|
||||
: GraphicPackItemRecyclerViewItem.GraphicPackItemType.GRAPHIC_PACK;
|
||||
GraphicPackItemRecyclerViewItem graphicPackItemRecyclerViewItem = new GraphicPackItemRecyclerViewItem(node.name,
|
||||
graphicPackItemType,
|
||||
() -> {
|
||||
graphicPackCurrentViewModel.setGraphicPackNode(node);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("title", node.name);
|
||||
NavHostFragment.findNavController(GraphicPacksFragment.this)
|
||||
.navigate(R.id.action_graphicPacksFragment_self, bundle);
|
||||
});
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(graphicPackItemRecyclerViewItem);
|
||||
});
|
||||
}
|
||||
|
||||
private void fillData(GraphicPacksRootFragment.GraphicPackDataNode graphicPackDataNode) {
|
||||
genericRecyclerViewAdapter.clearRecyclerViewItems();
|
||||
var graphicPack = NativeLibrary.getGraphicPack(graphicPackDataNode.getId());
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(new GraphicPackRecyclerViewItem(graphicPack));
|
||||
fillPresets(graphicPack);
|
||||
}
|
||||
|
||||
private void fillPresets(NativeLibrary.GraphicPack graphicPack) {
|
||||
var recyclerViewItems = new ArrayList<SingleSelectionRecyclerViewItem<String>>();
|
||||
graphicPack.presets.forEach(graphicPackPreset -> {
|
||||
String category = graphicPackPreset.getCategory();
|
||||
if (category == null) category = getString(R.string.active_preset_category);
|
||||
var recyclerViewItem = new SingleSelectionRecyclerViewItem<>(
|
||||
category,
|
||||
graphicPackPreset.getActivePreset(),
|
||||
new StringSelectionAdapter(
|
||||
graphicPackPreset.getPresets(),
|
||||
graphicPackPreset.getActivePreset()
|
||||
),
|
||||
(selectedValue, selectionRecyclerViewItem) -> {
|
||||
graphicPackPreset.setActivePreset(selectedValue);
|
||||
selectionRecyclerViewItem.setDescription(selectedValue);
|
||||
var oldPresets = graphicPack.presets;
|
||||
graphicPack.reloadPresets();
|
||||
if (!new HashSet<>(oldPresets).containsAll(graphicPack.presets)) {
|
||||
recyclerViewItems.forEach(genericRecyclerViewAdapter::removeRecyclerViewItem);
|
||||
fillPresets(graphicPack);
|
||||
}
|
||||
}
|
||||
);
|
||||
recyclerViewItems.add(recyclerViewItem);
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(recyclerViewItem);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import static java.util.regex.Pattern.CASE_INSENSITIVE;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GraphicPacksRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
|
||||
private static class GraphicPackSearchItemRecyclerViewItem implements RecyclerViewItem {
|
||||
|
||||
private static class HeaderViewHolder extends RecyclerView.ViewHolder {
|
||||
TextView name;
|
||||
TextView path;
|
||||
|
||||
public HeaderViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
name = itemView.findViewById(R.id.graphic_pack_search_name);
|
||||
path = itemView.findViewById(R.id.graphic_pack_search_path);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnClickCallback {
|
||||
void onClick();
|
||||
}
|
||||
|
||||
private final String name;
|
||||
private final String path;
|
||||
private final OnClickCallback onClickCallback;
|
||||
|
||||
private GraphicPackSearchItemRecyclerViewItem(String name, String path, OnClickCallback onClickCallback) {
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
this.onClickCallback = onClickCallback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent) {
|
||||
return new HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_graphic_pack_search_item, parent, false));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) {
|
||||
HeaderViewHolder headerViewHolder = (HeaderViewHolder) viewHolder;
|
||||
headerViewHolder.itemView.setOnClickListener(v -> onClickCallback.onClick());
|
||||
headerViewHolder.name.setText(name);
|
||||
headerViewHolder.path.setText(path);
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnItemClickCallback {
|
||||
void onClick(GraphicPacksRootFragment.GraphicPackDataNode graphicPackDataNode);
|
||||
}
|
||||
|
||||
private final OnItemClickCallback onItemClickCallback;
|
||||
|
||||
public GraphicPacksRecyclerViewAdapter(OnItemClickCallback onItemClickCallback) {
|
||||
this.onItemClickCallback = onItemClickCallback;
|
||||
}
|
||||
|
||||
private List<GraphicPackSearchItemRecyclerViewItem> recyclerViewItems = new ArrayList<>();
|
||||
private List<GraphicPackSearchItemRecyclerViewItem> filteredViewItems = new ArrayList<>();
|
||||
|
||||
public void setItems(List<GraphicPacksRootFragment.GraphicPackDataNode> graphicPackDataNodes) {
|
||||
clearItems();
|
||||
recyclerViewItems = graphicPackDataNodes.stream()
|
||||
.sorted(Comparator.comparing(GraphicPacksRootFragment.GraphicPackDataNode::getPath))
|
||||
.map(graphicPackDataNode ->
|
||||
new GraphicPackSearchItemRecyclerViewItem(
|
||||
graphicPackDataNode.getName(),
|
||||
graphicPackDataNode.getPath(),
|
||||
() -> GraphicPacksRecyclerViewAdapter.this.onItemClickCallback.onClick(graphicPackDataNode)
|
||||
)).collect(Collectors.toList());
|
||||
filteredViewItems = recyclerViewItems;
|
||||
notifyItemRangeInserted(0, filteredViewItems.size());
|
||||
}
|
||||
|
||||
public void filter(String text) {
|
||||
if (text.isBlank()) {
|
||||
filteredViewItems = recyclerViewItems;
|
||||
notifyDataSetChanged();
|
||||
return;
|
||||
}
|
||||
var stringBuilder = new StringBuilder();
|
||||
String pattern = Arrays.stream(text.split(" "))
|
||||
.map(s -> "(?=.*" + Pattern.quote(s) + ")")
|
||||
.collect(() -> stringBuilder, StringBuilder::append, StringBuilder::append)
|
||||
.append(".*")
|
||||
.toString();
|
||||
var regex = Pattern.compile(pattern, CASE_INSENSITIVE);
|
||||
filteredViewItems = recyclerViewItems.stream()
|
||||
.filter(g -> regex.matcher(g.path).matches())
|
||||
.collect(Collectors.toList());
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public void clearItems() {
|
||||
if (filteredViewItems.isEmpty()) return;
|
||||
int itemsCount = filteredViewItems.size();
|
||||
filteredViewItems.clear();
|
||||
notifyItemRangeRemoved(0, itemsCount);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
|
||||
return filteredViewItems.get(position).onCreateViewHolder(parent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||
filteredViewItems.get(position).onBindViewHolder(holder, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return filteredViewItems.size();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,354 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.SearchView;
|
||||
import androidx.core.view.MenuProvider;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.lifecycle.Lifecycle;
|
||||
import androidx.lifecycle.ViewModel;
|
||||
import androidx.lifecycle.ViewModelProvider;
|
||||
import androidx.navigation.fragment.NavHostFragment;
|
||||
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.snackbar.Snackbar;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
import info.cemu.Cemu.databinding.FragmentGraphicPacksRootBinding;
|
||||
import okhttp3.Call;
|
||||
import okhttp3.Callback;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
|
||||
public class GraphicPacksRootFragment extends Fragment {
|
||||
public abstract static class GraphicPackNode {
|
||||
protected String name;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GraphicPackDataNode extends GraphicPackNode {
|
||||
private final long id;
|
||||
private final String path;
|
||||
|
||||
public GraphicPackDataNode(long id, String name, String path) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GraphicPackSectionNode extends GraphicPackNode {
|
||||
ArrayList<GraphicPackNode> children = new ArrayList<>();
|
||||
|
||||
public GraphicPackSectionNode(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public GraphicPackSectionNode() {
|
||||
}
|
||||
|
||||
void clear() {
|
||||
children.clear();
|
||||
}
|
||||
|
||||
private void addGraphicPackDataByTokens(List<String> tokens, GraphicPackDataNode dataNode) {
|
||||
var node = this;
|
||||
for (var token : tokens) {
|
||||
node = getOrAddSectionByToken(token, node.children);
|
||||
}
|
||||
node.children.add(dataNode);
|
||||
}
|
||||
|
||||
private GraphicPackSectionNode getOrAddSectionByToken(String token, ArrayList<GraphicPackNode> graphicPackNodes) {
|
||||
var sectionNodeOptional = graphicPackNodes.stream()
|
||||
.filter(g -> (g instanceof GraphicPackSectionNode) && g.name.equals(token))
|
||||
.findFirst();
|
||||
if (!sectionNodeOptional.isPresent()) {
|
||||
var sectionNode = new GraphicPackSectionNode(token);
|
||||
graphicPackNodes.add(sectionNode);
|
||||
return sectionNode;
|
||||
}
|
||||
return (GraphicPackSectionNode) sectionNodeOptional.get();
|
||||
}
|
||||
|
||||
private void sort() {
|
||||
children.forEach(node -> {
|
||||
if (node instanceof GraphicPackSectionNode sectionNode) sectionNode.sort();
|
||||
});
|
||||
children.sort(Comparator.comparing(o -> o.name));
|
||||
}
|
||||
}
|
||||
|
||||
public static class GraphicPackViewModel extends ViewModel {
|
||||
private GraphicPackNode graphicPackNode;
|
||||
|
||||
public GraphicPackViewModel() {
|
||||
}
|
||||
|
||||
public GraphicPackNode getGraphicPackNode() {
|
||||
return graphicPackNode;
|
||||
}
|
||||
|
||||
public void setGraphicPackNode(GraphicPackNode graphicPackNode) {
|
||||
this.graphicPackNode = graphicPackNode;
|
||||
}
|
||||
}
|
||||
|
||||
private final GraphicPackSectionNode graphicPackSectionRootNode = new GraphicPackSectionNode();
|
||||
private final GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter();
|
||||
private GraphicPackViewModel graphicPackViewModel;
|
||||
private final GraphicPacksRecyclerViewAdapter graphicPacksRecyclerViewAdapter = new GraphicPacksRecyclerViewAdapter(graphicPackDataNode -> {
|
||||
graphicPackViewModel.setGraphicPackNode(graphicPackDataNode);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("title", graphicPackDataNode.name);
|
||||
NavHostFragment.findNavController(GraphicPacksRootFragment.this)
|
||||
.navigate(R.id.action_graphicPacksRootFragment_to_graphicPacksFragment, bundle);
|
||||
});
|
||||
private final OkHttpClient client = new OkHttpClient();
|
||||
private static final String RELEASES_API_URL = "https://api.github.com/repos/cemu-project/cemu_graphic_packs/releases/latest";
|
||||
private boolean updateInProgress;
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
fillGraphicPacks();
|
||||
var navController = NavHostFragment.findNavController(GraphicPacksRootFragment.this);
|
||||
graphicPackViewModel = new ViewModelProvider(Objects.requireNonNull(navController.getCurrentBackStackEntry()))
|
||||
.get(GraphicPackViewModel.class);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
|
||||
FragmentGraphicPacksRootBinding binding = FragmentGraphicPacksRootBinding.inflate(inflater, container, false);
|
||||
binding.graphicPacksRootRecyclerView.setAdapter(this.genericRecyclerViewAdapter);
|
||||
requireActivity().addMenuProvider(new MenuProvider() {
|
||||
@Override
|
||||
public void onCreateMenu(@NonNull Menu menu, @NonNull MenuInflater menuInflater) {
|
||||
menuInflater.inflate(R.menu.menu_graphic_packs, menu);
|
||||
var searchMenuItem = menu.findItem(R.id.action_graphic_packs_search);
|
||||
SearchView searchView = (SearchView) Objects.requireNonNull(searchMenuItem.getActionView());
|
||||
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
|
||||
@Override
|
||||
public boolean onQueryTextSubmit(String query) {
|
||||
graphicPacksRecyclerViewAdapter.filter(query);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onQueryTextChange(String newText) {
|
||||
graphicPacksRecyclerViewAdapter.filter(newText);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
searchMenuItem.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
|
||||
binding.graphicPacksRootRecyclerView.setAdapter(graphicPacksRecyclerViewAdapter);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
|
||||
binding.graphicPacksRootRecyclerView.setAdapter(genericRecyclerViewAdapter);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(@NonNull MenuItem menuItem) {
|
||||
if (menuItem.getItemId() == R.id.action_graphic_packs_download && !updateInProgress) {
|
||||
checkForUpdates();
|
||||
return true;
|
||||
}
|
||||
if (menuItem.getItemId() == R.id.action_graphic_packs_search) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}, getViewLifecycleOwner(), Lifecycle.State.RESUMED);
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
private void fillGraphicPacks() {
|
||||
var nativeGraphicPacks = NativeLibrary.getGraphicPackIdsAndVirtualPaths();
|
||||
graphicPackSectionRootNode.clear();
|
||||
List<GraphicPackDataNode> graphicPackDataNodes = new ArrayList<>();
|
||||
nativeGraphicPacks.forEach(graphicPack -> {
|
||||
var tokens = Arrays.asList(graphicPack.virtualPath().split("/"));
|
||||
var dataNode = new GraphicPackDataNode(graphicPack.id(), tokens.get(tokens.size() - 1), graphicPack.virtualPath());
|
||||
graphicPackDataNodes.add(dataNode);
|
||||
graphicPackSectionRootNode.addGraphicPackDataByTokens(tokens.subList(0, tokens.size() - 1), dataNode);
|
||||
});
|
||||
graphicPacksRecyclerViewAdapter.setItems(graphicPackDataNodes);
|
||||
|
||||
genericRecyclerViewAdapter.clearRecyclerViewItems();
|
||||
graphicPackSectionRootNode.sort();
|
||||
graphicPackSectionRootNode.children.forEach(node -> {
|
||||
var graphicPackItemType = node instanceof GraphicPackSectionNode
|
||||
? GraphicPackItemRecyclerViewItem.GraphicPackItemType.SECTION
|
||||
: GraphicPackItemRecyclerViewItem.GraphicPackItemType.GRAPHIC_PACK;
|
||||
GraphicPackItemRecyclerViewItem graphicPackItemRecyclerViewItem = new GraphicPackItemRecyclerViewItem(node.name,
|
||||
graphicPackItemType,
|
||||
() -> {
|
||||
graphicPackViewModel.setGraphicPackNode(node);
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putString("title", node.name);
|
||||
NavHostFragment.findNavController(GraphicPacksRootFragment.this)
|
||||
.navigate(R.id.action_graphicPacksRootFragment_to_graphicPacksFragment, bundle);
|
||||
});
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(graphicPackItemRecyclerViewItem);
|
||||
});
|
||||
}
|
||||
|
||||
private void checkForUpdates() {
|
||||
AlertDialog dialog = createGraphicPacksDownloadDialog();
|
||||
requireActivity().runOnUiThread(() -> Objects.requireNonNull((TextView) dialog.findViewById(R.id.textViewGraphicPacksDownload)).setText(R.string.checking_version_download_text));
|
||||
Request request = new Request.Builder()
|
||||
.url(RELEASES_API_URL)
|
||||
.build();
|
||||
requireActivity().runOnUiThread(() -> Objects.requireNonNull((TextView) dialog.findViewById(R.id.textViewGraphicPacksDownload)).setText(R.string.graphic_packs_download_text));
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
try (ResponseBody responseBody = response.body()) {
|
||||
var json = new JSONObject(Objects.requireNonNull(responseBody).string());
|
||||
var version = json.getString("name");
|
||||
var currentVersion = getCurrentVersion();
|
||||
if (currentVersion.isPresent() && currentVersion.get().equals(version)) {
|
||||
onGraphicPacksLatestVersion(dialog);
|
||||
return;
|
||||
}
|
||||
var downloadUrl = json.getJSONArray("assets")
|
||||
.getJSONObject(0)
|
||||
.getString("browser_download_url");
|
||||
downloadNewUpdate(downloadUrl, version, dialog);
|
||||
} catch (JSONException e) {
|
||||
requireActivity().runOnUiThread(dialog::dismiss);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
if (!call.isCanceled()) onDownloadError(dialog);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Optional<String> getCurrentVersion() {
|
||||
Path graphicPacksVersionFile = Paths.get(Objects.requireNonNull(requireActivity().getExternalFilesDir(null)).getAbsolutePath(),
|
||||
"graphicPacks",
|
||||
"downloadedGraphicPacks",
|
||||
"version.txt");
|
||||
try {
|
||||
return Optional.of(new String(Files.readAllBytes(graphicPacksVersionFile)));
|
||||
} catch (IOException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
private void downloadNewUpdate(String graphicPacksZipDownloadUrl, String version, @NonNull AlertDialog dialog) {
|
||||
Request request = new Request.Builder()
|
||||
.url(graphicPacksZipDownloadUrl)
|
||||
.build();
|
||||
|
||||
Path graphicPacksDirPath = Paths.get(Objects.requireNonNull(requireActivity().getExternalFilesDir(null)).getAbsolutePath(), "graphicPacks");
|
||||
client.newCall(request).enqueue(new Callback() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
|
||||
try (ResponseBody responseBody = response.body()) {
|
||||
Path graphicPacksTempDirPath = graphicPacksDirPath.resolve("downloadedGraphicPacksTemp");
|
||||
FileUtil.delete(graphicPacksTempDirPath);
|
||||
ZipUtil.unzip(Objects.requireNonNull(responseBody).byteStream(), graphicPacksTempDirPath.toString());
|
||||
Files.write(graphicPacksTempDirPath.resolve("version.txt"), version.getBytes(StandardCharsets.UTF_8));
|
||||
Path downloadedGraphicPacksDirPath = graphicPacksDirPath.resolve("downloadedGraphicPacks");
|
||||
FileUtil.delete(downloadedGraphicPacksDirPath);
|
||||
Files.move(graphicPacksTempDirPath, downloadedGraphicPacksDirPath);
|
||||
NativeLibrary.refreshGraphicPacks();
|
||||
requireActivity().runOnUiThread(() -> fillGraphicPacks());
|
||||
onDownloadFinish(dialog);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||
if (!call.isCanceled()) onDownloadError(dialog);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void onDownloadError(AlertDialog dialog) {
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
dialog.dismiss();
|
||||
Snackbar.make(requireView(), R.string.download_graphic_packs_error_text, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
private void onDownloadFinish(AlertDialog dialog) {
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
dialog.dismiss();
|
||||
Snackbar.make(requireView(), R.string.download_graphic_packs_finish_text, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
private void onGraphicPacksLatestVersion(AlertDialog dialog) {
|
||||
requireActivity().runOnUiThread(() -> {
|
||||
dialog.dismiss();
|
||||
Snackbar.make(requireView(), R.string.graphic_packs_no_updates_text, Snackbar.LENGTH_SHORT).show();
|
||||
});
|
||||
}
|
||||
|
||||
private AlertDialog createGraphicPacksDownloadDialog() {
|
||||
updateInProgress = true;
|
||||
return new MaterialAlertDialogBuilder(requireContext()).setView(R.layout.layout_graphic_packs_download)
|
||||
.setTitle(R.string.graphic_packs_download_dialog_title)
|
||||
.setCancelable(false)
|
||||
.setOnDismissListener(d -> updateInProgress = false)
|
||||
.setNegativeButton(R.string.cancel, (dialogInterface, i) -> client.dispatcher().cancelAll())
|
||||
.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
client.dispatcher().cancelAll();
|
||||
super.onDestroy();
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,16 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Surface;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
public class NativeLibrary {
|
||||
static {
|
||||
@@ -284,4 +289,94 @@ public class NativeLibrary {
|
||||
|
||||
public static native int getAudioDeviceVolume(boolean tv);
|
||||
|
||||
public record GraphicPackIdAndVirtualPath(long id, String virtualPath) {
|
||||
}
|
||||
|
||||
public static native ArrayList<GraphicPackIdAndVirtualPath> getGraphicPackIdsAndVirtualPaths();
|
||||
|
||||
public static class GraphicPackPreset {
|
||||
private final long graphicPackId;
|
||||
private final String category;
|
||||
private final ArrayList<String> presets;
|
||||
private String activePreset;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(graphicPackId, category, presets, activePreset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object object) {
|
||||
if (object == null) return false;
|
||||
if (object == this) return true;
|
||||
if (object instanceof GraphicPackPreset preset)
|
||||
return this.hashCode() == preset.hashCode();
|
||||
return false;
|
||||
}
|
||||
|
||||
public GraphicPackPreset(long graphicPackId, String category, ArrayList<String> presets, String activePreset) {
|
||||
this.graphicPackId = graphicPackId;
|
||||
this.category = category;
|
||||
this.presets = presets;
|
||||
this.activePreset = activePreset;
|
||||
}
|
||||
|
||||
public String getActivePreset() {
|
||||
return activePreset;
|
||||
}
|
||||
|
||||
public void setActivePreset(String activePreset) {
|
||||
if (presets.stream().noneMatch(s -> s.equals(activePreset)))
|
||||
throw new RuntimeException("Trying to set an invalid preset: " + activePreset);
|
||||
setGraphicPackActivePreset(graphicPackId, category, activePreset);
|
||||
this.activePreset = activePreset;
|
||||
}
|
||||
|
||||
public String getCategory() {
|
||||
return category;
|
||||
}
|
||||
|
||||
public ArrayList<String> getPresets() {
|
||||
return presets;
|
||||
}
|
||||
}
|
||||
|
||||
public static class GraphicPack {
|
||||
public final long id;
|
||||
protected boolean active;
|
||||
public final String name;
|
||||
public final String description;
|
||||
public List<GraphicPackPreset> presets;
|
||||
|
||||
public GraphicPack(long id, boolean active, String name, String description, ArrayList<GraphicPackPreset> presets) {
|
||||
this.id = id;
|
||||
this.active = active;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.presets = presets;
|
||||
}
|
||||
|
||||
public boolean isActive() {
|
||||
return active;
|
||||
}
|
||||
|
||||
public void reloadPresets() {
|
||||
presets = NativeLibrary.getGraphicPackPresets(id);
|
||||
}
|
||||
|
||||
public void setActive(boolean active) {
|
||||
this.active = active;
|
||||
setGraphicPackActive(id, active);
|
||||
}
|
||||
}
|
||||
|
||||
public static native void refreshGraphicPacks();
|
||||
|
||||
public static native GraphicPack getGraphicPack(long id);
|
||||
|
||||
public static native void setGraphicPackActive(long id, boolean active);
|
||||
|
||||
public static native void setGraphicPackActivePreset(long id, String category, String preset);
|
||||
|
||||
public static native ArrayList<GraphicPackPreset> getGraphicPackPresets(long id);
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ public class OptionsFragment extends Fragment {
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(new SimpleButtonRecyclerViewItem(getString(R.string.input_settings), () -> NavHostFragment.findNavController(OptionsFragment.this).navigate(R.id.action_optionsFragment_to_inputSettingsFragment)));
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(new SimpleButtonRecyclerViewItem(getString(R.string.graphics_settings), () -> NavHostFragment.findNavController(OptionsFragment.this).navigate(R.id.action_optionsFragment_to_graphicSettingsFragment)));
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(new SimpleButtonRecyclerViewItem(getString(R.string.audio_settings), () -> NavHostFragment.findNavController(OptionsFragment.this).navigate(R.id.action_optionsFragment_to_audioSettingsFragment)));
|
||||
genericRecyclerViewAdapter.addRecyclerViewItem(new SimpleButtonRecyclerViewItem(getString(R.string.graphic_packs), () -> NavHostFragment.findNavController(OptionsFragment.this).navigate(R.id.action_optionsFragment_to_graphicPacksRootFragment)));
|
||||
binding.optionsRecyclerView.setAdapter(genericRecyclerViewAdapter);
|
||||
|
||||
return binding.getRoot();
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.util.List;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class SelectionAdapter<T> extends BaseAdapter {
|
||||
public class SelectionAdapter<T> extends BaseSelectionAdapter<T> {
|
||||
public static class ChoiceItem<T> {
|
||||
int choiceTextResourceId;
|
||||
T choiceValue;
|
||||
@@ -24,7 +24,6 @@ public class SelectionAdapter<T> extends BaseAdapter {
|
||||
}
|
||||
|
||||
protected List<ChoiceItem<T>> choiceItems = new ArrayList<>();
|
||||
protected int selectedPosition = 0;
|
||||
|
||||
public SelectionAdapter() {
|
||||
super();
|
||||
@@ -36,10 +35,12 @@ public class SelectionAdapter<T> extends BaseAdapter {
|
||||
setSelectedValue(selectedValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedValue(T selectedValue) {
|
||||
selectedPosition = getPositionOf(selectedValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPositionOf(T value) {
|
||||
OptionalInt optionalInt = IntStream.range(0, choiceItems.size()).filter(position -> choiceItems.get(position).choiceValue.equals(value)).findFirst();
|
||||
if (!optionalInt.isPresent())
|
||||
@@ -47,6 +48,11 @@ public class SelectionAdapter<T> extends BaseAdapter {
|
||||
return optionalInt.getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRadioButtonText(MaterialRadioButton radioButton, int position) {
|
||||
radioButton.setText(choiceItems.get(position).choiceTextResourceId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return choiceItems.size();
|
||||
@@ -56,20 +62,4 @@ public class SelectionAdapter<T> extends BaseAdapter {
|
||||
public T getItem(int position) {
|
||||
return choiceItems.get(position).choiceValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getItemId(int position) {
|
||||
return position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, ViewGroup viewGroup) {
|
||||
if (view == null)
|
||||
view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.layout_single_selection_item, viewGroup, false);
|
||||
MaterialRadioButton radioButton = view.findViewById(R.id.single_selection_item_radio_button);
|
||||
radioButton.setEnabled(isEnabled(position));
|
||||
radioButton.setText(choiceItems.get(position).choiceTextResourceId);
|
||||
radioButton.setChecked(position == selectedPosition);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,31 +4,32 @@ import android.os.Bundle;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.navigation.NavController;
|
||||
import androidx.navigation.NavGraph;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.navigation.ui.AppBarConfiguration;
|
||||
import androidx.navigation.ui.NavigationUI;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import info.cemu.Cemu.databinding.ActivitySettingsBinding;
|
||||
|
||||
public class SettingsActivity extends AppCompatActivity {
|
||||
private AppBarConfiguration appBarConfiguration;
|
||||
private ActivitySettingsBinding binding;
|
||||
private NavController navController;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
binding = ActivitySettingsBinding.inflate(getLayoutInflater());
|
||||
info.cemu.Cemu.databinding.ActivitySettingsBinding binding = ActivitySettingsBinding.inflate(getLayoutInflater());
|
||||
setContentView(binding.getRoot());
|
||||
|
||||
setSupportActionBar(binding.toolbarSettings);
|
||||
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_settings);
|
||||
appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph()).build();
|
||||
navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_settings);
|
||||
appBarConfiguration = new AppBarConfiguration.Builder().build();
|
||||
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSupportNavigateUp() {
|
||||
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_settings);
|
||||
return NavigationUI.navigateUp(navController, appBarConfiguration)
|
||||
|| super.onSupportNavigateUp();
|
||||
}
|
||||
|
||||
@@ -25,14 +25,14 @@ public class SingleSelectionRecyclerViewItem<T> implements RecyclerViewItem {
|
||||
void onItemSelected(T selectedValue, SingleSelectionRecyclerViewItem<T> selectionRecyclerViewItem);
|
||||
}
|
||||
|
||||
private final SelectionAdapter<T> selectionAdapter;
|
||||
private final BaseSelectionAdapter<T> selectionAdapter;
|
||||
private final String label;
|
||||
private String description;
|
||||
private final OnItemSelectedListener<T> onItemSelectedListener;
|
||||
private SingleSelectionViewHolder singleSelectionViewHolder;
|
||||
private RecyclerView.Adapter<RecyclerView.ViewHolder> recyclerViewAdapter;
|
||||
|
||||
public SingleSelectionRecyclerViewItem(String label, String description, SelectionAdapter<T> selectionAdapter, OnItemSelectedListener<T> onItemSelectedListener) {
|
||||
public SingleSelectionRecyclerViewItem(String label, String description, BaseSelectionAdapter<T> selectionAdapter, OnItemSelectedListener<T> onItemSelectedListener) {
|
||||
this.label = label;
|
||||
this.description = description;
|
||||
this.selectionAdapter = selectionAdapter;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import com.google.android.material.radiobutton.MaterialRadioButton;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.OptionalInt;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
public class StringSelectionAdapter extends BaseSelectionAdapter<String> {
|
||||
private final List<String> choices;
|
||||
|
||||
public StringSelectionAdapter(List<String> choices, String selectedValue) {
|
||||
super();
|
||||
this.choices = choices;
|
||||
setSelectedValue(selectedValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSelectedValue(String selectedValue) {
|
||||
selectedPosition = getPositionOf(selectedValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPositionOf(String value) {
|
||||
OptionalInt optionalInt = IntStream.range(0, choices.size()).filter(position -> choices.get(position).equals(value)).findFirst();
|
||||
if (!optionalInt.isPresent())
|
||||
throw new RuntimeException("value " + value + " was not found in the list of choices");
|
||||
return optionalInt.getAsInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setRadioButtonText(MaterialRadioButton radioButton, int position) {
|
||||
radioButton.setText(choices.get(position));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCount() {
|
||||
return choices.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getItem(int position) {
|
||||
return choices.get(position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package info.cemu.Cemu;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipInputStream;
|
||||
|
||||
public class ZipUtil {
|
||||
|
||||
public static void unzip(InputStream stream, String targetDir) throws IOException {
|
||||
try (ZipInputStream zipInputStream = new ZipInputStream(stream)) {
|
||||
ZipEntry zipEntry;
|
||||
byte[] buffer = new byte[8192];
|
||||
while ((zipEntry = zipInputStream.getNextEntry()) != null) {
|
||||
String filePath = Paths.get(targetDir, zipEntry.getName()).toString();
|
||||
if (zipEntry.isDirectory()) {
|
||||
createDir(filePath);
|
||||
} else {
|
||||
try (FileOutputStream fileOutputStream = new FileOutputStream(filePath)) {
|
||||
int bytesRead;
|
||||
while ((bytesRead = zipInputStream.read(buffer)) > 0) {
|
||||
fileOutputStream.write(buffer, 0, bytesRead);
|
||||
}
|
||||
}
|
||||
}
|
||||
zipInputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void createDir(String dir) {
|
||||
File f = new File(dir);
|
||||
if (!f.isDirectory()) {
|
||||
f.mkdirs();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:autoMirrored="true"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M504,480L320,296L376,240L616,480L376,720L320,664L504,480Z" />
|
||||
</vector>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user