Merge pull request #11614 from t895/kotlin-cheats

Android: Convert Cheats Activity to Kotlin
This commit is contained in:
Mai 2023-03-02 15:04:19 -05:00 committed by GitHub
commit 78e8669bac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1511 additions and 1917 deletions

View File

@ -1,60 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
public class ARCheat extends AbstractCheat
{
@Keep
private final long mPointer;
public ARCheat()
{
mPointer = createNew();
}
@Keep
private ARCheat(long pointer)
{
mPointer = pointer;
}
@Override
public native void finalize();
private native long createNew();
public boolean supportsCreator()
{
return false;
}
public boolean supportsNotes()
{
return false;
}
@NonNull
public native String getName();
@NonNull
public native String getCode();
public native boolean getUserDefined();
public native boolean getEnabled();
@Override
protected native int trySetImpl(@NonNull String name, @NonNull String creator,
@NonNull String notes, @NonNull String code);
@Override
protected native void setEnabledImpl(boolean enabled);
@NonNull
public static native ARCheat[] loadCodes(String gameId, int revision);
public static native void saveCodes(String gameId, int revision, ARCheat[] codes);
}

View File

@ -0,0 +1,52 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model
import androidx.annotation.Keep
class ARCheat : AbstractCheat {
@Keep
private val pointer: Long
constructor() {
pointer = createNew()
}
@Keep
private constructor(pointer: Long) {
this.pointer = pointer
}
external fun finalize()
private external fun createNew(): Long
override fun supportsCreator(): Boolean = false
override fun supportsNotes(): Boolean = false
external override fun getName(): String
external override fun getCode(): String
external override fun getUserDefined(): Boolean
external override fun getEnabled(): Boolean
external override fun setEnabledImpl(enabled: Boolean)
external override fun setCheatImpl(
name: String,
creator: String,
notes: String,
code: String
): Int
companion object {
@JvmStatic
external fun loadCodes(gameId: String, revision: Int): Array<ARCheat>
@JvmStatic
external fun saveCodes(gameId: String, revision: Int, codes: Array<ARCheat>)
}
}

View File

@ -1,45 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.NonNull;
public abstract class AbstractCheat extends ReadOnlyCheat
{
public boolean supportsCode()
{
return true;
}
public int trySet(@NonNull String name, @NonNull String creator, @NonNull String notes,
@NonNull String code)
{
if (!code.isEmpty() && code.charAt(0) == '$')
{
int firstLineEnd = code.indexOf('\n');
if (firstLineEnd == -1)
{
name = code.substring(1);
code = "";
}
else
{
name = code.substring(1, firstLineEnd);
code = code.substring(firstLineEnd + 1);
}
}
if (name.isEmpty())
return TRY_SET_FAIL_NO_NAME;
int result = trySetImpl(name, creator, notes, code);
if (result == TRY_SET_SUCCESS)
onChanged();
return result;
}
protected abstract int trySetImpl(@NonNull String name, @NonNull String creator,
@NonNull String notes, @NonNull String code);
}

View File

@ -0,0 +1,47 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat.Companion.TRY_SET_FAIL_NO_NAME
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat.Companion.TRY_SET_SUCCESS
abstract class AbstractCheat : ReadOnlyCheat() {
override fun supportsCode(): Boolean {
return true
}
override fun setCheat(
name: String,
creator: String,
notes: String,
code: String
): Int {
var finalName = name
var finalCode = code
if (finalCode.isNotEmpty() && finalCode[0] == '$') {
val firstLineEnd = finalCode.indexOf('\n')
if (firstLineEnd == -1) {
finalName = finalCode.substring(1)
finalCode = ""
} else {
finalName = finalCode.substring(1, firstLineEnd)
finalCode = finalCode.substring(firstLineEnd + 1)
}
}
if (finalName.isEmpty()) return TRY_SET_FAIL_NO_NAME
val result = setCheatImpl(finalName, creator, notes, finalCode)
if (result == TRY_SET_SUCCESS) onChanged()
return result
}
protected abstract fun setCheatImpl(
name: String,
creator: String,
notes: String,
code: String
): Int
}

View File

@ -1,53 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public interface Cheat
{
int TRY_SET_FAIL_CODE_MIXED_ENCRYPTION = -3;
int TRY_SET_FAIL_NO_CODE_LINES = -2;
int TRY_SET_FAIL_NO_NAME = -1;
int TRY_SET_SUCCESS = 0;
// Result codes greater than 0 represent an error on the corresponding code line (one-indexed)
boolean supportsCreator();
boolean supportsNotes();
boolean supportsCode();
@NonNull
String getName();
@NonNull
default String getCreator()
{
return "";
}
@NonNull
default String getNotes()
{
return "";
}
@NonNull
default String getCode()
{
return "";
}
int trySet(@NonNull String name, @NonNull String creator, @NonNull String notes,
@NonNull String code);
boolean getUserDefined();
boolean getEnabled();
void setEnabled(boolean enabled);
void setChangedCallback(@Nullable Runnable callback);
}

View File

@ -0,0 +1,42 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model
interface Cheat {
fun supportsCreator(): Boolean
fun supportsNotes(): Boolean
fun supportsCode(): Boolean
fun getName(): String = ""
fun getCreator(): String = ""
fun getNotes(): String = ""
fun getCode(): String = ""
fun setCheat(
name: String,
creator: String,
notes: String,
code: String
): Int
fun getUserDefined(): Boolean
fun getEnabled(): Boolean
fun setEnabled(isChecked: Boolean)
fun setChangedCallback(callback: Runnable?)
companion object {
// Result codes greater than 0 represent an error on the corresponding code line (one-indexed)
const val TRY_SET_FAIL_CODE_MIXED_ENCRYPTION = -3
const val TRY_SET_FAIL_NO_CODE_LINES = -2
const val TRY_SET_FAIL_NO_NAME = -1
const val TRY_SET_SUCCESS = 0
}
}

View File

@ -1,321 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model;
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<Integer> mCheatDeletedEvent = new MutableLiveData<>(null);
private final MutableLiveData<Integer> mGeckoCheatsDownloadedEvent = new MutableLiveData<>(null);
private final MutableLiveData<Boolean> mOpenDetailsViewEvent = new MutableLiveData<>(false);
private GraphicsModGroup mGraphicsModGroup;
private ArrayList<GraphicsMod> mGraphicsMods;
private ArrayList<PatchCheat> mPatchCheats;
private ArrayList<ARCheat> mARCheats;
private ArrayList<GeckoCheat> mGeckoCheats;
private boolean mGraphicsModsNeedSaving = false;
private boolean mPatchCheatsNeedSaving = false;
private boolean mARCheatsNeedSaving = false;
private boolean mGeckoCheatsNeedSaving = false;
public void load(String gameID, int revision)
{
if (mLoaded)
return;
mGraphicsModGroup = GraphicsModGroup.load(gameID);
mGraphicsMods = new ArrayList<>();
Collections.addAll(mGraphicsMods, mGraphicsModGroup.getMods());
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 (GraphicsMod mod : mGraphicsMods)
{
mod.setChangedCallback(() -> mGraphicsModsNeedSaving = true);
}
for (PatchCheat cheat : mPatchCheats)
{
cheat.setChangedCallback(() -> mPatchCheatsNeedSaving = true);
}
for (ARCheat cheat : mARCheats)
{
cheat.setChangedCallback(() -> mARCheatsNeedSaving = true);
}
for (GeckoCheat cheat : mGeckoCheats)
{
cheat.setChangedCallback(() -> mGeckoCheatsNeedSaving = true);
}
mLoaded = true;
}
public void saveIfNeeded(String gameID, int revision)
{
if (mGraphicsModsNeedSaving)
{
mGraphicsModGroup.save();
mGraphicsModsNeedSaving = false;
}
if (mPatchCheatsNeedSaving)
{
PatchCheat.saveCodes(gameID, revision, mPatchCheats.toArray(new PatchCheat[0]));
mPatchCheatsNeedSaving = false;
}
if (mARCheatsNeedSaving)
{
ARCheat.saveCodes(gameID, revision, mARCheats.toArray(new ARCheat[0]));
mARCheatsNeedSaving = false;
}
if (mGeckoCheatsNeedSaving)
{
GeckoCheat.saveCodes(gameID, revision, mGeckoCheats.toArray(new GeckoCheat[0]));
mGeckoCheatsNeedSaving = false;
}
}
public LiveData<Cheat> getSelectedCheat()
{
return mSelectedCheat;
}
public void setSelectedCheat(Cheat cheat, int position)
{
if (mIsEditing.getValue())
setIsEditing(false);
mSelectedCheat.setValue(cheat);
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;
}
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);
}
/**
* When a cheat is edited, the integer stored in the returned LiveData
* changes to the position of that cheat, then changes back to null.
*/
public LiveData<Integer> getCheatChangedEvent()
{
return mCheatChangedEvent;
}
/**
* Notifies that an edit has been made to the contents of the currently selected cheat.
*/
public void notifySelectedCheatChanged()
{
notifyCheatChanged(mSelectedCheatPosition);
}
/**
* Notifies that an edit has been made to the contents of the cheat at the given position.
*/
public void notifyCheatChanged(int position)
{
mCheatChangedEvent.setValue(position);
mCheatChangedEvent.setValue(null);
}
/**
* When a cheat is deleted, the integer stored in the returned LiveData
* changes to the position of that cheat, then changes back to null.
*/
public LiveData<Integer> getCheatDeletedEvent()
{
return mCheatDeletedEvent;
}
public void deleteSelectedCheat()
{
Cheat cheat = mSelectedCheat.getValue();
int position = mSelectedCheatPosition;
setSelectedCheat(null, -1);
if (mPatchCheats.remove(cheat))
mPatchCheatsNeedSaving = true;
if (mARCheats.remove(cheat))
mARCheatsNeedSaving = true;
if (mGeckoCheats.remove(cheat))
mGeckoCheatsNeedSaving = true;
notifyCheatDeleted(position);
}
/**
* Notifies that the cheat at the given position has been deleted.
*/
private void notifyCheatDeleted(int position)
{
mCheatDeletedEvent.setValue(position);
mCheatDeletedEvent.setValue(null);
}
/**
* When Gecko cheats are downloaded, the integer stored in the returned LiveData
* changes to the number of cheats added, then changes back to null.
*/
public LiveData<Integer> getGeckoCheatsDownloadedEvent()
{
return mGeckoCheatsDownloadedEvent;
}
public int addDownloadedGeckoCodes(GeckoCheat[] cheats)
{
int cheatsAdded = 0;
for (GeckoCheat cheat : cheats)
{
if (!mGeckoCheats.contains(cheat))
{
mGeckoCheats.add(cheat);
cheatsAdded++;
}
}
if (cheatsAdded != 0)
{
mGeckoCheatsNeedSaving = true;
mGeckoCheatsDownloadedEvent.setValue(cheatsAdded);
mGeckoCheatsDownloadedEvent.setValue(null);
}
return cheatsAdded;
}
public LiveData<Boolean> getOpenDetailsViewEvent()
{
return mOpenDetailsViewEvent;
}
public void openDetailsView()
{
mOpenDetailsViewEvent.setValue(true);
mOpenDetailsViewEvent.setValue(false);
}
public ArrayList<GraphicsMod> getGraphicsMods()
{
return mGraphicsMods;
}
public ArrayList<PatchCheat> getPatchCheats()
{
return mPatchCheats;
}
public ArrayList<ARCheat> getARCheats()
{
return mARCheats;
}
public ArrayList<GeckoCheat> getGeckoCheats()
{
return mGeckoCheats;
}
}

