Android: Add edit button for cheats

This commit is contained in:
JosJuice 2021-08-07 16:08:07 +02:00
parent a303b4bc98
commit 43dcbf33ad
14 changed files with 253 additions and 27 deletions

View file

@ -22,8 +22,13 @@ public class ARCheat extends AbstractCheat
@NonNull
public native String getName();
public native boolean getUserDefined();
public native boolean getEnabled();
@Override
protected native int trySetImpl(@NonNull String name);
@Override
protected native void setEnabledImpl(boolean enabled);

View file

@ -2,12 +2,26 @@
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public abstract class AbstractCheat implements Cheat
{
private Runnable mChangedCallback = null;
public int trySet(@NonNull String name)
{
if (name.isEmpty())
return TRY_SET_FAIL_NO_NAME;
int result = trySetImpl(name);
if (result == TRY_SET_SUCCESS)
onChanged();
return result;
}
public void setEnabled(boolean enabled)
{
setEnabledImpl(enabled);
@ -25,5 +39,7 @@ public abstract class AbstractCheat implements Cheat
mChangedCallback.run();
}
protected abstract int trySetImpl(@NonNull String name);
protected abstract void setEnabledImpl(boolean enabled);
}

View file

@ -3,13 +3,23 @@
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public interface Cheat
{
int TRY_SET_FAIL_NO_NAME = -1;
int TRY_SET_SUCCESS = 0;
@NonNull
String getName();
int trySet(@NonNull String name);
boolean getUserDefined();
boolean getEnabled();
void setEnabled(boolean enabled);
void setChangedCallback(@Nullable Runnable callback);
}

View file

