summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2023-06-11 18:57:45 +0200
committerGitHub <noreply@github.com>2023-06-11 18:57:45 +0200
commitbcd6e2e6dcf7c8171056b8c4c5e643d00e5a0bc9 (patch)
treeeddd27db91433f0916b9a012931d1dc6ccd0a5a1 /Source/Android/app/src/main/java
parent35bb663c2abfba0208931b85d779ed5a0084c7e8 (diff)
parent28785c5fad460877798cfdd6c1d8c681aa0d44fa (diff)
Merge pull request #11921 from t895/kotlin-fragments
Android: Convert "fragments" package to Kotlin
Diffstat (limited to 'Source/Android/app/src/main/java')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/CustomFilePickerFragment.java90
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/CustomFilePickerFragment.kt73
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java355
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt264
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.java228
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt210
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/SaveLoadStateFragment.java149
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/SaveLoadStateFragment.kt121
8 files changed, 668 insertions, 822 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/CustomFilePickerFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/CustomFilePickerFragment.java
deleted file mode 100644
index 933b4ad2ff..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/CustomFilePickerFragment.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.fragments;
-
-import android.net.Uri;
-import android.os.Bundle;
-import android.view.View;
-import android.widget.TextView;
-
-import androidx.annotation.NonNull;
-import androidx.core.content.FileProvider;
-
-import com.nononsenseapps.filepicker.FilePickerFragment;
-
-import org.dolphinemu.dolphinemu.R;
-
-import java.io.File;
-import java.util.HashSet;
-
-public class CustomFilePickerFragment extends FilePickerFragment
-{
- public static final String KEY_EXTENSIONS = "KEY_EXTENSIONS";
-
- private HashSet<String> mExtensions;
-
- public void setExtensions(HashSet<String> extensions)
- {
- Bundle b = getArguments();
- if (b == null)
- b = new Bundle();
-
- b.putSerializable(KEY_EXTENSIONS, extensions);
- setArguments(b);
- }
-
- @NonNull
- @Override
- public Uri toUri(@NonNull final File file)
- {
- return FileProvider
- .getUriForFile(getContext(),
- getContext().getApplicationContext().getPackageName() + ".filesprovider",
- file);
- }
-
- @Override public void onActivityCreated(Bundle savedInstanceState)
- {
- super.onActivityCreated(savedInstanceState);
-
- mExtensions = (HashSet<String>) getArguments().getSerializable(KEY_EXTENSIONS);
-
- if (mode == MODE_DIR)
- {
- TextView ok = getActivity().findViewById(R.id.nnf_button_ok);
- ok.setText(R.string.select_dir);
-
- TextView cancel = getActivity().findViewById(R.id.nnf_button_cancel);
- cancel.setVisibility(View.GONE);
- }
- }
-
- @Override
- protected boolean isItemVisible(@NonNull final File file)
- {
- // Some users jump to the conclusion that Dolphin isn't able to detect their
- // files if the files don't show up in the file picker when mode == MODE_DIR.
- // To avoid this, show files even when the user needs to select a directory.
-
- return (showHiddenItems || !file.isHidden()) &&
- (file.isDirectory() ||
- mExtensions.contains(fileExtension(file.getName()).toLowerCase()));
- }
-
- @Override
- public boolean isCheckable(@NonNull final File file)
- {
- // We need to make a small correction to the isCheckable logic due to
- // overriding isItemVisible to show files when mode == MODE_DIR.
- // AbstractFilePickerFragment always treats files as checkable when
- // allowExistingFile == true, but we don't want files to be checkable when mode == MODE_DIR.
-
- return super.isCheckable(file) && !(mode == MODE_DIR && file.isFile());
- }
-
- private static String fileExtension(@NonNull String filename)
- {
- int i = filename.lastIndexOf('.');
- return i < 0 ? "" : filename.substring(i + 1);
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/CustomFilePickerFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/CustomFilePickerFragment.kt
new file mode 100644
index 0000000000..aa74fb6f8a
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/CustomFilePickerFragment.kt
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.fragments
+
+import android.net.Uri
+import android.os.Bundle
+import android.view.View
+import android.widget.TextView
+import androidx.core.content.FileProvider
+import com.nononsenseapps.filepicker.FilePickerFragment
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
+import java.io.File
+import java.util.Locale
+
+class CustomFilePickerFragment : FilePickerFragment() {
+ private var extensions: HashSet<String>? = null
+
+ fun setExtensions(extensions: HashSet<String>?) {
+ var b = arguments
+ if (b == null)
+ b = Bundle()
+ b.putSerializable(KEY_EXTENSIONS, extensions)
+ arguments = b
+ }
+
+ override fun toUri(file: File): Uri {
+ return FileProvider.getUriForFile(
+ requireContext(),
+ "${requireContext().applicationContext.packageName}.filesprovider",
+ file
+ )
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ extensions = requireArguments().serializable(KEY_EXTENSIONS) as HashSet<String>?
+
+ if (mode == MODE_DIR) {
+ val ok = requireActivity().findViewById<TextView>(R.id.nnf_button_ok)
+ ok.setText(R.string.select_dir)
+
+ val cancel = requireActivity().findViewById<TextView>(R.id.nnf_button_cancel)
+ cancel.visibility = View.GONE
+ }
+ }
+
+ override fun isItemVisible(file: File): Boolean {
+ // Some users jump to the conclusion that Dolphin isn't able to detect their
+ // files if the files don't show up in the file picker when mode == MODE_DIR.
+ // To avoid this, show files even when the user needs to select a directory.
+ return (showHiddenItems || !file.isHidden) &&
+ (file.isDirectory || extensions!!.contains(fileExtension(file.name).lowercase(Locale.getDefault())))
+ }
+
+ override fun isCheckable(file: File): Boolean {
+ // We need to make a small correction to the isCheckable logic due to
+ // overriding isItemVisible to show files when mode == MODE_DIR.
+ // AbstractFilePickerFragment always treats files as checkable when
+ // allowExistingFile == true, but we don't want files to be checkable when mode == MODE_DIR.
+ return super.isCheckable(file) && !(mode == MODE_DIR && file.isFile)
+ }
+
+ companion object {
+ const val KEY_EXTENSIONS = "KEY_EXTENSIONS"
+
+ private fun fileExtension(filename: String): String {
+ val i = filename.lastIndexOf('.')
+ return if (i < 0) "" else filename.substring(i + 1)
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java
deleted file mode 100644
index 3a7c9c1067..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.java
+++ /dev/null
@@ -1,355 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.fragments;
-
-import android.content.Context;
-import android.graphics.Rect;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-
-import org.dolphinemu.dolphinemu.NativeLibrary;
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.databinding.FragmentEmulationBinding;
-import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.Settings;
-import org.dolphinemu.dolphinemu.overlay.InputOverlay;
-import org.dolphinemu.dolphinemu.utils.Log;
-
-import java.io.File;
-
-public final class EmulationFragment extends Fragment implements SurfaceHolder.Callback
-{
- private static final String KEY_GAMEPATHS = "gamepaths";
- private static final String KEY_RIIVOLUTION = "riivolution";
- private static final String KEY_SYSTEM_MENU = "systemMenu";
-
- private InputOverlay mInputOverlay;
-
- private String[] mGamePaths;
- private boolean mRiivolution;
- private boolean mRunWhenSurfaceIsValid;
- private boolean mLoadPreviousTemporaryState;
- private boolean mLaunchSystemMenu;
-
- private EmulationActivity activity;
-
- private FragmentEmulationBinding mBinding;
-
- public static EmulationFragment newInstance(String[] gamePaths, boolean riivolution,
- boolean systemMenu)
- {
- Bundle args = new Bundle();
- args.putStringArray(KEY_GAMEPATHS, gamePaths);
- args.putBoolean(KEY_RIIVOLUTION, riivolution);
- args.putBoolean(KEY_SYSTEM_MENU, systemMenu);
-
- EmulationFragment fragment = new EmulationFragment();
- fragment.setArguments(args);
- return fragment;
- }
-
- @Override
- public void onAttach(@NonNull Context context)
- {
- super.onAttach(context);
-
- if (context instanceof EmulationActivity)
- {
- activity = (EmulationActivity) context;
- NativeLibrary.setEmulationActivity((EmulationActivity) context);
- }
- else
- {
- throw new IllegalStateException("EmulationFragment must have EmulationActivity parent");
- }
- }
-
- /**
- * Initialize anything that doesn't depend on the layout / views in here.
- */
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
-
- mGamePaths = getArguments().getStringArray(KEY_GAMEPATHS);
- mRiivolution = getArguments().getBoolean(KEY_RIIVOLUTION);
- mLaunchSystemMenu = getArguments().getBoolean(KEY_SYSTEM_MENU);
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState)
- {
- mBinding = FragmentEmulationBinding.inflate(inflater, container, false);
- return mBinding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
- {
- // The new Surface created here will get passed to the native code via onSurfaceChanged.
- SurfaceView surfaceView = mBinding.surfaceEmulation;
- surfaceView.getHolder().addCallback(this);
-
- mInputOverlay = mBinding.surfaceInputOverlay;
-
- Button doneButton = mBinding.doneControlConfig;
- if (doneButton != null)
- {
- doneButton.setOnClickListener(v -> stopConfiguringControls());
- }
-
- if (mInputOverlay != null)
- {
- view.post(() ->
- {
- int overlayX = mInputOverlay.getLeft();
- int overlayY = mInputOverlay.getTop();
- mInputOverlay.setSurfacePosition(new Rect(
- surfaceView.getLeft() - overlayX, surfaceView.getTop() - overlayY,
- surfaceView.getRight() - overlayX, surfaceView.getBottom() - overlayY));
- });
- }
- }
-
- @Override
- public void onDestroyView()
- {
- super.onDestroyView();
- mBinding = null;
- }
-
- @Override
- public void onResume()
- {
- super.onResume();
-
- if (mInputOverlay != null && NativeLibrary.IsGameMetadataValid())
- mInputOverlay.refreshControls();
-
- run(activity.isActivityRecreated());
- }
-
- @Override
- public void onPause()
- {
- if (NativeLibrary.IsRunningAndUnpaused() && !NativeLibrary.IsShowingAlertMessage())
- {
- Log.debug("[EmulationFragment] Pausing emulation.");
- NativeLibrary.PauseEmulation();
- }
-
- super.onPause();
- }
-
- @Override
- public void onDestroy()
- {
- if (mInputOverlay != null)
- mInputOverlay.onDestroy();
-
- super.onDestroy();
- }
-
- @Override
- public void onDetach()
- {
- NativeLibrary.clearEmulationActivity();
- super.onDetach();
- }
-
- public void toggleInputOverlayVisibility(Settings settings)
- {
- BooleanSetting.MAIN_SHOW_INPUT_OVERLAY
- .setBoolean(settings, !BooleanSetting.MAIN_SHOW_INPUT_OVERLAY.getBoolean());
-
- if (mInputOverlay != null)
- mInputOverlay.refreshControls();
- }
-
- public void initInputPointer()
- {
- if (mInputOverlay != null)
- mInputOverlay.initTouchPointer();
- }
-
- public void refreshInputOverlay()
- {
- if (mInputOverlay != null)
- mInputOverlay.refreshControls();
- }
-
- public void refreshOverlayPointer()
- {
- if (mInputOverlay != null)
- mInputOverlay.refreshOverlayPointer();
- }
-
- public void resetInputOverlay()
- {
- if (mInputOverlay != null)
- mInputOverlay.resetButtonPlacement();
- }
-
- @Override
- public void surfaceCreated(@NonNull SurfaceHolder holder)
- {
- // We purposely don't do anything here.
- // All work is done in surfaceChanged, which we are guaranteed to get even for surface creation.
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
- {
- Log.debug("[EmulationFragment] Surface changed. Resolution: " + width + "x" + height);
- NativeLibrary.SurfaceChanged(holder.getSurface());
- if (mRunWhenSurfaceIsValid)
- {
- runWithValidSurface();
- }
- }
-
- @Override
- public void surfaceDestroyed(@NonNull SurfaceHolder holder)
- {
- Log.debug("[EmulationFragment] Surface destroyed.");
- NativeLibrary.SurfaceDestroyed();
- mRunWhenSurfaceIsValid = true;
- }
-
- public void stopEmulation()
- {
- Log.debug("[EmulationFragment] Stopping emulation.");
- NativeLibrary.StopEmulation();
- }
-
- public void startConfiguringControls()
- {
- if (mInputOverlay != null)
- {
- mBinding.doneControlConfig.setVisibility(View.VISIBLE);
- mInputOverlay.setEditMode(true);
- }
- }
-
- public void stopConfiguringControls()
- {
- if (mInputOverlay != null)
- {
- mBinding.doneControlConfig.setVisibility(View.GONE);
- mInputOverlay.setEditMode(false);
- }
- }
-
- public boolean isConfiguringControls()
- {
- return mInputOverlay != null && mInputOverlay.isInEditMode();
- }
-
- private void run(boolean isActivityRecreated)
- {
- if (isActivityRecreated)
- {
- if (NativeLibrary.IsRunning())
- {
- mLoadPreviousTemporaryState = false;
- deleteFile(getTemporaryStateFilePath());
- }
- else
- {
- mLoadPreviousTemporaryState = true;
- }
- }
- else
- {
- Log.debug("[EmulationFragment] activity resumed or fresh start");
- mLoadPreviousTemporaryState = false;
- // activity resumed without being killed or this is the first run
- deleteFile(getTemporaryStateFilePath());
- }
-
- // If the surface is set, run now. Otherwise, wait for it to get set.
- if (NativeLibrary.HasSurface())
- {
- runWithValidSurface();
- }
- else
- {
- mRunWhenSurfaceIsValid = true;
- }
- }
-
- private void runWithValidSurface()
- {
- mRunWhenSurfaceIsValid = false;
- if (!NativeLibrary.IsRunning())
- {
- NativeLibrary.SetIsBooting();
-
- Thread emulationThread = new Thread(() ->
- {
- if (mLoadPreviousTemporaryState)
- {
- Log.debug("[EmulationFragment] Starting emulation thread from previous state.");
- NativeLibrary.Run(mGamePaths, mRiivolution, getTemporaryStateFilePath(), true);
- }
- if (mLaunchSystemMenu)
- {
- Log.debug("[EmulationFragment] Starting emulation thread for the Wii Menu.");
- NativeLibrary.RunSystemMenu();
- }
- else
- {
- Log.debug("[EmulationFragment] Starting emulation thread.");
- NativeLibrary.Run(mGamePaths, mRiivolution);
- }
- EmulationActivity.stopIgnoringLaunchRequests();
- }, "NativeEmulation");
- emulationThread.start();
- }
- else
- {
- if (!EmulationActivity.Companion.getHasUserPausedEmulation() &&
- !NativeLibrary.IsShowingAlertMessage())
- {
- Log.debug("[EmulationFragment] Resuming emulation.");
- NativeLibrary.UnPauseEmulation();
- }
- }
- }
-
- public void saveTemporaryState()
- {
- NativeLibrary.SaveStateAs(getTemporaryStateFilePath(), true);
- }
-
- private String getTemporaryStateFilePath()
- {
- return getContext().getFilesDir() + File.separator + "temp.sav";
- }
-
- private static void deleteFile(String path)
- {
- try
- {
- File file = new File(path);
- if (!file.delete())
- {
- Log.error("[EmulationFragment] Failed to delete " + file.getAbsolutePath());
- }
- }
- catch (Exception ignored)
- {
- }
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt
new file mode 100644
index 0000000000..e403e7ef5d
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/EmulationFragment.kt
@@ -0,0 +1,264 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.fragments
+
+import android.content.Context
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.SurfaceHolder
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import org.dolphinemu.dolphinemu.NativeLibrary
+import org.dolphinemu.dolphinemu.activities.EmulationActivity
+import org.dolphinemu.dolphinemu.databinding.FragmentEmulationBinding
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
+import org.dolphinemu.dolphinemu.features.settings.model.Settings
+import org.dolphinemu.dolphinemu.overlay.InputOverlay
+import org.dolphinemu.dolphinemu.utils.Log
+import java.io.File
+
+class EmulationFragment : Fragment(), SurfaceHolder.Callback {
+ private var inputOverlay: InputOverlay? = null
+
+ private var gamePaths: Array<String>? = null
+ private var riivolution = false
+ private var runWhenSurfaceIsValid = false
+ private var loadPreviousTemporaryState = false
+ private var launchSystemMenu = false
+
+ private var emulationActivity: EmulationActivity? = null
+
+ private var _binding: FragmentEmulationBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if (context is EmulationActivity) {
+ emulationActivity = context
+ NativeLibrary.setEmulationActivity(context)
+ } else {
+ throw IllegalStateException("EmulationFragment must have EmulationActivity parent")
+ }
+ }
+
+ /**
+ * Initialize anything that doesn't depend on the layout / views in here.
+ */
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ requireArguments().apply {
+ gamePaths = getStringArray(KEY_GAMEPATHS)
+ riivolution = getBoolean(KEY_RIIVOLUTION)
+ launchSystemMenu = getBoolean(KEY_SYSTEM_MENU)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentEmulationBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ // The new Surface created here will get passed to the native code via onSurfaceChanged.
+ val surfaceView = binding.surfaceEmulation
+ surfaceView.holder.addCallback(this)
+
+ inputOverlay = binding.surfaceInputOverlay
+
+ val doneButton = binding.doneControlConfig
+ doneButton?.setOnClickListener { stopConfiguringControls() }
+
+ if (inputOverlay != null) {
+ view.post {
+ val overlayX = inputOverlay!!.left
+ val overlayY = inputOverlay!!.top
+ inputOverlay?.setSurfacePosition(
+ Rect(
+ surfaceView.left - overlayX,
+ surfaceView.top - overlayY,
+ surfaceView.right - overlayX,
+ surfaceView.bottom - overlayY
+ )
+ )
+ }
+ }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (NativeLibrary.IsGameMetadataValid())
+ inputOverlay?.refreshControls()
+
+ run(emulationActivity!!.isActivityRecreated)
+ }
+
+ override fun onPause() {
+ if (NativeLibrary.IsRunningAndUnpaused() && !NativeLibrary.IsShowingAlertMessage()) {
+ Log.debug("[EmulationFragment] Pausing emulation.")
+ NativeLibrary.PauseEmulation()
+ }
+ super.onPause()
+ }
+
+ override fun onDestroy() {
+ inputOverlay?.onDestroy()
+ super.onDestroy()
+ }
+
+ override fun onDetach() {
+ NativeLibrary.clearEmulationActivity()
+ super.onDetach()
+ }
+
+ fun toggleInputOverlayVisibility(settings: Settings?) {
+ BooleanSetting.MAIN_SHOW_INPUT_OVERLAY.setBoolean(
+ settings!!,
+ !BooleanSetting.MAIN_SHOW_INPUT_OVERLAY.boolean
+ )
+
+ inputOverlay?.refreshControls()
+ }
+
+ fun initInputPointer() = inputOverlay?.initTouchPointer()
+
+ fun refreshInputOverlay() = inputOverlay?.refreshControls()
+
+ fun refreshOverlayPointer() = inputOverlay?.refreshOverlayPointer()
+
+ fun resetInputOverlay() = inputOverlay?.resetButtonPlacement()
+
+ override fun surfaceCreated(holder: SurfaceHolder) {
+ // We purposely don't do anything here.
+ // All work is done in surfaceChanged, which we are guaranteed to get even for surface creation.
+ }
+
+ override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
+ Log.debug("[EmulationFragment] Surface changed. Resolution: $width x $height")
+ NativeLibrary.SurfaceChanged(holder.surface)
+ if (runWhenSurfaceIsValid) {
+ runWithValidSurface()
+ }
+ }
+
+ override fun surfaceDestroyed(holder: SurfaceHolder) {
+ Log.debug("[EmulationFragment] Surface destroyed.")
+ NativeLibrary.SurfaceDestroyed()
+ runWhenSurfaceIsValid = true
+ }
+
+ fun stopEmulation() {
+ Log.debug("[EmulationFragment] Stopping emulation.")
+ NativeLibrary.StopEmulation()
+ }
+
+ fun startConfiguringControls() {
+ binding.doneControlConfig?.visibility = View.VISIBLE
+ inputOverlay?.editMode = true
+ }
+
+ fun stopConfiguringControls() {
+ binding.doneControlConfig?.visibility = View.GONE
+ inputOverlay?.editMode = false
+ }
+
+ val isConfiguringControls: Boolean
+ get() = inputOverlay != null && inputOverlay!!.isInEditMode
+
+ private fun run(isActivityRecreated: Boolean) {
+ if (isActivityRecreated) {
+ if (NativeLibrary.IsRunning()) {
+ loadPreviousTemporaryState = false
+ deleteFile(temporaryStateFilePath)
+ } else {
+ loadPreviousTemporaryState = true
+ }
+ } else {
+ Log.debug("[EmulationFragment] activity resumed or fresh start")
+ loadPreviousTemporaryState = false
+ // activity resumed without being killed or this is the first run
+ deleteFile(temporaryStateFilePath)
+ }
+
+ // If the surface is set, run now. Otherwise, wait for it to get set.
+ if (NativeLibrary.HasSurface()) {
+ runWithValidSurface()
+ } else {
+ runWhenSurfaceIsValid = true
+ }
+ }
+
+ private fun runWithValidSurface() {
+ runWhenSurfaceIsValid = false
+ if (!NativeLibrary.IsRunning()) {
+ NativeLibrary.SetIsBooting()
+ val emulationThread = Thread({
+ if (loadPreviousTemporaryState) {
+ Log.debug("[EmulationFragment] Starting emulation thread from previous state.")
+ NativeLibrary.Run(gamePaths, riivolution, temporaryStateFilePath, true)
+ }
+ if (launchSystemMenu) {
+ Log.debug("[EmulationFragment] Starting emulation thread for the Wii Menu.")
+ NativeLibrary.RunSystemMenu()
+ } else {
+ Log.debug("[EmulationFragment] Starting emulation thread.")
+ NativeLibrary.Run(gamePaths, riivolution)
+ }
+ EmulationActivity.stopIgnoringLaunchRequests()
+ }, "NativeEmulation")
+ emulationThread.start()
+ } else {
+ if (!EmulationActivity.hasUserPausedEmulation && !NativeLibrary.IsShowingAlertMessage()) {
+ Log.debug("[EmulationFragment] Resuming emulation.")
+ NativeLibrary.UnPauseEmulation()
+ }
+ }
+ }
+
+ fun saveTemporaryState() = NativeLibrary.SaveStateAs(temporaryStateFilePath, true)
+
+ private val temporaryStateFilePath: String
+ get() = "${requireContext().filesDir}${File.separator}temp.sav"
+
+ companion object {
+ private const val KEY_GAMEPATHS = "gamepaths"
+ private const val KEY_RIIVOLUTION = "riivolution"
+ private const val KEY_SYSTEM_MENU = "systemMenu"
+
+ fun newInstance(
+ gamePaths: Array<String>?,
+ riivolution: Boolean,
+ systemMenu: Boolean
+ ): EmulationFragment {
+ val args = Bundle()
+ args.apply {
+ putStringArray(KEY_GAMEPATHS, gamePaths)
+ putBoolean(KEY_RIIVOLUTION, riivolution)
+ putBoolean(KEY_SYSTEM_MENU, systemMenu)
+ }
+ val fragment = EmulationFragment()
+ fragment.arguments = args
+ return fragment
+ }
+
+ private fun deleteFile(path: String) {
+ try {
+ val file = File(path)
+ if (!file.delete()) {
+ Log.error("[EmulationFragment] Failed to delete ${file.absolutePath}")
+ }
+ } catch (ignored: Exception) {
+ }
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.java
deleted file mode 100644
index 5f8ce06013..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.java
+++ /dev/null
@@ -1,228 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.fragments;
-
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.util.SparseIntArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.LinearLayout;
-
-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 com.google.android.material.color.MaterialColors;
-import com.google.android.material.elevation.ElevationOverlayProvider;
-
-import org.dolphinemu.dolphinemu.NativeLibrary;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.databinding.FragmentIngameMenuBinding;
-import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting;
-import org.dolphinemu.dolphinemu.features.settings.model.IntSetting;
-import org.dolphinemu.dolphinemu.utils.InsetsHelper;
-import org.dolphinemu.dolphinemu.utils.ThemeHelper;
-
-public final class MenuFragment extends Fragment implements View.OnClickListener
-{
- private static final String KEY_TITLE = "title";
- private static final String KEY_WII = "wii";
- private static SparseIntArray buttonsActionsMap = new SparseIntArray();
-
- private int mCutInset = 0;
-
- static
- {
- buttonsActionsMap
- .append(R.id.menu_pause_emulation, EmulationActivity.MENU_ACTION_PAUSE_EMULATION);
- buttonsActionsMap
- .append(R.id.menu_unpause_emulation, EmulationActivity.MENU_ACTION_UNPAUSE_EMULATION);
- buttonsActionsMap
- .append(R.id.menu_take_screenshot, EmulationActivity.MENU_ACTION_TAKE_SCREENSHOT);
- buttonsActionsMap.append(R.id.menu_quicksave, EmulationActivity.MENU_ACTION_QUICK_SAVE);
- buttonsActionsMap.append(R.id.menu_quickload, EmulationActivity.MENU_ACTION_QUICK_LOAD);
- buttonsActionsMap
- .append(R.id.menu_emulation_save_root, EmulationActivity.MENU_ACTION_SAVE_ROOT);
- buttonsActionsMap
- .append(R.id.menu_emulation_load_root, EmulationActivity.MENU_ACTION_LOAD_ROOT);
- buttonsActionsMap
- .append(R.id.menu_overlay_controls, EmulationActivity.MENU_ACTION_OVERLAY_CONTROLS);
- buttonsActionsMap
- .append(R.id.menu_refresh_wiimotes, EmulationActivity.MENU_ACTION_REFRESH_WIIMOTES);
- buttonsActionsMap.append(R.id.menu_change_disc, EmulationActivity.MENU_ACTION_CHANGE_DISC);
- buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT);
- buttonsActionsMap.append(R.id.menu_settings, EmulationActivity.MENU_ACTION_SETTINGS);
- buttonsActionsMap.append(R.id.menu_skylanders, EmulationActivity.MENU_ACTION_SKYLANDERS);
- buttonsActionsMap.append(R.id.menu_infinitybase, EmulationActivity.MENU_ACTION_INFINITY_BASE);
- }
-
- private FragmentIngameMenuBinding mBinding;
-
- public static MenuFragment newInstance()
- {
- MenuFragment fragment = new MenuFragment();
-
- Bundle arguments = new Bundle();
- if (NativeLibrary.IsGameMetadataValid())
- {
- arguments.putString(KEY_TITLE, NativeLibrary.GetCurrentTitleDescription());
- arguments.putBoolean(KEY_WII, NativeLibrary.IsEmulatingWii());
- }
- fragment.setArguments(arguments);
-
- return fragment;
- }
-
- @NonNull
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState)
- {
- mBinding = FragmentIngameMenuBinding.inflate(inflater, container, false);
- return mBinding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
- {
- if (IntSetting.MAIN_INTERFACE_THEME.getInt() != ThemeHelper.DEFAULT)
- {
- @ColorInt int color = new ElevationOverlayProvider(view.getContext()).compositeOverlay(
- MaterialColors.getColor(view, R.attr.colorSurface),
- view.getElevation());
- view.setBackgroundColor(color);
- }
-
- setInsets();
- updatePauseUnpauseVisibility();
-
- if (!requireActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN))
- {
- mBinding.menuOverlayControls.setVisibility(View.GONE);
- }
-
- if (!getArguments().getBoolean(KEY_WII, true))
- {
- mBinding.menuRefreshWiimotes.setVisibility(View.GONE);
- mBinding.menuSkylanders.setVisibility(View.GONE);
- }
-
- if (!BooleanSetting.MAIN_EMULATE_SKYLANDER_PORTAL.getBoolean())
- {
- mBinding.menuSkylanders.setVisibility(View.GONE);
- }
-
- LinearLayout options = mBinding.layoutOptions;
- for (int childIndex = 0; childIndex < options.getChildCount(); childIndex++)
- {
- Button button = (Button) options.getChildAt(childIndex);
-
- button.setOnClickListener(this);
- }
-
- mBinding.menuExit.setOnClickListener(this);
-
- String title = getArguments().getString(KEY_TITLE, null);
- if (title != null)
- {
- mBinding.textGameTitle.setText(title);
- }
- }
-
- private void setInsets()
- {
- ViewCompat.setOnApplyWindowInsetsListener(mBinding.getRoot(), (v, windowInsets) ->
- {
- Insets cutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout());
- mCutInset = cutInsets.left;
-
- int left = 0;
- int right = 0;
- if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR)
- {
- left = cutInsets.left;
- }
- else
- {
- right = cutInsets.right;
- }
-
- v.post(() -> NativeLibrary.SetObscuredPixelsLeft(v.getWidth()));
-
- // Don't use padding if the navigation bar isn't in the way
- if (InsetsHelper.getBottomPaddingRequired(requireActivity()) > 0)
- {
- v.setPadding(left, cutInsets.top, right,
- cutInsets.bottom + InsetsHelper.getNavigationBarHeight(requireContext()));
- }
- else
- {
- v.setPadding(left, cutInsets.top, right,
- cutInsets.bottom + getResources().getDimensionPixelSize(R.dimen.spacing_large));
- }
- return windowInsets;
- });
- }
-
- @Override
- public void onResume()
- {
- super.onResume();
-
- boolean savestatesEnabled = BooleanSetting.MAIN_ENABLE_SAVESTATES.getBoolean();
- int savestateVisibility = savestatesEnabled ? View.VISIBLE : View.GONE;
- mBinding.menuQuicksave.setVisibility(savestateVisibility);
- mBinding.menuQuickload.setVisibility(savestateVisibility);
- mBinding.menuEmulationSaveRoot.setVisibility(savestateVisibility);
- mBinding.menuEmulationLoadRoot.setVisibility(savestateVisibility);
- }
-
- @Override
- public void onDestroyView()
- {
- super.onDestroyView();
-
- NativeLibrary.SetObscuredPixelsLeft(mCutInset);
- mBinding = null;
- }
-
- private void updatePauseUnpauseVisibility()
- {
- boolean paused = EmulationActivity.Companion.getHasUserPausedEmulation();
-
- mBinding.menuUnpauseEmulation.setVisibility(paused ? View.VISIBLE : View.GONE);
- mBinding.menuPauseEmulation.setVisibility(paused ? View.GONE : View.VISIBLE);
- }
-
- @Override
- public void onClick(View button)
- {
- int action = buttonsActionsMap.get(button.getId());
- EmulationActivity activity = (EmulationActivity) requireActivity();
-
- if (action == EmulationActivity.MENU_ACTION_OVERLAY_CONTROLS)
- {
- // We could use the button parameter as the anchor here, but this often results in a tiny menu
- // (because the button often is in the middle of the screen), so let's use mTitleText instead
- activity.showOverlayControlsMenu(mBinding.textGameTitle);
- }
- else if (action >= 0)
- {
- activity.handleMenuAction(action);
- }
-
- if (action == EmulationActivity.MENU_ACTION_PAUSE_EMULATION ||
- action == EmulationActivity.MENU_ACTION_UNPAUSE_EMULATION)
- {
- updatePauseUnpauseVisibility();
- }
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt
new file mode 100644
index 0000000000..a90d07930c
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt
@@ -0,0 +1,210 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.fragments
+
+import android.content.pm.PackageManager
+import android.os.Bundle
+import android.util.SparseIntArray
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.annotation.ColorInt
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowInsetsCompat
+import androidx.fragment.app.Fragment
+import com.google.android.material.color.MaterialColors
+import com.google.android.material.elevation.ElevationOverlayProvider
+import org.dolphinemu.dolphinemu.NativeLibrary
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.activities.EmulationActivity
+import org.dolphinemu.dolphinemu.databinding.FragmentIngameMenuBinding
+import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
+import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
+import org.dolphinemu.dolphinemu.utils.InsetsHelper
+import org.dolphinemu.dolphinemu.utils.ThemeHelper
+
+class MenuFragment : Fragment(), View.OnClickListener {
+ private var cutInset = 0
+
+ private var _binding: FragmentIngameMenuBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentIngameMenuBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ if (IntSetting.MAIN_INTERFACE_THEME.int != ThemeHelper.DEFAULT) {
+ @ColorInt val color = ElevationOverlayProvider(view.context).compositeOverlay(
+ MaterialColors.getColor(view, R.attr.colorSurface),
+ view.elevation
+ )
+ view.setBackgroundColor(color)
+ }
+
+ setInsets()
+ updatePauseUnpauseVisibility()
+
+ if (!requireActivity().packageManager.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
+ binding.menuOverlayControls.visibility = View.GONE
+ }
+
+ if (!requireArguments().getBoolean(KEY_WII, true)) {
+ binding.menuRefreshWiimotes.visibility = View.GONE
+ binding.menuSkylanders.visibility = View.GONE
+ }
+
+ if (!BooleanSetting.MAIN_EMULATE_SKYLANDER_PORTAL.boolean) {
+ binding.menuSkylanders.visibility = View.GONE
+ }
+
+ val options = binding.layoutOptions
+ for (childIndex in 0 until options.childCount) {
+ val button = options.getChildAt(childIndex) as Button
+ button.setOnClickListener(this)
+ }
+
+ binding.menuExit.setOnClickListener(this)
+
+ val title = requireArguments().getString(KEY_TITLE, null)
+ if (title != null) {
+ binding.textGameTitle.text = title
+ }
+ }
+
+ private fun setInsets() =
+ ViewCompat.setOnApplyWindowInsetsListener(binding.root) { v: View, windowInsets: WindowInsetsCompat ->
+ val cutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
+ cutInset = cutInsets.left
+ var left = 0
+ var right = 0
+ if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
+ left = cutInsets.left
+ } else {
+ right = cutInsets.right
+ }
+ v.post { NativeLibrary.SetObscuredPixelsLeft(v.width) }
+
+ // Don't use padding if the navigation bar isn't in the way
+ if (InsetsHelper.getBottomPaddingRequired(requireActivity()) > 0) {
+ v.setPadding(
+ left, cutInsets.top, right,
+ cutInsets.bottom + InsetsHelper.getNavigationBarHeight(requireContext())
+ )
+ } else {
+ v.setPadding(
+ left, cutInsets.top, right,
+ cutInsets.bottom + resources.getDimensionPixelSize(R.dimen.spacing_large)
+ )
+ }
+ windowInsets
+ }
+
+ override fun onResume() {
+ super.onResume()
+ val savestatesEnabled = BooleanSetting.MAIN_ENABLE_SAVESTATES.boolean
+ val savestateVisibility = if (savestatesEnabled) View.VISIBLE else View.GONE
+ binding.menuQuicksave.visibility = savestateVisibility
+ binding.menuQuickload.visibility = savestateVisibility
+ binding.menuEmulationSaveRoot.visibility = savestateVisibility
+ binding.menuEmulationLoadRoot.visibility = savestateVisibility
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ NativeLibrary.SetObscuredPixelsLeft(cutInset)
+ _binding = null
+ }
+
+ private fun updatePauseUnpauseVisibility() {
+ val paused = EmulationActivity.hasUserPausedEmulation
+ binding.menuUnpauseEmulation.visibility = if (paused) View.VISIBLE else View.GONE
+ binding.menuPauseEmulation.visibility = if (paused) View.GONE else View.VISIBLE
+ }
+
+ override fun onClick(button: View) {
+ val action = buttonsActionsMap[button.id]
+ val activity = requireActivity() as EmulationActivity
+
+ if (action == EmulationActivity.MENU_ACTION_OVERLAY_CONTROLS) {
+ // We could use the button parameter as the anchor here, but this often results in a tiny menu
+ // (because the button often is in the middle of the screen), so let's use mTitleText instead
+ activity.showOverlayControlsMenu(binding.textGameTitle)
+ } else if (action >= 0) {
+ activity.handleMenuAction(action)
+ }
+
+ if (action == EmulationActivity.MENU_ACTION_PAUSE_EMULATION ||
+ action == EmulationActivity.MENU_ACTION_UNPAUSE_EMULATION
+ ) {
+ updatePauseUnpauseVisibility()
+ }
+ }
+
+ companion object {
+ private const val KEY_TITLE = "title"
+ private const val KEY_WII = "wii"
+ private val buttonsActionsMap = SparseIntArray()
+
+ init {
+ buttonsActionsMap.append(
+ R.id.menu_pause_emulation,
+ EmulationActivity.MENU_ACTION_PAUSE_EMULATION
+ )
+ buttonsActionsMap.append(
+ R.id.menu_unpause_emulation,
+ EmulationActivity.MENU_ACTION_UNPAUSE_EMULATION
+ )
+ buttonsActionsMap.append(
+ R.id.menu_take_screenshot,
+ EmulationActivity.MENU_ACTION_TAKE_SCREENSHOT
+ )
+ buttonsActionsMap.append(R.id.menu_quicksave, EmulationActivity.MENU_ACTION_QUICK_SAVE)
+ buttonsActionsMap.append(R.id.menu_quickload, EmulationActivity.MENU_ACTION_QUICK_LOAD)
+ buttonsActionsMap.append(
+ R.id.menu_emulation_save_root,
+ EmulationActivity.MENU_ACTION_SAVE_ROOT
+ )
+ buttonsActionsMap.append(
+ R.id.menu_emulation_load_root,
+ EmulationActivity.MENU_ACTION_LOAD_ROOT
+ )
+ buttonsActionsMap.append(
+ R.id.menu_overlay_controls,
+ EmulationActivity.MENU_ACTION_OVERLAY_CONTROLS
+ )
+ buttonsActionsMap.append(
+ R.id.menu_refresh_wiimotes,
+ EmulationActivity.MENU_ACTION_REFRESH_WIIMOTES
+ )
+ buttonsActionsMap.append(
+ R.id.menu_change_disc,
+ EmulationActivity.MENU_ACTION_CHANGE_DISC
+ )
+ buttonsActionsMap.append(R.id.menu_exit, EmulationActivity.MENU_ACTION_EXIT)
+ buttonsActionsMap.append(R.id.menu_settings, EmulationActivity.MENU_ACTION_SETTINGS)
+ buttonsActionsMap.append(R.id.menu_skylanders, EmulationActivity.MENU_ACTION_SKYLANDERS)
+ buttonsActionsMap.append(
+ R.id.menu_infinitybase,
+ EmulationActivity.MENU_ACTION_INFINITY_BASE
+ )
+ }
+
+ fun newInstance(): MenuFragment {
+ val fragment = MenuFragment()
+ val arguments = Bundle()
+ if (NativeLibrary.IsGameMetadataValid()) {
+ arguments.putString(KEY_TITLE, NativeLibrary.GetCurrentTitleDescription())
+ arguments.putBoolean(KEY_WII, NativeLibrary.IsEmulatingWii())
+ }
+ fragment.arguments = arguments
+ return fragment
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/SaveLoadStateFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/SaveLoadStateFragment.java
deleted file mode 100644
index 2afce4d1cf..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/SaveLoadStateFragment.java
+++ /dev/null
@@ -1,149 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.fragments;
-
-import android.os.Bundle;
-import android.text.format.DateUtils;
-import android.util.SparseIntArray;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.GridLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.Fragment;
-
-import org.dolphinemu.dolphinemu.NativeLibrary;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.activities.EmulationActivity;
-import org.dolphinemu.dolphinemu.databinding.FragmentSaveloadStateBinding;
-
-public final class SaveLoadStateFragment extends Fragment implements View.OnClickListener
-{
- public enum SaveOrLoad
- {
- SAVE, LOAD
- }
-
- private static final String KEY_SAVEORLOAD = "saveorload";
-
- private static int[] saveActionsMap = new int[]{
- EmulationActivity.MENU_ACTION_SAVE_SLOT1,
- EmulationActivity.MENU_ACTION_SAVE_SLOT2,
- EmulationActivity.MENU_ACTION_SAVE_SLOT3,
- EmulationActivity.MENU_ACTION_SAVE_SLOT4,
- EmulationActivity.MENU_ACTION_SAVE_SLOT5,
- EmulationActivity.MENU_ACTION_SAVE_SLOT6,
- };
-
- private static int[] loadActionsMap = new int[]{
- EmulationActivity.MENU_ACTION_LOAD_SLOT1,
- EmulationActivity.MENU_ACTION_LOAD_SLOT2,
- EmulationActivity.MENU_ACTION_LOAD_SLOT3,
- EmulationActivity.MENU_ACTION_LOAD_SLOT4,
- EmulationActivity.MENU_ACTION_LOAD_SLOT5,
- EmulationActivity.MENU_ACTION_LOAD_SLOT6,
- };
-
- private static SparseIntArray buttonsMap = new SparseIntArray();
-
- static
- {
- buttonsMap.append(R.id.loadsave_state_button_1, 0);
- buttonsMap.append(R.id.loadsave_state_button_2, 1);
- buttonsMap.append(R.id.loadsave_state_button_3, 2);
- buttonsMap.append(R.id.loadsave_state_button_4, 3);
- buttonsMap.append(R.id.loadsave_state_button_5, 4);
- buttonsMap.append(R.id.loadsave_state_button_6, 5);
- }
-
- private SaveOrLoad mSaveOrLoad;
-
- private FragmentSaveloadStateBinding mBinding;
-
- public static SaveLoadStateFragment newInstance(SaveOrLoad saveOrLoad)
- {
- SaveLoadStateFragment fragment = new SaveLoadStateFragment();
-
- Bundle arguments = new Bundle();
- arguments.putSerializable(KEY_SAVEORLOAD, saveOrLoad);
- fragment.setArguments(arguments);
-
- return fragment;
- }
-
- @Override
- public void onCreate(@Nullable Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
-
- mSaveOrLoad = (SaveOrLoad) getArguments().getSerializable(KEY_SAVEORLOAD);
- }
-
- @NonNull
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState)
- {
- mBinding = FragmentSaveloadStateBinding.inflate(inflater, container, false);
- return mBinding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState)
- {
- GridLayout grid = mBinding.gridStateSlots;
- for (int childIndex = 0; childIndex < grid.getChildCount(); childIndex++)
- {
- Button button = (Button) grid.getChildAt(childIndex);
- setButtonText(button, childIndex);
- button.setOnClickListener(this);
- }
-
- // So that item clicked to start this Fragment is no longer the focused item.
- grid.requestFocus();
- }
-
- @Override
- public void onDestroyView()
- {
- super.onDestroyView();
- mBinding = null;
- }
-
- @Override
- public void onClick(View view)
- {
- int buttonIndex = buttonsMap.get(view.getId(), -1);
-
- int action = (mSaveOrLoad == SaveOrLoad.SAVE ? saveActionsMap : loadActionsMap)[buttonIndex];
- ((EmulationActivity) getActivity()).handleMenuAction(action);
-
- if (mSaveOrLoad == SaveOrLoad.SAVE)
- {
- // Update the "last modified" time.
- // The savestate most likely hasn't gotten saved to disk yet (it happens asynchronously),
- // so we unfortunately can't rely on setButtonText/GetUnixTimeOfStateSlot here.
-
- Button button = (Button) view;
- CharSequence time = DateUtils.getRelativeTimeSpanString(0, 0, DateUtils.MINUTE_IN_MILLIS);
- button.setText(getString(R.string.emulation_state_slot, buttonIndex + 1, time));
- }
- }
-
- private void setButtonText(Button button, int index)
- {
- long creationTime = NativeLibrary.GetUnixTimeOfStateSlot(index);
- if (creationTime != 0)
- {
- CharSequence relativeTime = DateUtils.getRelativeTimeSpanString(creationTime);
- button.setText(getString(R.string.emulation_state_slot, index + 1, relativeTime));
- }
- else
- {
- button.setText(getString(R.string.emulation_state_slot_empty, index + 1));
- }
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/SaveLoadStateFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/SaveLoadStateFragment.kt
new file mode 100644
index 0000000000..8ce2ccde10
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/SaveLoadStateFragment.kt
@@ -0,0 +1,121 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.fragments
+
+import android.os.Bundle
+import android.text.format.DateUtils
+import android.util.SparseIntArray
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Button
+import androidx.fragment.app.Fragment
+import org.dolphinemu.dolphinemu.NativeLibrary
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.activities.EmulationActivity
+import org.dolphinemu.dolphinemu.databinding.FragmentSaveloadStateBinding
+import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
+
+class SaveLoadStateFragment : Fragment(), View.OnClickListener {
+ enum class SaveOrLoad { SAVE, LOAD }
+
+ private var saveOrLoad: SaveOrLoad? = null
+
+ private var _binding: FragmentSaveloadStateBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ saveOrLoad = requireArguments().serializable(KEY_SAVEORLOAD) as SaveOrLoad?
+ }
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
+ _binding = FragmentSaveloadStateBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ val grid = binding.gridStateSlots
+ for (childIndex in 0 until grid.childCount) {
+ val button = grid.getChildAt(childIndex) as Button
+ setButtonText(button, childIndex)
+ button.setOnClickListener(this)
+ }
+
+ // So that item clicked to start this Fragment is no longer the focused item.
+ grid.requestFocus()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ override fun onClick(view: View) {
+ val buttonIndex = buttonsMap[view.id, -1]
+
+ val action =
+ (if (saveOrLoad == SaveOrLoad.SAVE) saveActionsMap else loadActionsMap)[buttonIndex]
+ (requireActivity() as EmulationActivity?)?.handleMenuAction(action)
+
+ if (saveOrLoad == SaveOrLoad.SAVE) {
+ // Update the "last modified" time.
+ // The savestate most likely hasn't gotten saved to disk yet (it happens asynchronously),
+ // so we unfortunately can't rely on setButtonText/GetUnixTimeOfStateSlot here.
+ val button = view as Button
+ val time = DateUtils.getRelativeTimeSpanString(0, 0, DateUtils.MINUTE_IN_MILLIS)
+ button.text = getString(R.string.emulation_state_slot, buttonIndex + 1, time)
+ }
+ }
+
+ private fun setButtonText(button: Button, index: Int) {
+ val creationTime = NativeLibrary.GetUnixTimeOfStateSlot(index)
+ button.text = if (creationTime != 0L) {
+ val relativeTime = DateUtils.getRelativeTimeSpanString(creationTime)
+ getString(R.string.emulation_state_slot, index + 1, relativeTime)
+ } else {
+ getString(R.string.emulation_state_slot_empty, index + 1)
+ }
+ }
+
+ companion object {
+ private const val KEY_SAVEORLOAD = "saveorload"
+
+ private val saveActionsMap = intArrayOf(
+ EmulationActivity.MENU_ACTION_SAVE_SLOT1,
+ EmulationActivity.MENU_ACTION_SAVE_SLOT2,
+ EmulationActivity.MENU_ACTION_SAVE_SLOT3,
+ EmulationActivity.MENU_ACTION_SAVE_SLOT4,
+ EmulationActivity.MENU_ACTION_SAVE_SLOT5,
+ EmulationActivity.MENU_ACTION_SAVE_SLOT6
+ )
+
+ private val loadActionsMap = intArrayOf(
+ EmulationActivity.MENU_ACTION_LOAD_SLOT1,
+ EmulationActivity.MENU_ACTION_LOAD_SLOT2,
+ EmulationActivity.MENU_ACTION_LOAD_SLOT3,
+ EmulationActivity.MENU_ACTION_LOAD_SLOT4,
+ EmulationActivity.MENU_ACTION_LOAD_SLOT5,
+ EmulationActivity.MENU_ACTION_LOAD_SLOT6
+ )
+
+ private val buttonsMap = SparseIntArray()
+
+ init {
+ buttonsMap.append(R.id.loadsave_state_button_1, 0)
+ buttonsMap.append(R.id.loadsave_state_button_2, 1)
+ buttonsMap.append(R.id.loadsave_state_button_3, 2)
+ buttonsMap.append(R.id.loadsave_state_button_4, 3)
+ buttonsMap.append(R.id.loadsave_state_button_5, 4)
+ buttonsMap.append(R.id.loadsave_state_button_6, 5)
+ }
+
+ fun newInstance(saveOrLoad: SaveOrLoad): SaveLoadStateFragment {
+ val fragment = SaveLoadStateFragment()
+ val arguments = Bundle()
+ arguments.putSerializable(KEY_SAVEORLOAD, saveOrLoad)
+ fragment.arguments = arguments
+ return fragment
+ }
+ }
+}