View File

@ -0,0 +1,207 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import org.dolphinemu.dolphinemu.features.cheats.model.ARCheat.Companion.loadCodes
import org.dolphinemu.dolphinemu.features.cheats.model.ARCheat.Companion.saveCodes
import kotlin.collections.ArrayList
class CheatsViewModel : ViewModel() {
private var loaded = false
private var selectedCheatPosition = -1
private val _selectedCheat = MutableLiveData<Cheat?>(null)
val selectedCheat: LiveData<Cheat?> get() = _selectedCheat
private val _isAdding = MutableLiveData(false)
val isAdding: LiveData<Boolean> get() = _isAdding
private val _isEditing = MutableLiveData(false)
val isEditing: LiveData<Boolean> get() = _isEditing
private val _cheatAddedEvent = MutableLiveData<Int?>(null)
val cheatAddedEvent: LiveData<Int?> get() = _cheatAddedEvent
private val _cheatChangedEvent = MutableLiveData<Int?>(null)
val cheatChangedEvent: LiveData<Int?> get() = _cheatChangedEvent
private val _cheatDeletedEvent = MutableLiveData<Int?>(null)
val cheatDeletedEvent: LiveData<Int?> get() = _cheatDeletedEvent
private val _geckoCheatsDownloadedEvent = MutableLiveData<Int?>(null)
val geckoCheatsDownloadedEvent: LiveData<Int?> get() = _geckoCheatsDownloadedEvent
private val _openDetailsViewEvent = MutableLiveData(false)
val openDetailsViewEvent: LiveData<Boolean> get() = _openDetailsViewEvent
private var graphicsModGroup: GraphicsModGroup? = null
var graphicsMods: ArrayList<GraphicsMod> = ArrayList()
var patchCheats: ArrayList<PatchCheat> = ArrayList()
var aRCheats: ArrayList<ARCheat> = ArrayList()
var geckoCheats: ArrayList<GeckoCheat> = ArrayList()
private var graphicsModsNeedSaving = false
private var patchCheatsNeedSaving = false
private var aRCheatsNeedSaving = false
private var geckoCheatsNeedSaving = false
fun load(gameID: String, revision: Int) {
if (loaded) return
graphicsModGroup = GraphicsModGroup.load(gameID)
graphicsMods.addAll(graphicsModGroup!!.mods)
patchCheats.addAll(PatchCheat.loadCodes(gameID, revision))
aRCheats.addAll(loadCodes(gameID, revision))
geckoCheats.addAll(GeckoCheat.loadCodes(gameID, revision))
for (mod in graphicsMods) {
mod.setChangedCallback { graphicsModsNeedSaving = true }
}
for (cheat in patchCheats) {
cheat.setChangedCallback { patchCheatsNeedSaving = true }
}
for (cheat in aRCheats) {
cheat.setChangedCallback { aRCheatsNeedSaving = true }
}
for (cheat in geckoCheats) {
cheat.setChangedCallback { geckoCheatsNeedSaving = true }
}
loaded = true
}
fun saveIfNeeded(gameID: String, revision: Int) {
if (graphicsModsNeedSaving) {
graphicsModGroup!!.save()
graphicsModsNeedSaving = false
}
if (patchCheatsNeedSaving) {
PatchCheat.saveCodes(gameID, revision, patchCheats.toTypedArray())
patchCheatsNeedSaving = false
}
if (aRCheatsNeedSaving) {
saveCodes(gameID, revision, aRCheats.toTypedArray())
aRCheatsNeedSaving = false
}
if (geckoCheatsNeedSaving) {
GeckoCheat.saveCodes(gameID, revision, geckoCheats.toTypedArray())
geckoCheatsNeedSaving = false
}
}
fun setSelectedCheat(cheat: Cheat?, position: Int) {
if (isEditing.value!!) setIsEditing(false)
_selectedCheat.value = cheat
selectedCheatPosition = position
}
fun startAddingCheat(cheat: Cheat?, position: Int) {
_selectedCheat.value = cheat
selectedCheatPosition = position
_isAdding.value = true
_isEditing.value = true
}
fun finishAddingCheat() {
check(isAdding.value!!)
_isAdding.value = false
_isEditing.value = false
when (val cheat = selectedCheat.value) {
is PatchCheat -> {
patchCheats.add(cheat)
cheat.setChangedCallback(Runnable { patchCheatsNeedSaving = true })
patchCheatsNeedSaving = true
}
is ARCheat -> {
aRCheats.add(cheat)
cheat.setChangedCallback(Runnable { patchCheatsNeedSaving = true })
aRCheatsNeedSaving = true
}
is GeckoCheat -> {
geckoCheats.add(cheat)
cheat.setChangedCallback(Runnable { geckoCheatsNeedSaving = true })
geckoCheatsNeedSaving = true
}
else -> throw UnsupportedOperationException()
}
notifyCheatAdded()
}
fun setIsEditing(isEditing: Boolean) {
_isEditing.value = isEditing
if (isAdding.value!! && !isEditing) {
_isAdding.value = false
setSelectedCheat(null, -1)
}
}
private fun notifyCheatAdded() {
_cheatAddedEvent.value = selectedCheatPosition
_cheatAddedEvent.value = null
}
/**
* Notifies that an edit has been made to the contents of the currently selected cheat.
*/
fun notifySelectedCheatChanged() {
notifyCheatChanged(selectedCheatPosition)
}
/**
* Notifies that an edit has been made to the contents of the cheat at the given position.
*/
private fun notifyCheatChanged(position: Int) {
_cheatChangedEvent.value = position
_cheatChangedEvent.value = null
}
fun deleteSelectedCheat() {
val cheat = selectedCheat.value
val position = selectedCheatPosition
setSelectedCheat(null, -1)
if (patchCheats.remove(cheat)) patchCheatsNeedSaving = true
if (aRCheats.remove(cheat)) aRCheatsNeedSaving = true
if (geckoCheats.remove(cheat)) geckoCheatsNeedSaving = true
notifyCheatDeleted(position)
}
/**
* Notifies that the cheat at the given position has been deleted.
*/
private fun notifyCheatDeleted(position: Int) {
_cheatDeletedEvent.value = position
_cheatDeletedEvent.value = null
}
fun addDownloadedGeckoCodes(cheats: Array<GeckoCheat>): Int {
var cheatsAdded = 0
for (cheat in cheats) {
if (!geckoCheats.contains(cheat)) {
geckoCheats.add(cheat)
cheatsAdded++
}
}
if (cheatsAdded != 0) {
geckoCheatsNeedSaving = true
_geckoCheatsDownloadedEvent.value = cheatsAdded
_geckoCheatsDownloadedEvent.value = null
}
return cheatsAdded
}
fun openDetailsView() {
_openDetailsViewEvent.value = true
_openDetailsViewEvent.value = false
}
}

View File

@ -1,78 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class GeckoCheat extends AbstractCheat
{
@Keep
private final long mPointer;
public GeckoCheat()
{
mPointer = createNew();
}
@Keep
private GeckoCheat(long pointer)
{
mPointer = pointer;
}
@Override
public native void finalize();
private native long createNew();
@Override
public boolean equals(@Nullable Object obj)
{
return obj != null && getClass() == obj.getClass() && equalsImpl((GeckoCheat) obj);
}
public boolean supportsCreator()
{
return true;
}
public boolean supportsNotes()
{
return true;
}
@NonNull
public native String getName();
@NonNull
public native String getCreator();
@NonNull
public native String getNotes();
@NonNull
public native String getCode();
public native boolean getUserDefined();
public native boolean getEnabled();
public native boolean equalsImpl(@NonNull GeckoCheat other);
@Override
protected native int trySetImpl(@NonNull String name, @NonNull String creator,
@NonNull String notes, @NonNull String code);
@Override
protected native void setEnabledImpl(boolean enabled);
@NonNull
public static native GeckoCheat[] loadCodes(String gameId, int revision);
public static native void saveCodes(String gameId, int revision, GeckoCheat[] codes);
@Nullable
public static native GeckoCheat[] downloadCodes(String gameTdbId);
}

View File

@ -0,0 +1,72 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model
import androidx.annotation.Keep
class GeckoCheat : AbstractCheat {
@Keep
private val mPointer: Long
constructor() {
mPointer = createNew()
}
@Keep
private constructor(pointer: Long) {
mPointer = pointer
}
external fun finalize()
private external fun createNew(): Long
override fun equals(other: Any?): Boolean {
return other != null && javaClass == other.javaClass && equalsImpl(other as GeckoCheat)
}
override fun hashCode(): Int {
return mPointer.hashCode()
}
override fun supportsCreator(): Boolean {
return true
}
override fun supportsNotes(): Boolean {
return true
}
external override fun getName(): String
external override fun getCreator(): String
external override fun getNotes(): String
external override fun getCode(): String
external override fun getUserDefined(): Boolean
external override fun getEnabled(): Boolean
private external fun equalsImpl(other: GeckoCheat): Boolean
external override fun setCheatImpl(
name: String,
creator: String,
notes: String,
code: String
): Int
external override fun setEnabledImpl(enabled: Boolean)
companion object {
@JvmStatic
external fun loadCodes(gameId: String, revision: Int): Array<GeckoCheat>
@JvmStatic
external fun saveCodes(gameId: String, revision: Int, codes: Array<GeckoCheat>)
@JvmStatic
external fun downloadCodes(gameTdbId: String): Array<GeckoCheat>?
}
}