@ -11,6 +11,7 @@ public class CheatsViewModel extends ViewModel
private boolean mLoaded = false;
private final MutableLiveData<Cheat> mSelectedCheat = new MutableLiveData<>(null);
private final MutableLiveData<Boolean> mIsEditing = new MutableLiveData<>(false);
private final MutableLiveData<Boolean> mOpenDetailsViewEvent = new MutableLiveData<>(false);
@ -75,9 +76,22 @@ public class CheatsViewModel extends ViewModel
public void setSelectedCheat(Cheat cheat)
{
if (mIsEditing.getValue())
setIsEditing(false);
mSelectedCheat.setValue(cheat);
}
public LiveData<Boolean> getIsEditing()
{
return mIsEditing;
}
public void setIsEditing(boolean isEditing)
{
mIsEditing.setValue(isEditing);
}
public LiveData<Boolean> getOpenDetailsViewEvent()
{
return mOpenDetailsViewEvent;

View file

@ -22,8 +22,13 @@ public class GeckoCheat extends AbstractCheat
@NonNull
public native String getName();
public native boolean getUserDefined();
public native boolean getEnabled();
@Override
protected native int trySetImpl(@NonNull String name);
@Override
protected native void setEnabledImpl(boolean enabled);

View file

@ -22,8 +22,13 @@ public class PatchCheat extends AbstractCheat
@NonNull
public native String getName();
public native boolean getUserDefined();
public native boolean getEnabled();
@Override
protected native int trySetImpl(@NonNull String name);
@Override
protected native void setEnabledImpl(boolean enabled);

View file

@ -6,6 +6,7 @@ import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import androidx.annotation.NonNull;
@ -22,6 +23,9 @@ public class CheatDetailsFragment extends Fragment
{
private View mRoot;
private EditText mEditName;
private Button mButtonEdit;
private Button mButtonCancel;
private Button mButtonOk;
private CheatsViewModel mViewModel;
private Cheat mCheat;
@ -39,24 +43,74 @@ public class CheatDetailsFragment extends Fragment
{
mRoot = view.findViewById(R.id.root);
mEditName = view.findViewById(R.id.edit_name);
mButtonEdit = view.findViewById(R.id.button_edit);
mButtonCancel = view.findViewById(R.id.button_cancel);
mButtonOk = view.findViewById(R.id.button_ok);
CheatsActivity activity = (CheatsActivity) requireActivity();
mViewModel = new ViewModelProvider(activity).get(CheatsViewModel.class);
LiveData<Cheat> selectedCheat = mViewModel.getSelectedCheat();
selectedCheat.observe(getViewLifecycleOwner(), this::populateFields);
populateFields(selectedCheat.getValue());
mViewModel.getSelectedCheat().observe(getViewLifecycleOwner(), this::onSelectedCheatUpdated);
mViewModel.getIsEditing().observe(getViewLifecycleOwner(), this::onIsEditingUpdated);
mButtonEdit.setOnClickListener((v) -> mViewModel.setIsEditing(true));
mButtonCancel.setOnClickListener((v) ->
{
mViewModel.setIsEditing(false);
onSelectedCheatUpdated(mCheat);
});
mButtonOk.setOnClickListener(this::onOkClicked);
}
private void populateFields(@Nullable Cheat cheat)
private void clearEditErrors()
{
mEditName.setError(null);
}
private void onOkClicked(View view)
{
clearEditErrors();
int result = mCheat.trySet(mEditName.getText().toString());
switch (result)
{
case Cheat.TRY_SET_SUCCESS:
mViewModel.setIsEditing(false);
break;
case Cheat.TRY_SET_FAIL_NO_NAME:
mEditName.setError(getText(R.string.cheats_error_no_name));
break;
}
}
private void onSelectedCheatUpdated(@Nullable Cheat cheat)
{
clearEditErrors();
mRoot.setVisibility(cheat == null ? View.GONE : View.VISIBLE);
if (cheat != null && cheat != mCheat)
boolean userDefined = cheat != null && cheat.getUserDefined();
mButtonEdit.setEnabled(userDefined);
// If the fragment was recreated while editing a cheat, it's vital that we
// don't repopulate the fields, otherwise the user's changes will be lost
boolean isEditing = mViewModel.getIsEditing().getValue();
if (!isEditing && cheat != null)
{
mEditName.setText(cheat.getName());
}
mCheat = cheat;
}
private void onIsEditingUpdated(boolean isEditing)
{
mEditName.setEnabled(isEditing);
mButtonEdit.setVisibility(isEditing ? View.GONE : View.VISIBLE);
mButtonCancel.setVisibility(isEditing ? View.VISIBLE : View.GONE);
mButtonOk.setVisibility(isEditing ? View.VISIBLE : View.GONE);
}
}

View file

@ -5,35 +5,91 @@
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/root"
android:layout_width="match_parent"
android:layout_height="wrap_content">
android:layout_height="match_parent">
<TextView
android:id="@+id/label_name"
<ScrollView
android:id="@+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Headline"
android:textSize="18sp"
android:text="@string/cheats_name"
android:layout_margin="@dimen/spacing_large"
android:labelFor="@id/edit_name"
android:layout_height="0dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/edit_name" />
app:layout_constraintBottom_toTopOf="@id/barrier">
<EditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/label_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/TextAppearance.AppCompat.Headline"
android:textSize="18sp"
android:text="@string/cheats_name"
android:layout_margin="@dimen/spacing_large"
android:labelFor="@id/edit_name"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/edit_name" />
<EditText
android:id="@+id/edit_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginHorizontal="@dimen/spacing_large"
android:importantForAutofill="no"
android:inputType="text"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/label_name"
app:layout_constraintBottom_toBottomOf="parent"
tools:text="Hyrule Field Speed Hack" />
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
<androidx.constraintlayout.widget.Barrier
android:id="@+id/barrier"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginHorizontal="@dimen/spacing_large"
android:importantForAutofill="no"
android:inputType="text"
android:enabled="false"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:barrierDirection="top"
app:constraint_referenced_ids="button_edit,button_cancel,button_ok" />
<Button
android:id="@+id/button_edit"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_large"
android:text="@string/cheats_edit"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/label_name"
tools:text="Hyrule Field Speed Hack" />
app:layout_constraintEnd_toStartOf="@id/button_cancel"
app:layout_constraintTop_toBottomOf="@id/barrier"
app:layout_constraintBottom_toBottomOf="parent" />
<Button
android:id="@+id/button_cancel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_large"
android:text="@string/cancel"
app:layout_constraintStart_toEndOf="@id/button_edit"
app:layout_constraintEnd_toStartOf="@id/button_ok"
app:layout_constraintTop_toBottomOf="@id/barrier"
app:layout_constraintBottom_toBottomOf="parent" />
<Button
android:id="@+id/button_ok"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="@dimen/spacing_large"
android:text="@string/ok"
app:layout_constraintStart_toEndOf="@id/button_cancel"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -390,6 +390,8 @@
<string name="cheats">Cheats</string>
<string name="cheats_with_game_id">Cheats: %1$s</string>
<string name="cheats_name">Name</string>
<string name="cheats_edit">Edit</string>
<string name="cheats_error_no_name">Name can\'t be empty</string>
<!-- Convert Screen -->
<string name="convert_format">Format</string>

View file

@ -1,5 +1,6 @@
add_library(main SHARED
Cheats/ARCheat.cpp
Cheats/Cheats.h
Cheats/GeckoCheat.cpp
Cheats/PatchCheat.cpp
Config/NativeConfig.cpp

View file

@ -12,6 +12,7 @@
#include "Core/ConfigManager.h"
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
#include "jni/Cheats/Cheats.h"
static ActionReplay::ARCode* GetPointer(JNIEnv* env, jobject obj)
{
@ -39,12 +40,28 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getName(JNIEnv* env
return ToJString(env, GetPointer(env, obj)->name);
}
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getUserDefined(JNIEnv* env,
jobject obj)
{
return static_cast<jboolean>(GetPointer(env, obj)->user_defined);
}
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getEnabled(JNIEnv* env, jobject obj)
{
return static_cast<jboolean>(GetPointer(env, obj)->enabled);
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_trySetImpl(
JNIEnv* env, jobject obj, jstring name)
{
ActionReplay::ARCode* code = GetPointer(env, obj);
code->name = GetJString(env, name);
return TRY_SET_SUCCESS;
}
JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_setEnabledImpl(
JNIEnv* env, jobject obj, jboolean enabled)
{

View file

@ -0,0 +1,7 @@
// Copyright 2021 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
constexpr int TRY_SET_FAIL_NO_NAME = -1;
constexpr int TRY_SET_SUCCESS = 0;

View file

@ -13,6 +13,7 @@
#include "Core/GeckoCodeConfig.h"
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
#include "jni/Cheats/Cheats.h"
static Gecko::GeckoCode* GetPointer(JNIEnv* env, jobject obj)
{
@ -40,12 +41,28 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getName(JNIEnv*
return ToJString(env, GetPointer(env, obj)->name);
}
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getUserDefined(JNIEnv* env,
jobject obj)
{
return static_cast<jboolean>(GetPointer(env, obj)->user_defined);
}
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_getEnabled(JNIEnv* env, jobject obj)
{
return static_cast<jboolean>(GetPointer(env, obj)->enabled);
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_trySetImpl(
JNIEnv* env, jobject obj, jstring name)
{
Gecko::GeckoCode* code = GetPointer(env, obj);
code->name = GetJString(env, name);
return TRY_SET_SUCCESS;
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_setEnabledImpl(JNIEnv* env,
jobject obj,

View file

@ -12,6 +12,7 @@
#include "Core/PatchEngine.h"
#include "jni/AndroidCommon/AndroidCommon.h"
#include "jni/AndroidCommon/IDCache.h"
#include "jni/Cheats/Cheats.h"
static PatchEngine::Patch* GetPointer(JNIEnv* env, jobject obj)
{
@ -39,12 +40,28 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getName(JNIEnv*
return ToJString(env, GetPointer(env, obj)->name);
}
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getUserDefined(JNIEnv* env,
jobject obj)
{
return static_cast<jboolean>(GetPointer(env, obj)->user_defined);
}
JNIEXPORT jboolean JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getEnabled(JNIEnv* env, jobject obj)
{
return static_cast<jboolean>(GetPointer(env, obj)->enabled);
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_trySetImpl(
JNIEnv* env, jobject obj, jstring name)
{
PatchEngine::Patch* patch = GetPointer(env, obj);
patch->name = GetJString(env, name);
return TRY_SET_SUCCESS;
}
JNIEXPORT void JNICALL
Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_setEnabledImpl(JNIEnv* env,
jobject obj,