From 404eb13e2f5252739a4a778bc549abc1867f284e Mon Sep 17 00:00:00 2001 From: JosJuice Date: Tue, 10 Aug 2021 13:51:32 +0200 Subject: [PATCH] Android: Add the ability to add cheats --- .../features/cheats/model/ARCheat.java | 7 ++ .../cheats/model/CheatsViewModel.java | 103 ++++++++++++++++-- .../features/cheats/model/GeckoCheat.java | 7 ++ .../features/cheats/model/PatchCheat.java | 7 ++ .../features/cheats/ui/ActionViewHolder.java | 60 ++++++++++ .../cheats/ui/CheatDetailsFragment.java | 12 +- .../features/cheats/ui/CheatItem.java | 1 + .../features/cheats/ui/CheatsAdapter.java | 61 ++++++++--- .../features/settings/ui/SettingsAdapter.java | 2 +- ...ting_submenu.xml => list_item_submenu.xml} | 0 .../app/src/main/res/values/strings.xml | 3 + Source/Android/jni/Cheats/ARCheat.cpp | 8 ++ Source/Android/jni/Cheats/GeckoCheat.cpp | 8 ++ Source/Android/jni/Cheats/PatchCheat.cpp | 8 ++ 14 files changed, 257 insertions(+), 30 deletions(-) create mode 100644 Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.java rename Source/Android/app/src/main/res/layout/{list_item_setting_submenu.xml => list_item_submenu.xml} (100%) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java index 24b74b489a..8a168e8bc5 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java @@ -10,6 +10,11 @@ public class ARCheat extends AbstractCheat @Keep private final long mPointer; + public ARCheat() + { + mPointer = createNew(); + } + @Keep private ARCheat(long pointer) { @@ -19,6 +24,8 @@ public class ARCheat extends AbstractCheat @Override public native void finalize(); + private native long createNew(); + public boolean supportsCreator() { return false; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java index cdd68221c6..f405c427a5 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java @@ -6,20 +6,25 @@ import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import java.util.ArrayList; +import java.util.Collections; + public class CheatsViewModel extends ViewModel { private boolean mLoaded = false; private int mSelectedCheatPosition = -1; private final MutableLiveData mSelectedCheat = new MutableLiveData<>(null); + private final MutableLiveData mIsAdding = new MutableLiveData<>(false); private final MutableLiveData mIsEditing = new MutableLiveData<>(false); + private final MutableLiveData mCheatAddedEvent = new MutableLiveData<>(null); private final MutableLiveData mCheatChangedEvent = new MutableLiveData<>(null); private final MutableLiveData mOpenDetailsViewEvent = new MutableLiveData<>(false); - private PatchCheat[] mPatchCheats; - private ARCheat[] mARCheats; - private GeckoCheat[] mGeckoCheats; + private ArrayList mPatchCheats; + private ArrayList mARCheats; + private ArrayList mGeckoCheats; private boolean mPatchCheatsNeedSaving = false; private boolean mARCheatsNeedSaving = false; @@ -30,9 +35,12 @@ public class CheatsViewModel extends ViewModel if (mLoaded) return; - mPatchCheats = PatchCheat.loadCodes(gameID, revision); - mARCheats = ARCheat.loadCodes(gameID, revision); - mGeckoCheats = GeckoCheat.loadCodes(gameID, revision); + mPatchCheats = new ArrayList<>(); + Collections.addAll(mPatchCheats, PatchCheat.loadCodes(gameID, revision)); + mARCheats = new ArrayList<>(); + Collections.addAll(mARCheats, ARCheat.loadCodes(gameID, revision)); + mGeckoCheats = new ArrayList<>(); + Collections.addAll(mGeckoCheats, GeckoCheat.loadCodes(gameID, revision)); for (PatchCheat cheat : mPatchCheats) { @@ -54,19 +62,19 @@ public class CheatsViewModel extends ViewModel { if (mPatchCheatsNeedSaving) { - PatchCheat.saveCodes(gameID, revision, mPatchCheats); + PatchCheat.saveCodes(gameID, revision, mPatchCheats.toArray(new PatchCheat[0])); mPatchCheatsNeedSaving = false; } if (mARCheatsNeedSaving) { - ARCheat.saveCodes(gameID, revision, mARCheats); + ARCheat.saveCodes(gameID, revision, mARCheats.toArray(new ARCheat[0])); mARCheatsNeedSaving = false; } if (mGeckoCheatsNeedSaving) { - GeckoCheat.saveCodes(gameID, revision, mGeckoCheats); + GeckoCheat.saveCodes(gameID, revision, mGeckoCheats.toArray(new GeckoCheat[0])); mGeckoCheatsNeedSaving = false; } } @@ -85,6 +93,56 @@ public class CheatsViewModel extends ViewModel mSelectedCheatPosition = position; } + public LiveData getIsAdding() + { + return mIsAdding; + } + + public void startAddingCheat(Cheat cheat, int position) + { + mSelectedCheat.setValue(cheat); + mSelectedCheatPosition = position; + + mIsAdding.setValue(true); + mIsEditing.setValue(true); + } + + public void finishAddingCheat() + { + if (!mIsAdding.getValue()) + throw new IllegalStateException(); + + mIsAdding.setValue(false); + mIsEditing.setValue(false); + + Cheat cheat = mSelectedCheat.getValue(); + + if (cheat instanceof PatchCheat) + { + mPatchCheats.add((PatchCheat) mSelectedCheat.getValue()); + cheat.setChangedCallback(() -> mPatchCheatsNeedSaving = true); + mPatchCheatsNeedSaving = true; + } + else if (cheat instanceof ARCheat) + { + mARCheats.add((ARCheat) mSelectedCheat.getValue()); + cheat.setChangedCallback(() -> mPatchCheatsNeedSaving = true); + mARCheatsNeedSaving = true; + } + else if (cheat instanceof GeckoCheat) + { + mGeckoCheats.add((GeckoCheat) mSelectedCheat.getValue()); + cheat.setChangedCallback(() -> mGeckoCheatsNeedSaving = true); + mGeckoCheatsNeedSaving = true; + } + else + { + throw new UnsupportedOperationException(); + } + + notifyCheatAdded(); + } + public LiveData getIsEditing() { return mIsEditing; @@ -93,6 +151,27 @@ public class CheatsViewModel extends ViewModel public void setIsEditing(boolean isEditing) { mIsEditing.setValue(isEditing); + + if (mIsAdding.getValue() && !isEditing) + { + mIsAdding.setValue(false); + setSelectedCheat(null, -1); + } + } + + /** + * When a cheat is added, the integer stored in the returned LiveData + * changes to the position of that cheat, then changes back to null. + */ + public LiveData getCheatAddedEvent() + { + return mCheatAddedEvent; + } + + private void notifyCheatAdded() + { + mCheatAddedEvent.setValue(mSelectedCheatPosition); + mCheatAddedEvent.setValue(null); } /** @@ -132,17 +211,17 @@ public class CheatsViewModel extends ViewModel mOpenDetailsViewEvent.setValue(false); } - public Cheat[] getPatchCheats() + public ArrayList getPatchCheats() { return mPatchCheats; } - public ARCheat[] getARCheats() + public ArrayList getARCheats() { return mARCheats; } - public Cheat[] getGeckoCheats() + public ArrayList getGeckoCheats() { return mGeckoCheats; } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java index 1847daa40d..19573723b6 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java @@ -10,6 +10,11 @@ public class GeckoCheat extends AbstractCheat @Keep private final long mPointer; + public GeckoCheat() + { + mPointer = createNew(); + } + @Keep private GeckoCheat(long pointer) { @@ -19,6 +24,8 @@ public class GeckoCheat extends AbstractCheat @Override public native void finalize(); + private native long createNew(); + public boolean supportsCreator() { return true; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java index 52d88413dd..9dcb9f8111 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java @@ -10,6 +10,11 @@ public class PatchCheat extends AbstractCheat @Keep private final long mPointer; + public PatchCheat() + { + mPointer = createNew(); + } + @Keep private PatchCheat(long pointer) { @@ -19,6 +24,8 @@ public class PatchCheat extends AbstractCheat @Override public native void finalize(); + private native long createNew(); + public boolean supportsCreator() { return false; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.java new file mode 100644 index 0000000000..72380f8ab1 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.java @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +package org.dolphinemu.dolphinemu.features.cheats.ui; + +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import org.dolphinemu.dolphinemu.R; +import org.dolphinemu.dolphinemu.features.cheats.model.ARCheat; +import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel; +import org.dolphinemu.dolphinemu.features.cheats.model.GeckoCheat; +import org.dolphinemu.dolphinemu.features.cheats.model.PatchCheat; + +public class ActionViewHolder extends CheatItemViewHolder implements View.OnClickListener +{ + private final TextView mName; + + private CheatsViewModel mViewModel; + private int mString; + private int mPosition; + + public ActionViewHolder(@NonNull View itemView) + { + super(itemView); + + mName = itemView.findViewById(R.id.text_setting_name); + + itemView.setOnClickListener(this); + } + + public void bind(CheatsViewModel viewModel, CheatItem item, int position) + { + mViewModel = viewModel; + mString = item.getString(); + mPosition = position; + + mName.setText(mString); + } + + public void onClick(View root) + { + if (mString == R.string.cheats_add_ar) + { + mViewModel.startAddingCheat(new ARCheat(), mPosition); + mViewModel.openDetailsView(); + } + else if (mString == R.string.cheats_add_gecko) + { + mViewModel.startAddingCheat(new GeckoCheat(), mPosition); + mViewModel.openDetailsView(); + } + else if (mString == R.string.cheats_add_patch) + { + mViewModel.startAddingCheat(new PatchCheat(), mPosition); + mViewModel.openDetailsView(); + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java index 2a2896251a..3de19df37a 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java @@ -94,8 +94,16 @@ public class CheatDetailsFragment extends Fragment switch (result) { case Cheat.TRY_SET_SUCCESS: - mViewModel.notifySelectedCheatChanged(); - mViewModel.setIsEditing(false); + if (mViewModel.getIsAdding().getValue()) + { + mViewModel.finishAddingCheat(); + onSelectedCheatUpdated(mCheat); + } + else + { + mViewModel.notifySelectedCheatChanged(); + mViewModel.setIsEditing(false); + } break; case Cheat.TRY_SET_FAIL_NO_NAME: mEditName.setError(getString(R.string.cheats_error_no_name)); diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.java index dd3ee76ab0..b293cd84d1 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.java @@ -11,6 +11,7 @@ public class CheatItem { public static final int TYPE_CHEAT = 0; public static final int TYPE_HEADER = 1; + public static final int TYPE_ACTION = 2; private final @Nullable Cheat mCheat; private final int mString; diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java index f0431c29a6..3406e1b692 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java @@ -11,8 +11,12 @@ import androidx.lifecycle.LifecycleOwner; import androidx.recyclerview.widget.RecyclerView; import org.dolphinemu.dolphinemu.R; -import org.dolphinemu.dolphinemu.features.cheats.model.Cheat; +import org.dolphinemu.dolphinemu.features.cheats.model.ARCheat; import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel; +import org.dolphinemu.dolphinemu.features.cheats.model.GeckoCheat; +import org.dolphinemu.dolphinemu.features.cheats.model.PatchCheat; + +import java.util.ArrayList; public class CheatsAdapter extends RecyclerView.Adapter { @@ -22,6 +26,12 @@ public class CheatsAdapter extends RecyclerView.Adapter { mViewModel = viewModel; + mViewModel.getCheatAddedEvent().observe(owner, (position) -> + { + if (position != null) + notifyItemInserted(position); + }); + mViewModel.getCheatChangedEvent().observe(owner, (position) -> { if (position != null) @@ -43,6 +53,9 @@ public class CheatsAdapter extends RecyclerView.Adapter case CheatItem.TYPE_HEADER: View headerView = inflater.inflate(R.layout.list_item_header, parent, false); return new HeaderViewHolder(headerView); + case CheatItem.TYPE_ACTION: + View actionView = inflater.inflate(R.layout.list_item_submenu, parent, false); + return new ActionViewHolder(actionView); default: throw new UnsupportedOperationException(); } @@ -57,8 +70,8 @@ public class CheatsAdapter extends RecyclerView.Adapter @Override public int getItemCount() { - return mViewModel.getARCheats().length + mViewModel.getGeckoCheats().length + - mViewModel.getPatchCheats().length + 3; + return mViewModel.getARCheats().size() + mViewModel.getGeckoCheats().size() + + mViewModel.getPatchCheats().size() + 6; } @Override @@ -69,32 +82,50 @@ public class CheatsAdapter extends RecyclerView.Adapter private CheatItem getItemAt(int position) { + // Patches + if (position == 0) return new CheatItem(CheatItem.TYPE_HEADER, R.string.cheats_header_patch); position -= 1; - Cheat[] patchCheats = mViewModel.getPatchCheats(); - if (position < patchCheats.length) - return new CheatItem(patchCheats[position]); - position -= patchCheats.length; + ArrayList patchCheats = mViewModel.getPatchCheats(); + if (position < patchCheats.size()) + return new CheatItem(patchCheats.get(position)); + position -= patchCheats.size(); + + if (position == 0) + return new CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_add_patch); + position -= 1; + + // AR codes if (position == 0) return new CheatItem(CheatItem.TYPE_HEADER, R.string.cheats_header_ar); position -= 1; - Cheat[] arCheats = mViewModel.getARCheats(); - if (position < arCheats.length) - return new CheatItem(arCheats[position]); - position -= arCheats.length; + ArrayList arCheats = mViewModel.getARCheats(); + if (position < arCheats.size()) + return new CheatItem(arCheats.get(position)); + position -= arCheats.size(); + + if (position == 0) + return new CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_add_ar); + position -= 1; + + // Gecko codes if (position == 0) return new CheatItem(CheatItem.TYPE_HEADER, R.string.cheats_header_gecko); position -= 1; - Cheat[] geckoCheats = mViewModel.getGeckoCheats(); - if (position < geckoCheats.length) - return new CheatItem(geckoCheats[position]); - position -= geckoCheats.length; + ArrayList geckoCheats = mViewModel.getGeckoCheats(); + if (position < geckoCheats.size()) + return new CheatItem(geckoCheats.get(position)); + position -= geckoCheats.size(); + + if (position == 0) + return new CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_add_gecko); + position -= 1; throw new IndexOutOfBoundsException(); } diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java index e216330280..753dddf522 100644 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsAdapter.java @@ -99,7 +99,7 @@ public final class SettingsAdapter extends RecyclerView.AdapterAR Codes Gecko Codes Patches + Add New AR Code + Add New Gecko Code + Add New Patch Name Creator Notes diff --git a/Source/Android/jni/Cheats/ARCheat.cpp b/Source/Android/jni/Cheats/ARCheat.cpp index 895fe68f7d..14da758539 100644 --- a/Source/Android/jni/Cheats/ARCheat.cpp +++ b/Source/Android/jni/Cheats/ARCheat.cpp @@ -36,6 +36,14 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_finalize(JNIEnv* en delete GetPointer(env, obj); } +JNIEXPORT jlong JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_createNew(JNIEnv* env, jobject obj) +{ + auto* code = new ActionReplay::ARCode; + code->user_defined = true; + return reinterpret_cast(code); +} + JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getName(JNIEnv* env, jobject obj) { diff --git a/Source/Android/jni/Cheats/GeckoCheat.cpp b/Source/Android/jni/Cheats/GeckoCheat.cpp index cb65e392c1..1765715581 100644 --- a/Source/Android/jni/Cheats/GeckoCheat.cpp +++ b/Source/Android/jni/Cheats/GeckoCheat.cpp @@ -35,6 +35,14 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_finalize(JNIEnv* delete GetPointer(env, obj); } +JNIEXPORT jlong JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_createNew(JNIEnv* env, jobject obj) +{ + auto* code = new Gecko::GeckoCode; + code->user_defined = true; + return reinterpret_cast(code); +} + JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getName(JNIEnv* env, jobject obj) { diff --git a/Source/Android/jni/Cheats/PatchCheat.cpp b/Source/Android/jni/Cheats/PatchCheat.cpp index 8d093c0d39..37a65b21aa 100644 --- a/Source/Android/jni/Cheats/PatchCheat.cpp +++ b/Source/Android/jni/Cheats/PatchCheat.cpp @@ -34,6 +34,14 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_finalize(JNIEnv* delete GetPointer(env, obj); } +JNIEXPORT jlong JNICALL +Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_createNew(JNIEnv* env, jobject obj) +{ + auto* patch = new PatchEngine::Patch; + patch->user_defined = true; + return reinterpret_cast(patch); +} + JNIEXPORT jstring JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getName(JNIEnv* env, jobject obj) {