View File

@ -1,61 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public class GraphicsMod extends ReadOnlyCheat
{
@Keep
private final long mPointer;
// When a C++ GraphicsModGroup object is destroyed, it also destroys the GraphicsMods it owns.
// To avoid getting dangling pointers, we keep a reference to the GraphicsModGroup here.
@Keep
private final GraphicsModGroup mParent;
@Keep
private GraphicsMod(long pointer, GraphicsModGroup parent)
{
mPointer = pointer;
mParent = parent;
}
public boolean supportsCreator()
{
return true;
}
public boolean supportsNotes()
{
return true;
}
public boolean supportsCode()
{
return false;
}
@NonNull
public native String getName();
@NonNull
public native String getCreator();
@NonNull
public native String getNotes();
public boolean getUserDefined()
{
// Technically graphics mods can be user defined, but we don't support editing graphics mods
// in the GUI, and editability is what this really controls
return false;
}
public native boolean getEnabled();
@Override
protected native void setEnabledImpl(boolean enabled);
}

View File

@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model
import androidx.annotation.Keep
class GraphicsMod @Keep private constructor(
@Keep private val pointer: Long,
// When a C++ GraphicsModGroup object is destroyed, it also destroys the GraphicsMods it owns.
// To avoid getting dangling pointers, we keep a reference to the GraphicsModGroup here.
@Keep private val parent: GraphicsModGroup
) : ReadOnlyCheat() {
override fun supportsCreator(): Boolean = true
override fun supportsNotes(): Boolean = true
override fun supportsCode(): Boolean = false
external override fun getName(): String
external override fun getCreator(): String
external override fun getNotes(): String
// Technically graphics mods can be user defined, but we don't support editing graphics mods
// in the GUI, and editability is what this really controls
override fun getUserDefined(): Boolean = false
external override fun getEnabled(): Boolean
external override fun setEnabledImpl(enabled: Boolean)
}

View File

@ -1,27 +0,0 @@
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
public class GraphicsModGroup
{
@Keep
private final long mPointer;
@Keep
private GraphicsModGroup(long pointer)
{
mPointer = pointer;
}
@Override
public native void finalize();
@NonNull
public native GraphicsMod[] getMods();
public native void save();
@NonNull
public static native GraphicsModGroup load(String gameId);
}

View File

@ -0,0 +1,17 @@
package org.dolphinemu.dolphinemu.features.cheats.model
import androidx.annotation.Keep
class GraphicsModGroup @Keep private constructor(@field:Keep private val pointer: Long) {
external fun finalize()
val mods: Array<GraphicsMod>
external get
external fun save()
companion object {
@JvmStatic
external fun load(gameId: String): GraphicsModGroup
}
}

View File

@ -1,60 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.Keep;
import androidx.annotation.NonNull;
public class PatchCheat extends AbstractCheat
{
@Keep
private final long mPointer;
public PatchCheat()
{
mPointer = createNew();
}
@Keep
private PatchCheat(long pointer)
{
mPointer = pointer;
}
@Override
public native void finalize();
private native long createNew();
public boolean supportsCreator()
{
return false;
}
public boolean supportsNotes()
{
return false;
}
@NonNull
public native String getName();
@NonNull
public native String getCode();
public native boolean getUserDefined();
public native boolean getEnabled();
@Override
protected native int trySetImpl(@NonNull String name, @NonNull String creator,
@NonNull String notes, @NonNull String code);
@Override
protected native void setEnabledImpl(boolean enabled);
@NonNull
public static native PatchCheat[] loadCodes(String gameId, int revision);
public static native void saveCodes(String gameId, int revision, PatchCheat[] codes);
}

View File

@ -0,0 +1,56 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model
import androidx.annotation.Keep
class PatchCheat : AbstractCheat {
@Keep
private val pointer: Long
constructor() {
pointer = createNew()
}
@Keep
private constructor(pointer: Long) {
this.pointer = pointer
}
external fun finalize()
private external fun createNew(): Long
override fun supportsCreator(): Boolean {
return false
}
override fun supportsNotes(): Boolean {
return false
}
external override fun getName(): String
external override fun getCode(): String
external override fun getUserDefined(): Boolean
external override fun getEnabled(): Boolean
external override fun setCheatImpl(
name: String,
creator: String,
notes: String,
code: String
): Int
external override fun setEnabledImpl(enabled: Boolean)
companion object {
@JvmStatic
external fun loadCodes(gameId: String, revision: Int): Array<PatchCheat>
@JvmStatic
external fun saveCodes(gameId: String, revision: Int, codes: Array<PatchCheat>)
}
}

View File

@ -1,36 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
public abstract class ReadOnlyCheat implements Cheat
{
private Runnable mChangedCallback = null;
public int trySet(@NonNull String name, @NonNull String creator, @NonNull String notes,
@NonNull String code)
{
throw new UnsupportedOperationException();
}
public void setEnabled(boolean enabled)
{
setEnabledImpl(enabled);
onChanged();
}
public void setChangedCallback(@Nullable Runnable callback)
{
mChangedCallback = callback;
}
protected void onChanged()
{
if (mChangedCallback != null)
mChangedCallback.run();
}
protected abstract void setEnabledImpl(boolean enabled);
}

View File

@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.model
abstract class ReadOnlyCheat : Cheat {
private var onChangedCallback: Runnable? = null
override fun setCheat(
name: String,
creator: String,
notes: String,
code: String
): Int {
throw UnsupportedOperationException()
}
override fun setEnabled(isChecked: Boolean) {
setEnabledImpl(isChecked)
onChanged()
}
override fun setChangedCallback(callback: Runnable?) {
onChangedCallback = callback
}
protected fun onChanged() {
if (onChangedCallback != null) onChangedCallback!!.run()
}
protected abstract fun setEnabledImpl(enabled: Boolean)
}

View File

@ -1,68 +0,0 @@
// 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 androidx.lifecycle.ViewModelProvider;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding;
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 CheatsActivity mActivity;
private CheatsViewModel mViewModel;
private int mString;
private int mPosition;
public ActionViewHolder(@NonNull ListItemSubmenuBinding binding)
{
super(binding.getRoot());
mName = binding.textSettingName;
binding.getRoot().setOnClickListener(this);
}
public void bind(CheatsActivity activity, CheatItem item, int position)
{
mActivity = activity;
mViewModel = new ViewModelProvider(activity).get(CheatsViewModel.class);
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();
}
else if (mString == R.string.cheats_download_gecko)
{
mActivity.downloadGeckoCodes();
}
}
}

View File

@ -0,0 +1,54 @@
// 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.lifecycle.ViewModelProvider
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding
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
class ActionViewHolder(binding: ListItemSubmenuBinding) : CheatItemViewHolder(binding.root),
View.OnClickListener {
private val mName: TextView
private lateinit var activity: CheatsActivity
private lateinit var viewModel: CheatsViewModel
private var string = 0
private var position = 0
init {
mName = binding.textSettingName
binding.root.setOnClickListener(this)
}
override fun bind(activity: CheatsActivity, item: CheatItem, position: Int) {
this.activity = activity
viewModel = ViewModelProvider(this.activity)[CheatsViewModel::class.java]
string = item.string
this.position = position
mName.setText(string)
}
override fun onClick(root: View) {
when(string) {
R.string.cheats_add_ar -> {
viewModel.startAddingCheat(ARCheat(), position)
viewModel.openDetailsView()
}
R.string.cheats_add_gecko -> {
viewModel.startAddingCheat(GeckoCheat(), position)
viewModel.openDetailsView()
}
R.string.cheats_add_patch -> {
viewModel.startAddingCheat(PatchCheat(), position)
viewModel.openDetailsView()
}
R.string.cheats_download_gecko -> activity.downloadGeckoCodes()
}
}
}

View File

