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>