From 3678a0e4748ba6281a44cf93fc926a7e666486eb Mon Sep 17 00:00:00 2001 From: SSimco <37044560+SSimco@users.noreply.github.com> Date: Sun, 1 Sep 2024 22:31:07 +0300 Subject: [PATCH] Added option to show graphic packs for installed games only --- .../main/cpp/AndroidGameIconLoadedCallback.h | 5 +- .../main/cpp/AndroidGameTitleLoadedCallback.h | 2 +- src/android/app/src/main/cpp/JNIUtils.cpp | 11 ++++ src/android/app/src/main/cpp/JNIUtils.h | 10 ++- src/android/app/src/main/cpp/native-lib.cpp | 21 ++++-- .../java/info/cemu/Cemu/NativeLibrary.java | 6 +- .../FilterableRecyclerViewAdapter.java | 64 +++++++++++++++++++ .../GraphicPackItemRecyclerViewItem.java | 4 +- .../graphicpacks/GraphicPacksFragment.java | 1 + .../GraphicPacksRecyclerViewAdapter.java | 44 ++++++++++--- .../GraphicPacksRootFragment.java | 48 +++++++++++--- .../src/main/res/menu/menu_graphic_packs.xml | 9 ++- .../app/src/main/res/values/strings.xml | 1 + 13 files changed, 190 insertions(+), 36 deletions(-) create mode 100644 src/android/app/src/main/java/info/cemu/Cemu/guibasecomponents/FilterableRecyclerViewAdapter.java diff --git a/src/android/app/src/main/cpp/AndroidGameIconLoadedCallback.h b/src/android/app/src/main/cpp/AndroidGameIconLoadedCallback.h index bad0a748..e48063eb 100644 --- a/src/android/app/src/main/cpp/AndroidGameIconLoadedCallback.h +++ b/src/android/app/src/main/cpp/AndroidGameIconLoadedCallback.h @@ -3,7 +3,8 @@ #include "GameIconLoader.h" #include "JNIUtils.h" -class AndroidGameIconLoadedCallback : public GameIconLoadedCallback { +class AndroidGameIconLoadedCallback : public GameIconLoadedCallback +{ jmethodID m_onGameIconLoadedMID; JNIUtils::Scopedjobject m_gameTitleLoadedCallbackObj; @@ -18,7 +19,7 @@ class AndroidGameIconLoadedCallback : public GameIconLoadedCallback { JNIUtils::ScopedJNIENV env; jintArray jIconData = env->NewIntArray(width * height); env->SetIntArrayRegion(jIconData, 0, width * height, reinterpret_cast(colors)); - env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameIconLoadedMID, *reinterpret_cast(&titleId), jIconData, width, height); + env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameIconLoadedMID, static_cast(titleId), jIconData, width, height); env->DeleteLocalRef(jIconData); } }; \ No newline at end of file diff --git a/src/android/app/src/main/cpp/AndroidGameTitleLoadedCallback.h b/src/android/app/src/main/cpp/AndroidGameTitleLoadedCallback.h index f6570eea..172dc5a5 100644 --- a/src/android/app/src/main/cpp/AndroidGameTitleLoadedCallback.h +++ b/src/android/app/src/main/cpp/AndroidGameTitleLoadedCallback.h @@ -15,7 +15,7 @@ class AndroidGameTitleLoadedCallback : public GameTitleLoadedCallback { { JNIUtils::ScopedJNIENV env; jstring name = env->NewStringUTF(game.name.c_str()); - jlong titleId = *reinterpret_cast(&game.titleId); + jlong titleId = static_cast(game.titleId); env->CallVoidMethod(*m_gameTitleLoadedCallbackObj, m_onGameTitleLoadedMID, titleId, name); env->DeleteLocalRef(name); } diff --git a/src/android/app/src/main/cpp/JNIUtils.cpp b/src/android/app/src/main/cpp/JNIUtils.cpp index e93c2e5f..76d7cf8d 100644 --- a/src/android/app/src/main/cpp/JNIUtils.cpp +++ b/src/android/app/src/main/cpp/JNIUtils.cpp @@ -53,6 +53,17 @@ jobject JNIUtils::createArrayList(JNIEnv* env, const std::vector& objec return arrayList; } +jobject JNIUtils::createJavaLongArrayList(JNIEnv* env, const std::vector& values) +{ + jclass longClass = env->FindClass("java/lang/Long"); + jmethodID valueOf = env->GetStaticMethodID(longClass, "valueOf", "(J)Ljava/lang/Long;"); + std::vector valuesJava; + for (auto&& value : values) + valuesJava.push_back(env->CallStaticObjectMethod(longClass, valueOf, static_cast(value))); + env->DeleteLocalRef(longClass); + return JNIUtils::createArrayList(env, valuesJava); +} + jobject JNIUtils::createJavaStringArrayList(JNIEnv* env, const std::vector& strings) { jclass arrayListClass = env->FindClass("java/util/ArrayList"); diff --git a/src/android/app/src/main/cpp/JNIUtils.h b/src/android/app/src/main/cpp/JNIUtils.h index 038e19a0..05cb5334 100644 --- a/src/android/app/src/main/cpp/JNIUtils.h +++ b/src/android/app/src/main/cpp/JNIUtils.h @@ -13,7 +13,8 @@ namespace JNIUtils jobject createJavaStringArrayList(JNIEnv* env, const std::vector& stringList); - class ScopedJNIENV { + class ScopedJNIENV + { public: ScopedJNIENV() { @@ -51,7 +52,8 @@ namespace JNIUtils bool m_threadWasAttached = false; }; - class Scopedjobject { + class Scopedjobject + { public: Scopedjobject() = default; @@ -103,7 +105,8 @@ namespace JNIUtils jobject m_jobject = nullptr; }; - class Scopedjclass { + class Scopedjclass + { public: Scopedjclass() = default; @@ -155,4 +158,5 @@ namespace JNIUtils Scopedjobject getEnumValue(JNIEnv* env, const std::string& enumClassName, const std::string& enumName); jobject createArrayList(JNIEnv* env, const std::vector& objects); + jobject createJavaLongArrayList(JNIEnv* env, const std::vector& values); } // namespace JNIUtils diff --git a/src/android/app/src/main/cpp/native-lib.cpp b/src/android/app/src/main/cpp/native-lib.cpp index 7a90982b..ba5ebc0d 100644 --- a/src/android/app/src/main/cpp/native-lib.cpp +++ b/src/android/app/src/main/cpp/native-lib.cpp @@ -6,6 +6,8 @@ EmulationState s_emulationState; +static_assert(sizeof(TitleId) == sizeof(jlong)); + extern "C" [[maybe_unused]] JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, [[maybe_unused]] void* reserved) { JNIUtils::g_jvm = vm; @@ -27,7 +29,7 @@ Java_info_cemu_Cemu_NativeLibrary_setSurfaceSize([[maybe_unused]] JNIEnv* env, [ extern "C" [[maybe_unused]] JNIEXPORT void JNICALL Java_info_cemu_Cemu_NativeLibrary_startGame([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong title_id) { - s_emulationState.startGame(*reinterpret_cast(&title_id)); + s_emulationState.startGame(static_cast(title_id)); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL @@ -50,7 +52,7 @@ Java_info_cemu_Cemu_NativeLibrary_setGameIconLoadedCallback(JNIEnv* env, [[maybe extern "C" [[maybe_unused]] JNIEXPORT void JNICALL Java_info_cemu_Cemu_NativeLibrary_requestGameIcon([[maybe_unused]] JNIEnv* env, [[maybe_unused]] jclass clazz, jlong title_id) { - s_emulationState.requestGameIcon(*reinterpret_cast(&title_id)); + s_emulationState.requestGameIcon(static_cast(title_id)); } extern "C" [[maybe_unused]] JNIEXPORT void JNICALL @@ -297,10 +299,10 @@ Java_info_cemu_Cemu_NativeLibrary_refreshGraphicPacks([[maybe_unused]] JNIEnv* e } extern "C" JNIEXPORT jobject JNICALL -Java_info_cemu_Cemu_NativeLibrary_getGraphicPackIdsAndVirtualPaths(JNIEnv* env, [[maybe_unused]] jclass clazz) +Java_info_cemu_Cemu_NativeLibrary_getGraphicPackBasicInfos(JNIEnv* env, [[maybe_unused]] jclass clazz) { - auto graphicPackInfoClass = env->FindClass("info/cemu/Cemu/NativeLibrary$GraphicPackIdAndVirtualPath"); - auto graphicPackInfoCtorId = env->GetMethodID(graphicPackInfoClass, "", "(JLjava/lang/String;)V"); + auto graphicPackInfoClass = env->FindClass("info/cemu/Cemu/NativeLibrary$GraphicPackBasicInfo"); + auto graphicPackInfoCtorId = env->GetMethodID(graphicPackInfoClass, "", "(JLjava/lang/String;Ljava/util/ArrayList;)V"); auto graphicPacks = s_emulationState.getGraphicPacks(); std::vector graphicPackInfoJObjects; @@ -308,7 +310,8 @@ Java_info_cemu_Cemu_NativeLibrary_getGraphicPackIdsAndVirtualPaths(JNIEnv* env, { jstring virtualPath = env->NewStringUTF(graphicPack.second->GetVirtualPath().c_str()); jlong id = graphicPack.first; - jobject jGraphicPack = env->NewObject(graphicPackInfoClass, graphicPackInfoCtorId, id, virtualPath); + jobject titleIds = JNIUtils::createJavaLongArrayList(env, graphicPack.second->GetTitleIds()); + jobject jGraphicPack = env->NewObject(graphicPackInfoClass, graphicPackInfoCtorId, id, virtualPath, titleIds); graphicPackInfoJObjects.push_back(jGraphicPack); } return JNIUtils::createArrayList(env, graphicPackInfoJObjects); @@ -589,3 +592,9 @@ Java_info_cemu_Cemu_NativeLibrary_onTouchMove([[maybe_unused]] JNIEnv* env, [[ma { s_emulationState.onTouchMove(x, y, isTV); } + +extern "C" JNIEXPORT jobject JNICALL +Java_info_cemu_Cemu_NativeLibrary_getInstalledGamesTitleIds(JNIEnv* env, [[maybe_unused]] jclass clazz) +{ + return JNIUtils::createJavaLongArrayList(env, CafeTitleList::GetAllTitleIds()); +} \ No newline at end of file diff --git a/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java b/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java index 967cf691..ed298fd2 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/NativeLibrary.java @@ -241,10 +241,12 @@ public class NativeLibrary { public static native int getAudioDeviceVolume(boolean tv); - public record GraphicPackIdAndVirtualPath(long id, String virtualPath) { + public record GraphicPackBasicInfo(long id, String virtualPath, ArrayList titleIds) { } - public static native ArrayList getGraphicPackIdsAndVirtualPaths(); + public static native ArrayList getInstalledGamesTitleIds(); + + public static native ArrayList getGraphicPackBasicInfos(); public static class GraphicPackPreset { private final long graphicPackId; diff --git a/src/android/app/src/main/java/info/cemu/Cemu/guibasecomponents/FilterableRecyclerViewAdapter.java b/src/android/app/src/main/java/info/cemu/Cemu/guibasecomponents/FilterableRecyclerViewAdapter.java new file mode 100644 index 00000000..984831b3 --- /dev/null +++ b/src/android/app/src/main/java/info/cemu/Cemu/guibasecomponents/FilterableRecyclerViewAdapter.java @@ -0,0 +1,64 @@ +package info.cemu.Cemu.guibasecomponents; + +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public class FilterableRecyclerViewAdapter extends RecyclerView.Adapter { + + private Predicate predicate; + private final List recyclerViewItems = new ArrayList<>(); + private List filteredRecyclerViewItems = new ArrayList<>(); + + public void addRecyclerViewItem(T recyclerViewItem) { + recyclerViewItems.add(recyclerViewItem); + if (predicate == null || predicate.test(recyclerViewItem)) { + filteredRecyclerViewItems.add(recyclerViewItem); + notifyItemInserted(filteredRecyclerViewItems.size() - 1); + } + } + + public void setPredicate(@NonNull Predicate predicate) { + this.predicate = predicate; + int oldSize = filteredRecyclerViewItems.size(); + filteredRecyclerViewItems = recyclerViewItems.stream().filter(predicate).collect(Collectors.toList()); + if (oldSize != filteredRecyclerViewItems.size()) + notifyDataSetChanged(); + } + + public void clearPredicate() { + int oldSize = filteredRecyclerViewItems.size(); + filteredRecyclerViewItems = new ArrayList<>(recyclerViewItems); + if (oldSize != filteredRecyclerViewItems.size()) + notifyDataSetChanged(); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) { + return filteredRecyclerViewItems.get(position).onCreateViewHolder(parent); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { + filteredRecyclerViewItems.get(position).onBindViewHolder(holder, this); + } + + @Override + public int getItemCount() { + return filteredRecyclerViewItems.size(); + } + + public void clearRecyclerViewItems() { + int size = filteredRecyclerViewItems.size(); + filteredRecyclerViewItems.clear(); + recyclerViewItems.clear(); + notifyItemRangeRemoved(0, size); + } +} diff --git a/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPackItemRecyclerViewItem.java b/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPackItemRecyclerViewItem.java index f2f02b2e..6338d9c4 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPackItemRecyclerViewItem.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPackItemRecyclerViewItem.java @@ -34,11 +34,13 @@ public class GraphicPackItemRecyclerViewItem implements RecyclerViewItem { } private final String text; + public final boolean hasInstalledTitleId; private final GraphicPackItemType graphicPackItemType; private final OnClickCallback onClickCallback; - public GraphicPackItemRecyclerViewItem(String text, GraphicPackItemType graphicPackItemType, OnClickCallback onClickCallback) { + public GraphicPackItemRecyclerViewItem(String text, boolean hasInstalledTitleId, GraphicPackItemType graphicPackItemType, OnClickCallback onClickCallback) { this.text = text; + this.hasInstalledTitleId = hasInstalledTitleId; this.graphicPackItemType = graphicPackItemType; this.onClickCallback = onClickCallback; } diff --git a/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPacksFragment.java b/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPacksFragment.java index fed02901..82f13187 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPacksFragment.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPacksFragment.java @@ -63,6 +63,7 @@ public class GraphicPacksFragment extends Fragment { ? GraphicPackItemRecyclerViewItem.GraphicPackItemType.SECTION : GraphicPackItemRecyclerViewItem.GraphicPackItemType.GRAPHIC_PACK; GraphicPackItemRecyclerViewItem graphicPackItemRecyclerViewItem = new GraphicPackItemRecyclerViewItem(node.name, + node.hasTitleIdInstalled(), graphicPackItemType, () -> { graphicPackCurrentViewModel.setGraphicPackNode(node); diff --git a/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPacksRecyclerViewAdapter.java b/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPacksRecyclerViewAdapter.java index db1b1434..b533bffd 100644 --- a/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPacksRecyclerViewAdapter.java +++ b/src/android/app/src/main/java/info/cemu/Cemu/settings/graphicpacks/GraphicPacksRecyclerViewAdapter.java @@ -40,11 +40,13 @@ public class GraphicPacksRecyclerViewAdapter extends RecyclerView.Adapter recyclerViewItems = new ArrayList<>(); private List filteredViewItems = new ArrayList<>(); + public void setShowInstalledOnly(boolean showInstalledGamesOnly) { + this.showInstalledGamesOnly = showInstalledGamesOnly; + filter(); + } + public void setItems(List graphicPackDataNodes) { clearItems(); recyclerViewItems = graphicPackDataNodes.stream() @@ -83,28 +92,44 @@ public class GraphicPacksRecyclerViewAdapter extends RecyclerView.Adapter GraphicPacksRecyclerViewAdapter.this.onItemClickCallback.onClick(graphicPackDataNode) )).collect(Collectors.toList()); filteredViewItems = recyclerViewItems; + if (showInstalledGamesOnly) + filteredViewItems = filteredViewItems.stream().filter(g -> g.hasInstalledTitleId).collect(Collectors.toList()); notifyItemRangeInserted(0, filteredViewItems.size()); } - public void filter(String text) { - if (text.isBlank()) { - filteredViewItems = recyclerViewItems; - notifyDataSetChanged(); + private void resetFilteredItems() { + filteredViewItems = recyclerViewItems; + if (showInstalledGamesOnly) + filteredViewItems = filteredViewItems.stream().filter(g -> g.hasInstalledTitleId).collect(Collectors.toList()); + notifyDataSetChanged(); + } + + public void setFilterText(String filterText) { + this.filterText = filterText; + filter(); + } + + public void filter() { + if (filterText.isBlank()) { + resetFilteredItems(); return; } var stringBuilder = new StringBuilder(); - String pattern = Arrays.stream(text.split(" ")) + String pattern = Arrays.stream(filterText.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()); + var filteredData = recyclerViewItems.stream(); + if (showInstalledGamesOnly) + filteredData = filteredData.filter(g -> g.hasInstalledTitleId); + filteredData = filteredData.filter(g -> regex.matcher(g.path).matches()); + filteredViewItems = filteredData.collect(Collectors.toList()); notifyDataSetChanged(); } @@ -112,6 +137,7 @@ public class GraphicPacksRecyclerViewAdapter extends RecyclerView.Adapter children = new ArrayList<>(); - public GraphicPackSectionNode(String name) { + public GraphicPackSectionNode(String name, boolean hasTitleIdInstalled) { this.name = name; + this.titleIdInstalled = hasTitleIdInstalled; } public GraphicPackSectionNode() { @@ -96,17 +104,17 @@ public class GraphicPacksRootFragment extends Fragment { private void addGraphicPackDataByTokens(List tokens, GraphicPackDataNode dataNode) { var node = this; for (var token : tokens) { - node = getOrAddSectionByToken(token, node.children); + node = getOrAddSectionByToken(token, node.children, dataNode.titleIdInstalled); } node.children.add(dataNode); } - private GraphicPackSectionNode getOrAddSectionByToken(String token, ArrayList graphicPackNodes) { + private GraphicPackSectionNode getOrAddSectionByToken(String token, ArrayList graphicPackNodes, boolean hasTitleIdInstalled) { var sectionNodeOptional = graphicPackNodes.stream() .filter(g -> (g instanceof GraphicPackSectionNode) && g.name.equals(token)) .findFirst(); if (!sectionNodeOptional.isPresent()) { - var sectionNode = new GraphicPackSectionNode(token); + var sectionNode = new GraphicPackSectionNode(token, hasTitleIdInstalled); graphicPackNodes.add(sectionNode); return sectionNode; } @@ -136,8 +144,9 @@ public class GraphicPacksRootFragment extends Fragment { } } + private final ArrayList installedTitleIds = NativeLibrary.getInstalledGamesTitleIds(); private final GraphicPackSectionNode graphicPackSectionRootNode = new GraphicPackSectionNode(); - private final GenericRecyclerViewAdapter genericRecyclerViewAdapter = new GenericRecyclerViewAdapter(); + private final FilterableRecyclerViewAdapter genericRecyclerViewAdapter = new FilterableRecyclerViewAdapter<>(); private GraphicPackViewModel graphicPackViewModel; private final GraphicPacksRecyclerViewAdapter graphicPacksRecyclerViewAdapter = new GraphicPacksRecyclerViewAdapter(graphicPackDataNode -> { graphicPackViewModel.setGraphicPackNode(graphicPackDataNode); @@ -153,6 +162,8 @@ public class GraphicPacksRootFragment extends Fragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + genericRecyclerViewAdapter.setPredicate(g -> g.hasInstalledTitleId); + graphicPacksRecyclerViewAdapter.setShowInstalledOnly(true); fillGraphicPacks(); var navController = NavHostFragment.findNavController(GraphicPacksRootFragment.this); graphicPackViewModel = new ViewModelProvider(Objects.requireNonNull(navController.getCurrentBackStackEntry())) @@ -173,13 +184,13 @@ public class GraphicPacksRootFragment extends Fragment { searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { - graphicPacksRecyclerViewAdapter.filter(query); + graphicPacksRecyclerViewAdapter.setFilterText(query); return true; } @Override public boolean onQueryTextChange(String newText) { - graphicPacksRecyclerViewAdapter.filter(newText); + graphicPacksRecyclerViewAdapter.setFilterText(newText); return true; } }); @@ -198,6 +209,14 @@ public class GraphicPacksRootFragment extends Fragment { }); } + private void showInstalledOnly(boolean installedOnly) { + if (installedOnly) { + genericRecyclerViewAdapter.setPredicate(g -> g.hasInstalledTitleId); + return; + } + genericRecyclerViewAdapter.clearPredicate(); + } + @Override public boolean onMenuItemSelected(@NonNull MenuItem menuItem) { if (menuItem.getItemId() == R.id.action_graphic_packs_download && !updateInProgress) { @@ -207,6 +226,13 @@ public class GraphicPacksRootFragment extends Fragment { if (menuItem.getItemId() == R.id.action_graphic_packs_search) { return true; } + if (menuItem.getItemId() == R.id.show_installed_games_only) { + boolean installedOnly = !menuItem.isChecked(); + graphicPacksRecyclerViewAdapter.setShowInstalledOnly(installedOnly); + menuItem.setChecked(installedOnly); + showInstalledOnly(installedOnly); + return true; + } return false; } }, getViewLifecycleOwner(), Lifecycle.State.RESUMED); @@ -214,12 +240,13 @@ public class GraphicPacksRootFragment extends Fragment { } private void fillGraphicPacks() { - var nativeGraphicPacks = NativeLibrary.getGraphicPackIdsAndVirtualPaths(); + var nativeGraphicPacks = NativeLibrary.getGraphicPackBasicInfos(); graphicPackSectionRootNode.clear(); List 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()); + boolean hasTitleIdInstalled = graphicPack.titleIds().stream().anyMatch(installedTitleIds::contains); + var dataNode = new GraphicPackDataNode(graphicPack.id(), tokens.get(tokens.size() - 1), graphicPack.virtualPath(), hasTitleIdInstalled); graphicPackDataNodes.add(dataNode); graphicPackSectionRootNode.addGraphicPackDataByTokens(tokens.subList(0, tokens.size() - 1), dataNode); }); @@ -232,6 +259,7 @@ public class GraphicPacksRootFragment extends Fragment { ? GraphicPackItemRecyclerViewItem.GraphicPackItemType.SECTION : GraphicPackItemRecyclerViewItem.GraphicPackItemType.GRAPHIC_PACK; GraphicPackItemRecyclerViewItem graphicPackItemRecyclerViewItem = new GraphicPackItemRecyclerViewItem(node.name, + node.hasTitleIdInstalled(), graphicPackItemType, () -> { graphicPackViewModel.setGraphicPackNode(node); diff --git a/src/android/app/src/main/res/menu/menu_graphic_packs.xml b/src/android/app/src/main/res/menu/menu_graphic_packs.xml index dd8b1b62..1c3ca347 100644 --- a/src/android/app/src/main/res/menu/menu_graphic_packs.xml +++ b/src/android/app/src/main/res/menu/menu_graphic_packs.xml @@ -1,6 +1,5 @@ + xmlns:app="http://schemas.android.com/apk/res-auto"> + + \ No newline at end of file diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index f1a5e166..8ebc8c06 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -163,4 +163,5 @@ Edit inputs Show pad Replace TV with PAD + Installed games \ No newline at end of file