@ -1,178 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.databinding.FragmentCheatDetailsBinding;
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat;
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel;
public class CheatDetailsFragment extends Fragment
{
private CheatsViewModel mViewModel;
private Cheat mCheat;
private FragmentCheatDetailsBinding mBinding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState)
{
mBinding = FragmentCheatDetailsBinding.inflate(inflater, container, false);
return mBinding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
CheatsActivity activity = (CheatsActivity) requireActivity();
mViewModel = new ViewModelProvider(activity).get(CheatsViewModel.class);
mViewModel.getSelectedCheat().observe(getViewLifecycleOwner(), this::onSelectedCheatUpdated);
mViewModel.getIsEditing().observe(getViewLifecycleOwner(), this::onIsEditingUpdated);
mBinding.buttonDelete.setOnClickListener(this::onDeleteClicked);
mBinding.buttonEdit.setOnClickListener(this::onEditClicked);
mBinding.buttonCancel.setOnClickListener(this::onCancelClicked);
mBinding.buttonOk.setOnClickListener(this::onOkClicked);
CheatsActivity.setOnFocusChangeListenerRecursively(view,
(v, hasFocus) -> activity.onDetailsViewFocusChange(hasFocus));
}
@Override
public void onDestroyView()
{
super.onDestroyView();
mBinding = null;
}
private void clearEditErrors()
{
mBinding.editName.setError(null);
mBinding.editCode.setError(null);
}
private void onDeleteClicked(View view)
{
new MaterialAlertDialogBuilder(requireContext())
.setMessage(getString(R.string.cheats_delete_confirmation, mCheat.getName()))
.setPositiveButton(R.string.yes, (dialog, i) -> mViewModel.deleteSelectedCheat())
.setNegativeButton(R.string.no, null)
.show();
}
private void onEditClicked(View view)
{
mViewModel.setIsEditing(true);
mBinding.buttonOk.requestFocus();
}
private void onCancelClicked(View view)
{
mViewModel.setIsEditing(false);
onSelectedCheatUpdated(mCheat);
mBinding.buttonDelete.requestFocus();
}
private void onOkClicked(View view)
{
clearEditErrors();
int result = mCheat.trySet(mBinding.editNameInput.getText().toString(),
mBinding.editCreatorInput.getText().toString(),
mBinding.editNotesInput.getText().toString(),
mBinding.editCodeInput.getText().toString());
switch (result)
{
case Cheat.TRY_SET_SUCCESS:
if (mViewModel.getIsAdding().getValue())
{
mViewModel.finishAddingCheat();
onSelectedCheatUpdated(mCheat);
}
else
{
mViewModel.notifySelectedCheatChanged();
mViewModel.setIsEditing(false);
}
mBinding.buttonEdit.requestFocus();
break;
case Cheat.TRY_SET_FAIL_NO_NAME:
mBinding.editName.setError(getString(R.string.cheats_error_no_name));
mBinding.scrollView.smoothScrollTo(0, mBinding.editNameInput.getTop());
break;
case Cheat.TRY_SET_FAIL_NO_CODE_LINES:
mBinding.editCode.setError(getString(R.string.cheats_error_no_code_lines));
mBinding.scrollView.smoothScrollTo(0, mBinding.editCodeInput.getBottom());
break;
case Cheat.TRY_SET_FAIL_CODE_MIXED_ENCRYPTION:
mBinding.editCode.setError(getString(R.string.cheats_error_mixed_encryption));
mBinding.scrollView.smoothScrollTo(0, mBinding.editCodeInput.getBottom());
break;
default:
mBinding.editCode.setError(getString(R.string.cheats_error_on_line, result));
mBinding.scrollView.smoothScrollTo(0, mBinding.editCodeInput.getBottom());
break;
}
}
private void onSelectedCheatUpdated(@Nullable Cheat cheat)
{
clearEditErrors();
mBinding.root.setVisibility(cheat == null ? View.GONE : View.VISIBLE);
int creatorVisibility = cheat != null && cheat.supportsCreator() ? View.VISIBLE : View.GONE;
int notesVisibility = cheat != null && cheat.supportsNotes() ? View.VISIBLE : View.GONE;
int codeVisibility = cheat != null && cheat.supportsCode() ? View.VISIBLE : View.GONE;
mBinding.editCreator.setVisibility(creatorVisibility);
mBinding.editNotes.setVisibility(notesVisibility);
mBinding.editCode.setVisibility(codeVisibility);
boolean userDefined = cheat != null && cheat.getUserDefined();
mBinding.buttonDelete.setEnabled(userDefined);
mBinding.buttonEdit.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)
{
mBinding.editNameInput.setText(cheat.getName());
mBinding.editCreatorInput.setText(cheat.getCreator());
mBinding.editNotesInput.setText(cheat.getNotes());
mBinding.editCodeInput.setText(cheat.getCode());
}
mCheat = cheat;
}
private void onIsEditingUpdated(boolean isEditing)
{
mBinding.editNameInput.setEnabled(isEditing);
mBinding.editCreatorInput.setEnabled(isEditing);
mBinding.editNotesInput.setEnabled(isEditing);
mBinding.editCodeInput.setEnabled(isEditing);
mBinding.buttonDelete.setVisibility(isEditing ? View.GONE : View.VISIBLE);
mBinding.buttonEdit.setVisibility(isEditing ? View.GONE : View.VISIBLE);
mBinding.buttonCancel.setVisibility(isEditing ? View.VISIBLE : View.GONE);
mBinding.buttonOk.setVisibility(isEditing ? View.VISIBLE : View.GONE);
}
}

View File

@ -0,0 +1,170 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import android.content.DialogInterface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.FragmentCheatDetailsBinding
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel
class CheatDetailsFragment : Fragment() {
private lateinit var viewModel: CheatsViewModel
private var cheat: Cheat? = null
private var _binding: FragmentCheatDetailsBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCheatDetailsBinding.inflate(inflater, container, false)
return binding.getRoot()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val activity = requireActivity() as CheatsActivity
viewModel = ViewModelProvider(activity)[CheatsViewModel::class.java]
viewModel.selectedCheat.observe(viewLifecycleOwner) { cheat: Cheat? ->
onSelectedCheatUpdated(
cheat
)
}
viewModel.isEditing.observe(viewLifecycleOwner) { isEditing: Boolean ->
onIsEditingUpdated(
isEditing
)
}
binding.buttonDelete.setOnClickListener { onDeleteClicked() }
binding.buttonEdit.setOnClickListener { onEditClicked() }
binding.buttonCancel.setOnClickListener { onCancelClicked() }
binding.buttonOk.setOnClickListener { onOkClicked() }
CheatsActivity.setOnFocusChangeListenerRecursively(
view
) { _: View?, hasFocus: Boolean -> activity.onDetailsViewFocusChange(hasFocus) }
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun clearEditErrors() {
binding.editName.error = null
binding.editCode.error = null
}
private fun onDeleteClicked() {
MaterialAlertDialogBuilder(requireContext())
.setMessage(getString(R.string.cheats_delete_confirmation, cheat!!.getName()))
.setPositiveButton(R.string.yes) { _: DialogInterface?, i: Int -> viewModel.deleteSelectedCheat() }
.setNegativeButton(R.string.no, null)
.show()
}
private fun onEditClicked() {
viewModel.setIsEditing(true)
binding.buttonOk.requestFocus()
}
private fun onCancelClicked() {
viewModel.setIsEditing(false)
onSelectedCheatUpdated(cheat)
binding.buttonDelete.requestFocus()
}
private fun onOkClicked() {
clearEditErrors()
val result = cheat!!.setCheat(
binding.editNameInput.text.toString(),
binding.editCreatorInput.text.toString(),
binding.editNotesInput.text.toString(),
binding.editCodeInput.text.toString()
)
when (result) {
Cheat.TRY_SET_SUCCESS -> {
if (viewModel.isAdding.value!!) {
viewModel.finishAddingCheat()
onSelectedCheatUpdated(cheat)
} else {
viewModel.notifySelectedCheatChanged()
viewModel.setIsEditing(false)
}
binding.buttonEdit.requestFocus()
}
Cheat.TRY_SET_FAIL_NO_NAME -> {
binding.editName.error = getString(R.string.cheats_error_no_name)
binding.scrollView.smoothScrollTo(0, binding.editNameInput.top)
}
Cheat.TRY_SET_FAIL_NO_CODE_LINES -> {
binding.editCode.error = getString(R.string.cheats_error_no_code_lines)
binding.scrollView.smoothScrollTo(0, binding.editCodeInput.bottom)
}
Cheat.TRY_SET_FAIL_CODE_MIXED_ENCRYPTION -> {
binding.editCode.error = getString(R.string.cheats_error_mixed_encryption)
binding.scrollView.smoothScrollTo(0, binding.editCodeInput.bottom)
}
else -> {
binding.editCode.error = getString(R.string.cheats_error_on_line, result)
binding.scrollView.smoothScrollTo(0, binding.editCodeInput.bottom)
}
}
}
private fun onSelectedCheatUpdated(cheat: Cheat?) {
clearEditErrors()
binding.root.visibility = if (cheat == null) View.GONE else View.VISIBLE
val creatorVisibility =
if (cheat != null && cheat.supportsCreator()) View.VISIBLE else View.GONE
val notesVisibility =
if (cheat != null && cheat.supportsNotes()) View.VISIBLE else View.GONE
val codeVisibility = if (cheat != null && cheat.supportsCode()) View.VISIBLE else View.GONE
binding.editCreator.visibility = creatorVisibility
binding.editNotes.visibility = notesVisibility
binding.editCode.visibility = codeVisibility
val userDefined = cheat != null && cheat.getUserDefined()
binding.buttonDelete.isEnabled = userDefined
binding.buttonEdit.isEnabled = 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
val isEditing = viewModel.isEditing.value!!
if (!isEditing && cheat != null) {
binding.editNameInput.setText(cheat.getName())
binding.editCreatorInput.setText(cheat.getCreator())
binding.editNotesInput.setText(cheat.getNotes())
binding.editCodeInput.setText(cheat.getCode())
}
this.cheat = cheat
}
private fun onIsEditingUpdated(isEditing: Boolean) {
binding.editNameInput.isEnabled = isEditing
binding.editCreatorInput.isEnabled = isEditing
binding.editNotesInput.isEnabled = isEditing
binding.editCodeInput.isEnabled = isEditing
binding.buttonDelete.visibility = if (isEditing) View.GONE else View.VISIBLE
binding.buttonEdit.visibility = if (isEditing) View.GONE else View.VISIBLE
binding.buttonCancel.visibility = if (isEditing) View.VISIBLE else View.GONE
binding.buttonOk.visibility = if (isEditing) View.VISIBLE else View.GONE
}
}

View File

@ -1,49 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat;
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;
private final int mType;
public CheatItem(@NonNull Cheat cheat)
{
mCheat = cheat;
mString = 0;
mType = TYPE_CHEAT;
}
public CheatItem(int type, int string)
{
mCheat = null;
mString = string;
mType = type;
}
@Nullable
public Cheat getCheat()
{
return mCheat;
}
public int getString()
{
return mString;
}
public int getType()
{
return mType;
}
}

View File

@ -0,0 +1,29 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat
class CheatItem {
val cheat: Cheat?
val string: Int
val type: Int
constructor(cheat: Cheat) {
this.cheat = cheat
string = 0
type = TYPE_CHEAT
}
constructor(type: Int, string: Int) {
cheat = null
this.string = string
this.type = type
}
companion object {
const val TYPE_CHEAT = 0
const val TYPE_HEADER = 1
const val TYPE_ACTION = 2
}
}

View File

@ -1,18 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
public abstract class CheatItemViewHolder extends RecyclerView.ViewHolder
{
public CheatItemViewHolder(@NonNull View itemView)
{
super(itemView);
}
public abstract void bind(CheatsActivity activity, CheatItem item, int position);
}

View File

@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import android.view.View
import androidx.recyclerview.widget.RecyclerView
abstract class CheatItemViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(activity: CheatsActivity, item: CheatItem, position: Int)
}

View File

@ -1,82 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.divider.MaterialDividerItemDecoration;
import com.google.android.material.elevation.ElevationOverlayProvider;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.databinding.FragmentCheatListBinding;
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel;
public class CheatListFragment extends Fragment
{
private FragmentCheatListBinding mBinding;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState)
{
mBinding = FragmentCheatListBinding.inflate(inflater, container, false);
return mBinding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
CheatsActivity activity = (CheatsActivity) requireActivity();
CheatsViewModel viewModel = new ViewModelProvider(activity).get(CheatsViewModel.class);
mBinding.cheatList.setAdapter(new CheatsAdapter(activity, viewModel));
mBinding.cheatList.setLayoutManager(new LinearLayoutManager(activity));
MaterialDividerItemDecoration divider =
new MaterialDividerItemDecoration(requireActivity(), LinearLayoutManager.VERTICAL);
divider.setLastItemDecorated(false);
mBinding.cheatList.addItemDecoration(divider);
@ColorInt int color =
new ElevationOverlayProvider(mBinding.cheatsWarning.getContext()).compositeOverlay(
MaterialColors.getColor(mBinding.cheatsWarning, R.attr.colorSurface),
getResources().getDimensionPixelSize(R.dimen.elevated_app_bar));
mBinding.cheatsWarning.setBackgroundColor(color);
mBinding.gfxModsWarning.setBackgroundColor(color);
setInsets();
}
@Override
public void onDestroyView()
{
super.onDestroyView();
mBinding = null;
}
private void setInsets()
{
ViewCompat.setOnApplyWindowInsetsListener(mBinding.cheatList, (v, windowInsets) ->
{
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
v.setPadding(0, 0, 0,
insets.bottom + getResources().getDimensionPixelSize(R.dimen.spacing_xtralarge));
return windowInsets;
});
}
}

