Android: Add the ability to add cheats

This commit is contained in:
JosJuice 2021-08-10 13:51:32 +02:00
parent 6934b9a21d
commit 404eb13e2f
14 changed files with 257 additions and 30 deletions

View File

@ -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;

View File

@ -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<Cheat> mSelectedCheat = new MutableLiveData<>(null);
private final MutableLiveData<Boolean> mIsAdding = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> mIsEditing = new MutableLiveData<>(false);
private final MutableLiveData<Integer> mCheatAddedEvent = new MutableLiveData<>(null);
private final MutableLiveData<Integer> mCheatChangedEvent = new MutableLiveData<>(null);
private final MutableLiveData<Boolean> mOpenDetailsViewEvent = new MutableLiveData<>(false);
private PatchCheat[] mPatchCheats;
private ARCheat[] mARCheats;
private GeckoCheat[] mGeckoCheats;
private ArrayList<PatchCheat> mPatchCheats;
private ArrayList<ARCheat> mARCheats;
private ArrayList<GeckoCheat> 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<Boolean> 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<Boolean> 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<Integer> 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<PatchCheat> getPatchCheats()
{
return mPatchCheats;
}
public ARCheat[] getARCheats()
public ArrayList<ARCheat> getARCheats()
{
return mARCheats;
}
public Cheat[] getGeckoCheats()
public ArrayList<GeckoCheat> getGeckoCheats()
{
return mGeckoCheats;
}

View File

@ -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;

View File

@ -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;

View File

@ -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();
}
}
}

View File

@ -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));

View File

@ -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;

View File

@ -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<CheatItemViewHolder>
{
@ -22,6 +26,12 @@ public class CheatsAdapter extends RecyclerView.Adapter<CheatItemViewHolder>
{
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<CheatItemViewHolder>
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<CheatItemViewHolder>
@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<CheatItemViewHolder>
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<PatchCheat> 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<ARCheat> 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<GeckoCheat> 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();
}

View File

@ -99,7 +99,7 @@ public final class SettingsAdapter extends RecyclerView.Adapter<SettingViewHolde
return new SliderViewHolder(view, this, mContext);
case SettingsItem.TYPE_SUBMENU:
view = inflater.inflate(R.layout.list_item_setting_submenu, parent, false);
view = inflater.inflate(R.layout.list_item_submenu, parent, false);
return new SubmenuViewHolder(view, this);
case SettingsItem.TYPE_INPUT_BINDING:

View File

@ -392,6 +392,9 @@
<string name="cheats_header_ar">AR Codes</string>
<string name="cheats_header_gecko">Gecko Codes</string>
<string name="cheats_header_patch">Patches</string>
<string name="cheats_add_ar">Add New AR Code</string>
<string name="cheats_add_gecko">Add New Gecko Code</string>
<string name="cheats_add_patch">Add New Patch</string>
<string name="cheats_name">Name</string>
<string name="cheats_creator">Creator</string>
<string name="cheats_notes">Notes</string>

View File

@ -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<jlong>(code);
}
JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getName(JNIEnv* env, jobject obj)
{

View File

@ -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<jlong>(code);
}
JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getName(JNIEnv* env, jobject obj)
{

View File

@ -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<jlong>(patch);
}
JNIEXPORT jstring JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getName(JNIEnv* env, jobject obj)
{