diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java deleted file mode 100644 index 8a168e8bc5..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.kt new file mode 100644 index 0000000000..7d3013ee32 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ARCheat.kt @@ -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 + + @JvmStatic + external fun saveCodes(gameId: String, revision: Int, codes: Array) + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.java deleted file mode 100644 index d672013b4e..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.kt new file mode 100644 index 0000000000..d9e17ffd41 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/AbstractCheat.kt @@ -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 +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java deleted file mode 100644 index 142931b9cb..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.kt new file mode 100644 index 0000000000..848acf3ea1 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/Cheat.kt @@ -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 + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java deleted file mode 100644 index 4d9900c168..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.java +++ /dev/null @@ -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 mSelectedCheat = new MutableLiveData<>(null); - private final MutableLiveData mIsAdding = new MutableLiveData<>(false); - private final MutableLiveData mIsEditing = new MutableLiveData<>(false); - - private final MutableLiveData mCheatAddedEvent = new MutableLiveData<>(null); - private final MutableLiveData mCheatChangedEvent = new MutableLiveData<>(null); - private final MutableLiveData mCheatDeletedEvent = new MutableLiveData<>(null); - private final MutableLiveData mGeckoCheatsDownloadedEvent = new MutableLiveData<>(null); - private final MutableLiveData mOpenDetailsViewEvent = new MutableLiveData<>(false); - - private GraphicsModGroup mGraphicsModGroup; - private ArrayList mGraphicsMods; - private ArrayList mPatchCheats; - private ArrayList mARCheats; - private ArrayList 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 getSelectedCheat() - { - return mSelectedCheat; - } - - public void setSelectedCheat(Cheat cheat, int position) - { - if (mIsEditing.getValue()) - setIsEditing(false); - - mSelectedCheat.setValue(cheat); - mSelectedCheatPosition = position; - } - - public LiveData getIsAdding() - { - return mIsAdding; - } - - public void startAddingCheat(Cheat cheat, int position) - { - mSelectedCheat.setValue(cheat); - mSelectedCheatPosition = position; - - mIsAdding.setValue(true); - mIsEditing.setValue(true); - } - - public void finishAddingCheat() - { - if (!mIsAdding.getValue()) - throw new IllegalStateException(); - - mIsAdding.setValue(false); - mIsEditing.setValue(false); - - Cheat cheat = mSelectedCheat.getValue(); - - if (cheat instanceof PatchCheat) - { - mPatchCheats.add((PatchCheat) mSelectedCheat.getValue()); - cheat.setChangedCallback(() -> mPatchCheatsNeedSaving = true); - mPatchCheatsNeedSaving = true; - } - else if (cheat instanceof ARCheat) - { - mARCheats.add((ARCheat) mSelectedCheat.getValue()); - cheat.setChangedCallback(() -> mPatchCheatsNeedSaving = true); - mARCheatsNeedSaving = true; - } - else if (cheat instanceof GeckoCheat) - { - mGeckoCheats.add((GeckoCheat) mSelectedCheat.getValue()); - cheat.setChangedCallback(() -> mGeckoCheatsNeedSaving = true); - mGeckoCheatsNeedSaving = true; - } - else - { - throw new UnsupportedOperationException(); - } - - notifyCheatAdded(); - } - - public LiveData getIsEditing() - { - return mIsEditing; - } - - public void setIsEditing(boolean isEditing) - { - mIsEditing.setValue(isEditing); - - if (mIsAdding.getValue() && !isEditing) - { - mIsAdding.setValue(false); - setSelectedCheat(null, -1); - } - } - - /** - * When a cheat is added, the integer stored in the returned LiveData - * changes to the position of that cheat, then changes back to null. - */ - public LiveData getCheatAddedEvent() - { - return mCheatAddedEvent; - } - - private void notifyCheatAdded() - { - mCheatAddedEvent.setValue(mSelectedCheatPosition); - mCheatAddedEvent.setValue(null); - } - - /** - * 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 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 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 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 getOpenDetailsViewEvent() - { - return mOpenDetailsViewEvent; - } - - public void openDetailsView() - { - mOpenDetailsViewEvent.setValue(true); - mOpenDetailsViewEvent.setValue(false); - } - - public ArrayList getGraphicsMods() - { - return mGraphicsMods; - } - - public ArrayList getPatchCheats() - { - return mPatchCheats; - } - - public ArrayList getARCheats() - { - return mARCheats; - } - - public ArrayList getGeckoCheats() - { - return mGeckoCheats; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.kt new file mode 100644 index 0000000000..e2d52bbc78 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/CheatsViewModel.kt @@ -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(null) + val selectedCheat: LiveData get() = _selectedCheat + private val _isAdding = MutableLiveData(false) + val isAdding: LiveData get() = _isAdding + private val _isEditing = MutableLiveData(false) + val isEditing: LiveData get() = _isEditing + + private val _cheatAddedEvent = MutableLiveData(null) + val cheatAddedEvent: LiveData get() = _cheatAddedEvent + private val _cheatChangedEvent = MutableLiveData(null) + val cheatChangedEvent: LiveData get() = _cheatChangedEvent + private val _cheatDeletedEvent = MutableLiveData(null) + val cheatDeletedEvent: LiveData get() = _cheatDeletedEvent + private val _geckoCheatsDownloadedEvent = MutableLiveData(null) + val geckoCheatsDownloadedEvent: LiveData get() = _geckoCheatsDownloadedEvent + private val _openDetailsViewEvent = MutableLiveData(false) + val openDetailsViewEvent: LiveData get() = _openDetailsViewEvent + + private var graphicsModGroup: GraphicsModGroup? = null + var graphicsMods: ArrayList = ArrayList() + var patchCheats: ArrayList = ArrayList() + var aRCheats: ArrayList = ArrayList() + var geckoCheats: ArrayList = 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): 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 + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java deleted file mode 100644 index 4397a95d81..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.kt new file mode 100644 index 0000000000..7a23066545 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GeckoCheat.kt @@ -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 + + @JvmStatic + external fun saveCodes(gameId: String, revision: Int, codes: Array) + + @JvmStatic + external fun downloadCodes(gameTdbId: String): Array? + } +} \ No newline at end of file diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsMod.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsMod.java deleted file mode 100644 index 241d15093f..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsMod.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsMod.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsMod.kt new file mode 100644 index 0000000000..3ea8c23e43 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsMod.kt @@ -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) +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup.java deleted file mode 100644 index 0cb0a3c456..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup.kt new file mode 100644 index 0000000000..5d9b8b2217 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup.kt @@ -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 + external get + + external fun save() + + companion object { + @JvmStatic + external fun load(gameId: String): GraphicsModGroup + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java deleted file mode 100644 index 9dcb9f8111..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.kt new file mode 100644 index 0000000000..f4cd9660a4 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/PatchCheat.kt @@ -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 + + @JvmStatic + external fun saveCodes(gameId: String, revision: Int, codes: Array) + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ReadOnlyCheat.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ReadOnlyCheat.java deleted file mode 100644 index 9751c414f8..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ReadOnlyCheat.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ReadOnlyCheat.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ReadOnlyCheat.kt new file mode 100644 index 0000000000..542a407792 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/model/ReadOnlyCheat.kt @@ -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) +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.java deleted file mode 100644 index fd6617cd21..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.java +++ /dev/null @@ -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(); - } - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.kt new file mode 100644 index 0000000000..3bab551ca4 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/ActionViewHolder.kt @@ -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() + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java deleted file mode 100644 index 9e118e3932..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.java +++ /dev/null @@ -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); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.kt new file mode 100644 index 0000000000..1d473ef6e0 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatDetailsFragment.kt @@ -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 + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.java deleted file mode 100644 index b293cd84d1..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.java +++ /dev/null @@ -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; - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.kt new file mode 100644 index 0000000000..55b13a5be5 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItem.kt @@ -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 + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItemViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItemViewHolder.java deleted file mode 100644 index 681f3ae785..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItemViewHolder.java +++ /dev/null @@ -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); -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItemViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItemViewHolder.kt new file mode 100644 index 0000000000..5795d3bc54 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatItemViewHolder.kt @@ -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) +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.java deleted file mode 100644 index b7dc0c8e4e..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.java +++ /dev/null @@ -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; - }); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.kt new file mode 100644 index 0000000000..093c8c3fbe --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatListFragment.kt @@ -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 + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java deleted file mode 100644 index 0623aeabeb..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.java +++ /dev/null @@ -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); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.kt new file mode 100644 index 0000000000..15b51e7299 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatViewHolder.kt @@ -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) + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java deleted file mode 100644 index 8941626b92..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.java +++ /dev/null @@ -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 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; - } - }); - } - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.kt new file mode 100644 index 0000000000..11278360cf --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsActivity.kt @@ -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 + ): 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) + } + } + } + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java deleted file mode 100644 index 3335795c75..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.java +++ /dev/null @@ -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 -{ - 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 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 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 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 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(); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.kt new file mode 100644 index 0000000000..c75330375d --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsAdapter.kt @@ -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() { + 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() + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsDisabledWarningFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsDisabledWarningFragment.java deleted file mode 100644 index f2dfd7b341..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsDisabledWarningFragment.java +++ /dev/null @@ -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); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsDisabledWarningFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsDisabledWarningFragment.kt new file mode 100644 index 0000000000..4209835011 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/CheatsDisabledWarningFragment.kt @@ -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 +) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/GraphicsModsDisabledWarningFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/GraphicsModsDisabledWarningFragment.java deleted file mode 100644 index eaf26f094b..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/GraphicsModsDisabledWarningFragment.java +++ /dev/null @@ -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); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/GraphicsModsDisabledWarningFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/GraphicsModsDisabledWarningFragment.kt new file mode 100644 index 0000000000..7790ba300e --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/GraphicsModsDisabledWarningFragment.kt @@ -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 +) diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/HeaderViewHolder.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/HeaderViewHolder.java deleted file mode 100644 index 8f347d363e..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/HeaderViewHolder.java +++ /dev/null @@ -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()); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/HeaderViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/HeaderViewHolder.kt new file mode 100644 index 0000000000..b314ef58a6 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/HeaderViewHolder.kt @@ -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) + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/SettingDisabledWarningFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/SettingDisabledWarningFragment.java deleted file mode 100644 index 7e703f3fd1..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/SettingDisabledWarningFragment.java +++ /dev/null @@ -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); - } -} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/SettingDisabledWarningFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/SettingDisabledWarningFragment.kt new file mode 100644 index 0000000000..f67b0f3862 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/cheats/ui/SettingDisabledWarningFragment.kt @@ -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) + } +} diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.java deleted file mode 100644 index 0ab829fa77..0000000000 --- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.java +++ /dev/null @@ -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); - } -} - diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.kt new file mode 100644 index 0000000000..e9fa0679d2 --- /dev/null +++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/ui/TwoPaneOnBackPressedCallback.kt @@ -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) + } +} diff --git a/Source/Android/jni/AndroidCommon/IDCache.cpp b/Source/Android/jni/AndroidCommon/IDCache.cpp index 6d23727953..d02ccd9aa9 100644 --- a/Source/Android/jni/AndroidCommon/IDCache.cpp +++ b/Source/Android/jni/AndroidCommon/IDCache.cpp @@ -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(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, "", "(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(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, "", "(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(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, "", "(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(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, "", "(JLorg/dolphinemu/dolphinemu/features/cheats/model/GraphicsModGroup;)V"); diff --git a/Source/Android/jni/Cheats/ARCheat.cpp b/Source/Android/jni/Cheats/ARCheat.cpp index 3211e35c18..a3af52827a 100644 --- a/Source/Android/jni/Cheats/ARCheat.cpp +++ b/Source/Android/jni/Cheats/ARCheat.cpp @@ -81,7 +81,7 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_ARCheat_getEnabled(JNIEnv* return static_cast(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); diff --git a/Source/Android/jni/Cheats/GeckoCheat.cpp b/Source/Android/jni/Cheats/GeckoCheat.cpp index eb1df24947..8b09dc6130 100644 --- a/Source/Android/jni/Cheats/GeckoCheat.cpp +++ b/Source/Android/jni/Cheats/GeckoCheat.cpp @@ -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); diff --git a/Source/Android/jni/Cheats/PatchCheat.cpp b/Source/Android/jni/Cheats/PatchCheat.cpp index 7958fbdf1c..fb5be8d5b5 100644 --- a/Source/Android/jni/Cheats/PatchCheat.cpp +++ b/Source/Android/jni/Cheats/PatchCheat.cpp @@ -79,7 +79,7 @@ Java_org_dolphinemu_dolphinemu_features_cheats_model_PatchCheat_getEnabled(JNIEn return static_cast(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);