View File

@ -0,0 +1,74 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.ColorInt
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.color.MaterialColors
import com.google.android.material.divider.MaterialDividerItemDecoration
import com.google.android.material.elevation.ElevationOverlayProvider
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.FragmentCheatListBinding
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel
class CheatListFragment : Fragment() {
private var _binding: FragmentCheatListBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentCheatListBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val activity = requireActivity() as CheatsActivity
val viewModel = ViewModelProvider(activity)[CheatsViewModel::class.java]
binding.cheatList.adapter = CheatsAdapter(activity, viewModel)
binding.cheatList.layoutManager = LinearLayoutManager(activity)
val divider = MaterialDividerItemDecoration(requireActivity(), LinearLayoutManager.VERTICAL)
divider.isLastItemDecorated = false
binding.cheatList.addItemDecoration(divider)
@ColorInt val color =
ElevationOverlayProvider(binding.cheatsWarning.context).compositeOverlay(
MaterialColors.getColor(binding.cheatsWarning, R.attr.colorSurface),
resources.getDimensionPixelSize(R.dimen.elevated_app_bar).toFloat()
)
binding.cheatsWarning.setBackgroundColor(color)
binding.gfxModsWarning.setBackgroundColor(color)
setInsets()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.cheatList) { v: View, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(
0,
0,
0,
insets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_xtralarge)
)
windowInsets
}
}
}

View File

@ -1,55 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import android.view.View;
import android.widget.CompoundButton;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider;
import org.dolphinemu.dolphinemu.databinding.ListItemCheatBinding;
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat;
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel;
public class CheatViewHolder extends CheatItemViewHolder
implements View.OnClickListener, CompoundButton.OnCheckedChangeListener
{
private final ListItemCheatBinding mBinding;
private CheatsViewModel mViewModel;
private Cheat mCheat;
private int mPosition;
public CheatViewHolder(@NonNull ListItemCheatBinding binding)
{
super(binding.getRoot());
mBinding = binding;
}
public void bind(CheatsActivity activity, CheatItem item, int position)
{
mBinding.cheatSwitch.setOnCheckedChangeListener(null);
mViewModel = new ViewModelProvider(activity).get(CheatsViewModel.class);
mCheat = item.getCheat();
mPosition = position;
mBinding.textName.setText(mCheat.getName());
mBinding.cheatSwitch.setChecked(mCheat.getEnabled());
mBinding.root.setOnClickListener(this);
mBinding.cheatSwitch.setOnCheckedChangeListener(this);
}
public void onClick(View root)
{
mViewModel.setSelectedCheat(mCheat, mPosition);
mViewModel.openDetailsView();
}
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked)
{
mCheat.setEnabled(isChecked);
}
}

View File

@ -0,0 +1,39 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import android.view.View
import android.widget.CompoundButton
import androidx.lifecycle.ViewModelProvider
import org.dolphinemu.dolphinemu.databinding.ListItemCheatBinding
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel
class CheatViewHolder(private val binding: ListItemCheatBinding) :
CheatItemViewHolder(binding.getRoot()),
View.OnClickListener,
CompoundButton.OnCheckedChangeListener {
private lateinit var viewModel: CheatsViewModel
private lateinit var cheat: Cheat
private var position = 0
override fun bind(activity: CheatsActivity, item: CheatItem, position: Int) {
binding.cheatSwitch.setOnCheckedChangeListener(null)
viewModel = ViewModelProvider(activity)[CheatsViewModel::class.java]
cheat = item.cheat!!
this.position = position
binding.textName.text = cheat.getName()
binding.cheatSwitch.isChecked = cheat.getEnabled()
binding.root.setOnClickListener(this)
binding.cheatSwitch.setOnCheckedChangeListener(this)
}
override fun onClick(root: View) {
viewModel.setSelectedCheat(cheat, position)
viewModel.openDetailsView()
}
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
cheat.setEnabled(isChecked)
}
}

View File

@ -1,342 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsAnimationCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.slidingpanelayout.widget.SlidingPaneLayout;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.elevation.ElevationOverlayProvider;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.databinding.ActivityCheatsBinding;
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat;
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel;
import org.dolphinemu.dolphinemu.features.cheats.model.GeckoCheat;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.ui.TwoPaneOnBackPressedCallback;
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
import org.dolphinemu.dolphinemu.utils.InsetsHelper;
import org.dolphinemu.dolphinemu.utils.ThemeHelper;
import java.util.List;
public class CheatsActivity extends AppCompatActivity
implements SlidingPaneLayout.PanelSlideListener
{
private static final String ARG_GAME_ID = "game_id";
private static final String ARG_GAMETDB_ID = "gametdb_id";
private static final String ARG_REVISION = "revision";
private static final String ARG_IS_WII = "is_wii";
private String mGameId;
private String mGameTdbId;
private int mRevision;
private boolean mIsWii;
private CheatsViewModel mViewModel;
private View mCheatListLastFocus;
private View mCheatDetailsLastFocus;
private ActivityCheatsBinding mBinding;
public static void launch(Context context, String gameId, String gameTdbId, int revision,
boolean isWii)
{
Intent intent = new Intent(context, CheatsActivity.class);
intent.putExtra(ARG_GAME_ID, gameId);
intent.putExtra(ARG_GAMETDB_ID, gameTdbId);
intent.putExtra(ARG_REVISION, revision);
intent.putExtra(ARG_IS_WII, isWii);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
MainPresenter.skipRescanningLibrary();
Intent intent = getIntent();
mGameId = intent.getStringExtra(ARG_GAME_ID);
mGameTdbId = intent.getStringExtra(ARG_GAMETDB_ID);
mRevision = intent.getIntExtra(ARG_REVISION, 0);
mIsWii = intent.getBooleanExtra(ARG_IS_WII, true);
setTitle(getString(R.string.cheats_with_game_id, mGameId));
mViewModel = new ViewModelProvider(this).get(CheatsViewModel.class);
mViewModel.load(mGameId, mRevision);
mBinding = ActivityCheatsBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
mCheatListLastFocus = mBinding.cheatList;
mCheatDetailsLastFocus = mBinding.cheatDetails;
mBinding.slidingPaneLayout.addPanelSlideListener(this);
getOnBackPressedDispatcher().addCallback(this,
new TwoPaneOnBackPressedCallback(mBinding.slidingPaneLayout));
mViewModel.getSelectedCheat().observe(this, this::onSelectedCheatChanged);
onSelectedCheatChanged(mViewModel.getSelectedCheat().getValue());
mViewModel.getOpenDetailsViewEvent().observe(this, this::openDetailsView);
setSupportActionBar(mBinding.toolbarCheats);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setInsets();
@ColorInt int color =
new ElevationOverlayProvider(mBinding.toolbarCheats.getContext()).compositeOverlay(
MaterialColors.getColor(mBinding.toolbarCheats, R.attr.colorSurface),
getResources().getDimensionPixelSize(R.dimen.elevated_app_bar));
mBinding.toolbarCheats.setBackgroundColor(color);
ThemeHelper.setStatusBarColor(this, color);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_settings, menu);
return true;
}
@Override
protected void onStop()
{
super.onStop();
mViewModel.saveIfNeeded(mGameId, mRevision);
}
@Override
public void onPanelSlide(@NonNull View panel, float slideOffset)
{
}
@Override
public void onPanelOpened(@NonNull View panel)
{
boolean rtl = ViewCompat.getLayoutDirection(panel) == ViewCompat.LAYOUT_DIRECTION_RTL;
mCheatDetailsLastFocus.requestFocus(rtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT);
}
@Override
public void onPanelClosed(@NonNull View panel)
{
boolean rtl = ViewCompat.getLayoutDirection(panel) == ViewCompat.LAYOUT_DIRECTION_RTL;
mCheatListLastFocus.requestFocus(rtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT);
}
private void onSelectedCheatChanged(Cheat selectedCheat)
{
boolean cheatSelected = selectedCheat != null;
if (!cheatSelected && mBinding.slidingPaneLayout.isOpen())
mBinding.slidingPaneLayout.close();
mBinding.slidingPaneLayout.setLockMode(cheatSelected ?
SlidingPaneLayout.LOCK_MODE_UNLOCKED : SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED);
}
public void onListViewFocusChange(boolean hasFocus)
{
if (hasFocus)
{
mCheatListLastFocus = mBinding.cheatList.findFocus();
if (mCheatListLastFocus == null)
throw new NullPointerException();
mBinding.slidingPaneLayout.close();
}
}
public void onDetailsViewFocusChange(boolean hasFocus)
{
if (hasFocus)
{
mCheatDetailsLastFocus = mBinding.cheatDetails.findFocus();
if (mCheatDetailsLastFocus == null)
throw new NullPointerException();
mBinding.slidingPaneLayout.open();
}
}
@Override
public boolean onSupportNavigateUp()
{
onBackPressed();
return true;
}
private void openDetailsView(boolean open)
{
if (open)
mBinding.slidingPaneLayout.open();
}
public Settings loadGameSpecificSettings()
{
Settings settings = new Settings();
settings.loadSettings(null, mGameId, mRevision, mIsWii);
return settings;
}
public void downloadGeckoCodes()
{
AlertDialog progressDialog = new MaterialAlertDialogBuilder(this)
.setTitle(R.string.cheats_downloading)
.setView(R.layout.dialog_indeterminate_progress)
.setCancelable(false)
.show();
new Thread(() ->
{
GeckoCheat[] codes = GeckoCheat.downloadCodes(mGameTdbId);
runOnUiThread(() ->
{
progressDialog.dismiss();
if (codes == null)
{
new MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.cheats_download_failed))
.setPositiveButton(R.string.ok, null)
.show();
}
else if (codes.length == 0)
{
new MaterialAlertDialogBuilder(this)
.setMessage(getString(R.string.cheats_download_empty))
.setPositiveButton(R.string.ok, null)
.show();
}
else
{
int cheatsAdded = mViewModel.addDownloadedGeckoCodes(codes);
String message = getString(R.string.cheats_download_succeeded, codes.length, cheatsAdded);
new MaterialAlertDialogBuilder(this)
.setMessage(message)
.setPositiveButton(R.string.ok, null)
.show();
}
});
}).start();
}
public static void setOnFocusChangeListenerRecursively(@NonNull View view,
View.OnFocusChangeListener listener)
{
view.setOnFocusChangeListener(listener);
if (view instanceof ViewGroup)
{
ViewGroup viewGroup = (ViewGroup) view;
for (int i = 0; i < viewGroup.getChildCount(); i++)
{
View child = viewGroup.getChildAt(i);
setOnFocusChangeListenerRecursively(child, listener);
}
}
}
private void setInsets()
{
ViewCompat.setOnApplyWindowInsetsListener(mBinding.appbarCheats, (v, windowInsets) ->
{
Insets barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
Insets keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime());
InsetsHelper.insetAppBar(barInsets, mBinding.appbarCheats);
mBinding.slidingPaneLayout.setPadding(barInsets.left, 0, barInsets.right, 0);
// Set keyboard insets if the system supports smooth keyboard animations
ViewGroup.MarginLayoutParams mlpDetails =
(ViewGroup.MarginLayoutParams) mBinding.cheatDetails.getLayoutParams();
if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.R)
{
if (keyboardInsets.bottom > 0)
{
mlpDetails.bottomMargin = keyboardInsets.bottom;
}
else
{
mlpDetails.bottomMargin = barInsets.bottom;
}
}
else
{
if (mlpDetails.bottomMargin == 0)
{
mlpDetails.bottomMargin = barInsets.bottom;
}
}
mBinding.cheatDetails.setLayoutParams(mlpDetails);
InsetsHelper.applyNavbarWorkaround(barInsets.bottom, mBinding.workaroundView);
ThemeHelper.setNavigationBarColor(this,
MaterialColors.getColor(mBinding.appbarCheats, R.attr.colorSurface));
return windowInsets;
});
// Update the layout for every frame that the keyboard animates in
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.R)
{
ViewCompat.setWindowInsetsAnimationCallback(mBinding.cheatDetails,
new WindowInsetsAnimationCompat.Callback(
WindowInsetsAnimationCompat.Callback.DISPATCH_MODE_STOP)
{
int keyboardInsets = 0;
int barInsets = 0;
@NonNull
@Override
public WindowInsetsCompat onProgress(@NonNull WindowInsetsCompat insets,
@NonNull List<WindowInsetsAnimationCompat> runningAnimations)
{
ViewGroup.MarginLayoutParams mlpDetails =
(ViewGroup.MarginLayoutParams) mBinding.cheatDetails.getLayoutParams();
keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom;
barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom;
mlpDetails.bottomMargin = Math.max(keyboardInsets, barInsets);
mBinding.cheatDetails.setLayoutParams(mlpDetails);
return insets;
}
});
}
}
}

View File

@ -0,0 +1,293 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.Menu
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.annotation.ColorInt
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsAnimationCompat
import androidx.core.view.WindowInsetsCompat
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.slidingpanelayout.widget.SlidingPaneLayout
import androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.elevation.ElevationOverlayProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.ActivityCheatsBinding
import org.dolphinemu.dolphinemu.features.cheats.model.Cheat
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel
import org.dolphinemu.dolphinemu.features.cheats.model.GeckoCheat.Companion.downloadCodes
import org.dolphinemu.dolphinemu.features.settings.model.Settings
import org.dolphinemu.dolphinemu.ui.TwoPaneOnBackPressedCallback
import org.dolphinemu.dolphinemu.ui.main.MainPresenter
import org.dolphinemu.dolphinemu.utils.InsetsHelper
import org.dolphinemu.dolphinemu.utils.ThemeHelper
class CheatsActivity : AppCompatActivity(), PanelSlideListener {
private var gameId: String? = null
private var gameTdbId: String? = null
private var revision = 0
private var isWii = false
private lateinit var viewModel: CheatsViewModel
private var cheatListLastFocus: View? = null
private var cheatDetailsLastFocus: View? = null
private lateinit var binding: ActivityCheatsBinding
override fun onCreate(savedInstanceState: Bundle?) {
ThemeHelper.setTheme(this)
super.onCreate(savedInstanceState)
MainPresenter.skipRescanningLibrary()
gameId = intent.getStringExtra(ARG_GAME_ID)
gameTdbId = intent.getStringExtra(ARG_GAMETDB_ID)
revision = intent.getIntExtra(ARG_REVISION, 0)
isWii = intent.getBooleanExtra(ARG_IS_WII, true)
title = getString(R.string.cheats_with_game_id, gameId)
viewModel = ViewModelProvider(this)[CheatsViewModel::class.java]
viewModel.load(gameId!!, revision)
binding = ActivityCheatsBinding.inflate(layoutInflater)
setContentView(binding.root)
WindowCompat.setDecorFitsSystemWindows(window, false)
cheatListLastFocus = binding.cheatList
cheatDetailsLastFocus = binding.cheatDetails
binding.slidingPaneLayout.addPanelSlideListener(this)
onBackPressedDispatcher.addCallback(
this,
TwoPaneOnBackPressedCallback(binding.slidingPaneLayout)
)
viewModel.selectedCheat.observe(this) { selectedCheat: Cheat? ->
onSelectedCheatChanged(
selectedCheat
)
}
onSelectedCheatChanged(viewModel.selectedCheat.value)
viewModel.openDetailsViewEvent.observe(this) { open: Boolean -> openDetailsView(open) }
setSupportActionBar(binding.toolbarCheats)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
setInsets()
@ColorInt val color =
ElevationOverlayProvider(binding.toolbarCheats.context).compositeOverlay(
MaterialColors.getColor(binding.toolbarCheats, R.attr.colorSurface),
resources.getDimensionPixelSize(R.dimen.elevated_app_bar).toFloat()
)
binding.toolbarCheats.setBackgroundColor(color)
ThemeHelper.setStatusBarColor(this, color)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_settings, menu)
return true
}
override fun onStop() {
super.onStop()
viewModel.saveIfNeeded(gameId!!, revision)
}
override fun onPanelSlide(panel: View, slideOffset: Float) {}
override fun onPanelOpened(panel: View) {
val rtl = ViewCompat.getLayoutDirection(panel) == ViewCompat.LAYOUT_DIRECTION_RTL
cheatDetailsLastFocus!!.requestFocus(if (rtl) View.FOCUS_LEFT else View.FOCUS_RIGHT)
}
override fun onPanelClosed(panel: View) {
val rtl = ViewCompat.getLayoutDirection(panel) == ViewCompat.LAYOUT_DIRECTION_RTL
cheatListLastFocus!!.requestFocus(if (rtl) View.FOCUS_RIGHT else View.FOCUS_LEFT)
}
private fun onSelectedCheatChanged(selectedCheat: Cheat?) {
val cheatSelected = selectedCheat != null
if (!cheatSelected && binding.slidingPaneLayout.isOpen) binding.slidingPaneLayout.close()
binding.slidingPaneLayout.lockMode =
if (cheatSelected) SlidingPaneLayout.LOCK_MODE_UNLOCKED else SlidingPaneLayout.LOCK_MODE_LOCKED_CLOSED
}
fun onListViewFocusChange(hasFocus: Boolean) {
if (hasFocus) {
cheatListLastFocus = binding.cheatList.findFocus()
if (cheatListLastFocus == null) throw NullPointerException()
binding.slidingPaneLayout.close()
}
}
fun onDetailsViewFocusChange(hasFocus: Boolean) {
if (hasFocus) {
cheatDetailsLastFocus = binding.cheatDetails.findFocus()
if (cheatDetailsLastFocus == null) throw NullPointerException()
binding.slidingPaneLayout.open()
}
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
private fun openDetailsView(open: Boolean) {
if (open) binding.slidingPaneLayout.open()
}
fun loadGameSpecificSettings(): Settings {
val settings = Settings()
settings.loadSettings(null, gameId, revision, isWii)
return settings
}
fun downloadGeckoCodes() {
val progressDialog = MaterialAlertDialogBuilder(this)
.setTitle(R.string.cheats_downloading)
.setView(R.layout.dialog_indeterminate_progress)
.setCancelable(false)
.show()
lifecycleScope.launchWhenResumed {
withContext(Dispatchers.IO) {
val codes = downloadCodes(gameTdbId!!)
withContext(Dispatchers.Main) {
progressDialog.dismiss()
if (codes == null) {
MaterialAlertDialogBuilder(binding.root.context)
.setMessage(getString(R.string.cheats_download_failed))
.setPositiveButton(R.string.ok, null)
.show()
} else if (codes.isEmpty()) {
MaterialAlertDialogBuilder(binding.root.context)
.setMessage(getString(R.string.cheats_download_empty))
.setPositiveButton(R.string.ok, null)
.show()
} else {
val cheatsAdded = viewModel.addDownloadedGeckoCodes(codes)
val message =
getString(R.string.cheats_download_succeeded, codes.size, cheatsAdded)
MaterialAlertDialogBuilder(binding.root.context)
.setMessage(message)
.setPositiveButton(R.string.ok, null)
.show()
}
}
}
}
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding.appbarCheats) { _: View?, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val keyboardInsets = windowInsets.getInsets(WindowInsetsCompat.Type.ime())
InsetsHelper.insetAppBar(barInsets, binding.appbarCheats)
binding.slidingPaneLayout.setPadding(barInsets.left, 0, barInsets.right, 0)
// Set keyboard insets if the system supports smooth keyboard animations
val mlpDetails = binding.cheatDetails.layoutParams as MarginLayoutParams
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
if (keyboardInsets.bottom > 0) {
mlpDetails.bottomMargin = keyboardInsets.bottom
} else {
mlpDetails.bottomMargin = barInsets.bottom
}
} else {
if (mlpDetails.bottomMargin == 0) {
mlpDetails.bottomMargin = barInsets.bottom
}
}
binding.cheatDetails.layoutParams = mlpDetails
InsetsHelper.applyNavbarWorkaround(barInsets.bottom, binding.workaroundView)
ThemeHelper.setNavigationBarColor(
this,
MaterialColors.getColor(binding.appbarCheats, R.attr.colorSurface)
)
windowInsets
}
// Update the layout for every frame that the keyboard animates in
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
ViewCompat.setWindowInsetsAnimationCallback(
binding.cheatDetails,
object : WindowInsetsAnimationCompat.Callback(DISPATCH_MODE_STOP) {
var keyboardInsets = 0
var barInsets = 0
override fun onProgress(
insets: WindowInsetsCompat,
runningAnimations: List<WindowInsetsAnimationCompat>
): WindowInsetsCompat {
val mlpDetails = binding.cheatDetails.layoutParams as MarginLayoutParams
keyboardInsets = insets.getInsets(WindowInsetsCompat.Type.ime()).bottom
barInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()).bottom
mlpDetails.bottomMargin = keyboardInsets.coerceAtLeast(barInsets)
binding.cheatDetails.layoutParams = mlpDetails
return insets
}
})
}
}
companion object {
private const val ARG_GAME_ID = "game_id"
private const val ARG_GAMETDB_ID = "gametdb_id"
private const val ARG_REVISION = "revision"
private const val ARG_IS_WII = "is_wii"
@JvmStatic
fun launch(
context: Context,
gameId: String,
gameTdbId: String,
revision: Int,
isWii: Boolean
) {
val intent = Intent(context, CheatsActivity::class.java)
intent.putExtra(ARG_GAME_ID, gameId)
intent.putExtra(ARG_GAMETDB_ID, gameTdbId)
intent.putExtra(ARG_REVISION, revision)
intent.putExtra(ARG_IS_WII, isWii)
context.startActivity(intent)
}
@JvmStatic
fun setOnFocusChangeListenerRecursively(
view: View,
listener: View.OnFocusChangeListener?
) {
view.onFocusChangeListener = listener
if (view is ViewGroup) {
for (i in 0 until view.childCount) {
val child = view.getChildAt(i)
setOnFocusChangeListenerRecursively(child, listener)
}
}
}
}
}

View File

@ -1,176 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.databinding.ListItemCheatBinding;
import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding;
import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding;
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.GraphicsMod;
import org.dolphinemu.dolphinemu.features.cheats.model.PatchCheat;
import java.util.ArrayList;
public class CheatsAdapter extends RecyclerView.Adapter<CheatItemViewHolder>
{
private final CheatsActivity mActivity;
private final CheatsViewModel mViewModel;
public CheatsAdapter(CheatsActivity activity, CheatsViewModel viewModel)
{
mActivity = activity;
mViewModel = viewModel;
mViewModel.getCheatAddedEvent().observe(activity, (position) ->
{
if (position != null)
notifyItemInserted(position);
});
mViewModel.getCheatChangedEvent().observe(activity, (position) ->
{
if (position != null)
notifyItemChanged(position);
});
mViewModel.getCheatDeletedEvent().observe(activity, (position) ->
{
if (position != null)
notifyItemRemoved(position);
});
mViewModel.getGeckoCheatsDownloadedEvent().observe(activity, (cheatsAdded) ->
{
if (cheatsAdded != null)
{
int positionEnd = getItemCount() - 2; // Skip "Add Gecko Code" and "Download Gecko Codes"
int positionStart = positionEnd - cheatsAdded;
notifyItemRangeInserted(positionStart, cheatsAdded);
}
});
}
@NonNull
@Override
public CheatItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType)
{
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
switch (viewType)
{
case CheatItem.TYPE_CHEAT:
ListItemCheatBinding listItemCheatBinding = ListItemCheatBinding.inflate(inflater);
addViewListeners(listItemCheatBinding.getRoot());
return new CheatViewHolder(listItemCheatBinding);
case CheatItem.TYPE_HEADER:
ListItemHeaderBinding listItemHeaderBinding = ListItemHeaderBinding.inflate(inflater);
addViewListeners(listItemHeaderBinding.getRoot());
return new HeaderViewHolder(listItemHeaderBinding);
case CheatItem.TYPE_ACTION:
ListItemSubmenuBinding listItemSubmenuBinding = ListItemSubmenuBinding.inflate(inflater);
addViewListeners(listItemSubmenuBinding.getRoot());
return new ActionViewHolder(listItemSubmenuBinding);
default:
throw new UnsupportedOperationException();
}
}
@Override
public void onBindViewHolder(@NonNull CheatItemViewHolder holder, int position)
{
holder.bind(mActivity, getItemAt(position), position);
}
@Override
public int getItemCount()
{
return mViewModel.getGraphicsMods().size() + mViewModel.getPatchCheats().size() +
mViewModel.getARCheats().size() + mViewModel.getGeckoCheats().size() + 8;
}
@Override
public int getItemViewType(int position)
{
return getItemAt(position).getType();
}
private void addViewListeners(View view)
{
CheatsActivity.setOnFocusChangeListenerRecursively(view,
(v, hasFocus) -> mActivity.onListViewFocusChange(hasFocus));
}
private CheatItem getItemAt(int position)
{
// Graphics mods
if (position == 0)
return new CheatItem(CheatItem.TYPE_HEADER, R.string.cheats_header_graphics_mod);
position -= 1;
ArrayList<GraphicsMod> graphicsMods = mViewModel.getGraphicsMods();
if (position < graphicsMods.size())
return new CheatItem(graphicsMods.get(position));
position -= graphicsMods.size();
// Patches
if (position == 0)
return new CheatItem(CheatItem.TYPE_HEADER, R.string.cheats_header_patch);
position -= 1;
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;
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;
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;
if (position == 0)
return new CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_download_gecko);
throw new IndexOutOfBoundsException();
}
}

View File

@ -0,0 +1,138 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.ListItemCheatBinding
import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding
import org.dolphinemu.dolphinemu.databinding.ListItemSubmenuBinding
import org.dolphinemu.dolphinemu.features.cheats.model.CheatsViewModel
import org.dolphinemu.dolphinemu.features.cheats.ui.CheatsActivity.Companion.setOnFocusChangeListenerRecursively
class CheatsAdapter(
private val activity: CheatsActivity,
private val viewModel: CheatsViewModel
) : RecyclerView.Adapter<CheatItemViewHolder>() {
init {
viewModel.cheatAddedEvent.observe(activity) { position: Int? ->
position?.let { notifyItemInserted(it) }
}
viewModel.cheatChangedEvent.observe(activity) { position: Int? ->
position?.let { notifyItemChanged(it) }
}
viewModel.cheatDeletedEvent.observe(activity) { position: Int? ->
position?.let { notifyItemRemoved(it) }
}
viewModel.geckoCheatsDownloadedEvent.observe(activity) { cheatsAdded: Int? ->
cheatsAdded?.let {
val positionEnd = itemCount - 2 // Skip "Add Gecko Code" and "Download Gecko Codes"
val positionStart = positionEnd - it
notifyItemRangeInserted(positionStart, it)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CheatItemViewHolder {
val inflater = LayoutInflater.from(parent.context)
return when (viewType) {
CheatItem.TYPE_CHEAT -> {
val listItemCheatBinding = ListItemCheatBinding.inflate(inflater)
addViewListeners(listItemCheatBinding.getRoot())
CheatViewHolder(listItemCheatBinding)
}
CheatItem.TYPE_HEADER -> {
val listItemHeaderBinding = ListItemHeaderBinding.inflate(inflater)
addViewListeners(listItemHeaderBinding.root)
HeaderViewHolder(listItemHeaderBinding)
}
CheatItem.TYPE_ACTION -> {
val listItemSubmenuBinding = ListItemSubmenuBinding.inflate(inflater)
addViewListeners(listItemSubmenuBinding.root)
ActionViewHolder(listItemSubmenuBinding)
}
else -> throw UnsupportedOperationException()
}
}
override fun onBindViewHolder(holder: CheatItemViewHolder, position: Int) {
holder.bind(activity, getItemAt(position), position)
}
override fun getItemCount(): Int {
return viewModel.graphicsMods.size + viewModel.patchCheats.size + viewModel.aRCheats.size +
viewModel.geckoCheats.size + 8
}
override fun getItemViewType(position: Int): Int {
return getItemAt(position).type
}
private fun addViewListeners(view: View) {
setOnFocusChangeListenerRecursively(view) { _: View?, hasFocus: Boolean ->
activity.onListViewFocusChange(
hasFocus
)
}
}
private fun getItemAt(position: Int): CheatItem {
// Graphics mods
var itemPosition = position
if (itemPosition == 0) return CheatItem(
CheatItem.TYPE_HEADER,
R.string.cheats_header_graphics_mod
)
itemPosition -= 1
val graphicsMods = viewModel.graphicsMods
if (itemPosition < graphicsMods.size) return CheatItem(graphicsMods[itemPosition])
itemPosition -= graphicsMods.size
// Patches
if (itemPosition == 0) return CheatItem(CheatItem.TYPE_HEADER, R.string.cheats_header_patch)
itemPosition -= 1
val patchCheats = viewModel.patchCheats
if (itemPosition < patchCheats.size) return CheatItem(patchCheats[itemPosition])
itemPosition -= patchCheats.size
if (itemPosition == 0) return CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_add_patch)
itemPosition -= 1
// AR codes
if (itemPosition == 0) return CheatItem(CheatItem.TYPE_HEADER, R.string.cheats_header_ar)
itemPosition -= 1
val arCheats = viewModel.aRCheats
if (itemPosition < arCheats.size) return CheatItem(arCheats[itemPosition])
itemPosition -= arCheats.size
if (itemPosition == 0) return CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_add_ar)
itemPosition -= 1
// Gecko codes
if (itemPosition == 0) return CheatItem(CheatItem.TYPE_HEADER, R.string.cheats_header_gecko)
itemPosition -= 1
val geckoCheats = viewModel.geckoCheats
if (itemPosition < geckoCheats.size) return CheatItem(geckoCheats[itemPosition])
itemPosition -= geckoCheats.size
if (itemPosition == 0) return CheatItem(CheatItem.TYPE_ACTION, R.string.cheats_add_gecko)
itemPosition -= 1
if (itemPosition == 0) return CheatItem(
CheatItem.TYPE_ACTION,
R.string.cheats_download_gecko
)
throw IndexOutOfBoundsException()
}
}

View File

@ -1,16 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
public class CheatsDisabledWarningFragment extends SettingDisabledWarningFragment
{
public CheatsDisabledWarningFragment()
{
super(BooleanSetting.MAIN_ENABLE_CHEATS, MenuTag.CONFIG_GENERAL,
R.string.cheats_disabled_warning);
}
}

View File

@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
class CheatsDisabledWarningFragment : SettingDisabledWarningFragment(
BooleanSetting.MAIN_ENABLE_CHEATS,
MenuTag.CONFIG_GENERAL,
R.string.cheats_disabled_warning
)

View File

@ -1,16 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
public class GraphicsModsDisabledWarningFragment extends SettingDisabledWarningFragment
{
public GraphicsModsDisabledWarningFragment()
{
super(BooleanSetting.GFX_MODS_ENABLE, MenuTag.ADVANCED_GRAPHICS,
R.string.gfx_mods_disabled_warning);
}
}

View File

@ -0,0 +1,13 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
class GraphicsModsDisabledWarningFragment : SettingDisabledWarningFragment(
BooleanSetting.GFX_MODS_ENABLE,
MenuTag.ADVANCED_GRAPHICS,
R.string.gfx_mods_disabled_warning
)

View File

@ -1,28 +0,0 @@
// 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.databinding.ListItemHeaderBinding;
public class HeaderViewHolder extends CheatItemViewHolder
{
private TextView mHeaderName;
public HeaderViewHolder(@NonNull ListItemHeaderBinding binding)
{
super(binding.getRoot());
mHeaderName = binding.textHeaderName;
}
public void bind(CheatsActivity activity, CheatItem item, int position)
{
mHeaderName.setText(item.getString());
}
}

View File

@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import android.widget.TextView
import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding
class HeaderViewHolder(binding: ListItemHeaderBinding) : CheatItemViewHolder(binding.root) {
private val headerName: TextView
init {
headerName = binding.textHeaderName
}
override fun bind(activity: CheatsActivity, item: CheatItem, position: Int) {
headerName.setText(item.string)
}
}

View File

@ -1,85 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import org.dolphinemu.dolphinemu.databinding.FragmentCheatWarningBinding;
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting;
import org.dolphinemu.dolphinemu.features.settings.model.Settings;
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag;
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity;
public abstract class SettingDisabledWarningFragment extends Fragment
implements View.OnClickListener
{
private View mView;
private final AbstractBooleanSetting mSetting;
private final MenuTag mSettingShortcut;
private final int mText;
private FragmentCheatWarningBinding mBinding;
public SettingDisabledWarningFragment(AbstractBooleanSetting setting, MenuTag settingShortcut,
int text)
{
mSetting = setting;
mSettingShortcut = settingShortcut;
mText = text;
}
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState)
{
mBinding = FragmentCheatWarningBinding.inflate(inflater, container, false);
return mBinding.getRoot();
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
{
mView = view;
mBinding.textWarning.setText(mText);
mBinding.buttonSettings.setOnClickListener(this);
CheatsActivity activity = (CheatsActivity) requireActivity();
CheatsActivity.setOnFocusChangeListenerRecursively(view,
(v, hasFocus) -> activity.onListViewFocusChange(hasFocus));
}
@Override
public void onResume()
{
super.onResume();
CheatsActivity activity = (CheatsActivity) requireActivity();
try (Settings settings = activity.loadGameSpecificSettings())
{
boolean cheatsEnabled = mSetting.getBoolean(settings);
mView.setVisibility(cheatsEnabled ? View.GONE : View.VISIBLE);
}
}
@Override
public void onDestroyView()
{
super.onDestroyView();
mBinding = null;
}
public void onClick(View view)
{
SettingsActivity.launch(requireContext(), mSettingShortcut);
}
}

View File

@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.cheats.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import org.dolphinemu.dolphinemu.databinding.FragmentCheatWarningBinding
import org.dolphinemu.dolphinemu.features.cheats.ui.CheatsActivity.Companion.setOnFocusChangeListenerRecursively
import org.dolphinemu.dolphinemu.features.settings.model.AbstractBooleanSetting
import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivity
abstract class SettingDisabledWarningFragment(
private val setting: AbstractBooleanSetting,
private val settingShortcut: MenuTag,
private val text: Int
) : Fragment(), View.OnClickListener {
private var _binding: FragmentCheatWarningBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentCheatWarningBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
binding.textWarning.setText(text)
binding.buttonSettings.setOnClickListener(this)
val activity = requireActivity() as CheatsActivity
setOnFocusChangeListenerRecursively(view) { _: View?, hasFocus: Boolean ->
activity.onListViewFocusChange(hasFocus)
}
}
override fun onResume() {
super.onResume()
val activity = requireActivity() as CheatsActivity
activity.loadGameSpecificSettings().use { settings ->
val cheatsEnabled = setting.getBoolean(settings)
requireView().visibility = if (cheatsEnabled) View.GONE else View.VISIBLE
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onClick(view: View) {
SettingsActivity.launch(requireContext(), settingShortcut)
}
}

View File

@ -1,56 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.ui;
import android.content.Context;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.slidingpanelayout.widget.SlidingPaneLayout;
public class TwoPaneOnBackPressedCallback extends OnBackPressedCallback
implements SlidingPaneLayout.PanelSlideListener
{
private final SlidingPaneLayout mSlidingPaneLayout;
public TwoPaneOnBackPressedCallback(@NonNull SlidingPaneLayout slidingPaneLayout)
{
super(slidingPaneLayout.isSlideable() && slidingPaneLayout.isOpen());
mSlidingPaneLayout = slidingPaneLayout;
slidingPaneLayout.addPanelSlideListener(this);
}
@Override
public void handleOnBackPressed()
{
mSlidingPaneLayout.close();
}
@Override
public void onPanelSlide(@NonNull View panel, float slideOffset)
{
}
@Override
public void onPanelOpened(@NonNull View panel)
{
setEnabled(true);
}
@Override
public void onPanelClosed(@NonNull View panel)
{
closeKeyboard();
setEnabled(false);
}
private void closeKeyboard()
{
InputMethodManager manager = (InputMethodManager) mSlidingPaneLayout.getContext()
.getSystemService(Context.INPUT_METHOD_SERVICE);
manager.hideSoftInputFromWindow(mSlidingPaneLayout.getRootView().getWindowToken(), 0);
}
}

View File

@ -0,0 +1,38 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.ui
import android.content.Context
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.activity.OnBackPressedCallback
import androidx.slidingpanelayout.widget.SlidingPaneLayout
import androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener
class TwoPaneOnBackPressedCallback(private val slidingPaneLayout: SlidingPaneLayout) :
OnBackPressedCallback(slidingPaneLayout.isSlideable && slidingPaneLayout.isOpen),
PanelSlideListener {
init {
slidingPaneLayout.addPanelSlideListener(this)
}
override fun handleOnBackPressed() {
slidingPaneLayout.close()
}
override fun onPanelSlide(panel: View, slideOffset: Float) {}
override fun onPanelOpened(panel: View) {
isEnabled = true
}
override fun onPanelClosed(panel: View) {
closeKeyboard()
isEnabled = false
}
private fun closeKeyboard() {
val manager = slidingPaneLayout.context
.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
manager.hideSoftInputFromWindow(slidingPaneLayout.rootView.windowToken, 0)
}
}

View File

@ -500,7 +500,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
const jclass ar_cheat_class =
env->FindClass("org/dolphinemu/dolphinemu/features/cheats/model/ARCheat");
s_ar_cheat_class = reinterpret_cast<jclass>(env->NewGlobalRef(ar_cheat_class));
s_ar_cheat_pointer = env->GetFieldID(ar_cheat_class, "mPointer", "J");
s_ar_cheat_pointer = env->GetFieldID(ar_cheat_class, "pointer", "J");
s_ar_cheat_constructor = env->GetMethodID(ar_cheat_class, "<init>", "(J)V");
env->DeleteLocalRef(ar_cheat_class);
@ -514,7 +514,7 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
const jclass patch_cheat_class =
env->FindClass("org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat");
s_patch_cheat_class = reinterpret_cast<jclass>(env->NewGlobalRef(patch_cheat_class));
s_patch_cheat_pointer = env->GetFieldID(patch_cheat_class, "mPointer", "J");
s_patch_cheat_pointer = env->GetFieldID(patch_cheat_class, "pointer", "J");
s_patch_cheat_constructor = env->GetMethodID(patch_cheat_class, "<init>", "(J)V");
env->DeleteLocalRef(patch_cheat_class);
@ -522,14 +522,14 @@ JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved)
env->FindClass("org/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup");
s_graphics_mod_group_class =
reinterpret_cast<jclass>(env->NewGlobalRef(graphics_mod_group_class));
s_graphics_mod_group_pointer = env->GetFieldID(graphics_mod_group_class, "mPointer", "J");
s_graphics_mod_group_pointer = env->GetFieldID(graphics_mod_group_class, "pointer", "J");
s_graphics_mod_group_constructor = env->GetMethodID(graphics_mod_group_class, "<init>", "(J)V");
env->DeleteLocalRef(graphics_mod_group_class);
const jclass graphics_mod_class =
env->FindClass("org/dolphinemu/dolphinemu/features/cheats/model/GraphicsMod");
s_graphics_mod_class = reinterpret_cast<jclass>(env->NewGlobalRef(graphics_mod_class));
s_graphics_mod_pointer = env->GetFieldID(graphics_mod_class, "mPointer", "J");
s_graphics_mod_pointer = env->GetFieldID(graphics_mod_class, "pointer", "J");
s_graphics_mod_constructor =
env->GetMethodID(graphics_mod_class, "<init>",
"(JLorg/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup;)V");

View File

@ -81,7 +81,7 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getEnabled(JNIEnv*
return static_cast<jboolean>(GetPointer(env, obj)->enabled);
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_trySetImpl(
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_setCheatImpl(
JNIEnv* env, jobject obj, jstring name, jstring creator, jstring notes, jstring code_string)
{
ActionReplay::ARCode* code = GetPointer(env, obj);

View File

@ -99,7 +99,7 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_equalsImpl(JNIEn
return *GetPointer(env, obj) == *GetPointer(env, other);
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_trySetImpl(
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_GeckoCheat_setCheatImpl(
JNIEnv* env, jobject obj, jstring name, jstring creator, jstring notes, jstring code_string)
{
Gecko::GeckoCode* code = GetPointer(env, obj);

View File

@ -79,7 +79,7 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getEnabled(JNIEn
return static_cast<jboolean>(GetPointer(env, obj)->enabled);
}
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_trySetImpl(
JNIEXPORT jint JNICALL Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_setCheatImpl(
JNIEnv* env, jobject obj, jstring name, jstring creator, jstring notes, jstring code_string)
{
PatchEngine::Patch* patch = GetPointer(env, obj);