summaryrefslogtreecommitdiff
path: root/Source/Android
diff options
context:
space:
mode:
authorMai <mai.iam2048@gmail.com>2023-03-02 15:02:45 -0500
committerGitHub <noreply@github.com>2023-03-02 15:02:45 -0500
commit5d00bc088a160bfeb053d97e154d92748bfed830 (patch)
tree0eebf9910e95171c6b091b76a06493b60ebe3859 /Source/Android
parentc5e00b085e3a56f05e7a6d3a8ae3a9268cd7175e (diff)
parent2067c8eed1b60db536c5c93618167af9409bc315 (diff)
Merge pull request #11619 from t895/kotlin-convert
Android: Convert Convert Activity to Kotlin
Diffstat (limited to 'Source/Android')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java90
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.kt84
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java535
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt535
4 files changed, 619 insertions, 625 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java
deleted file mode 100644
index 9525b5cf39..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.activities;
-
-import android.content.Context;
-import android.content.Intent;
-import android.os.Bundle;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.graphics.Insets;
-import androidx.core.view.ViewCompat;
-import androidx.core.view.WindowCompat;
-import androidx.core.view.WindowInsetsCompat;
-
-import com.google.android.material.color.MaterialColors;
-
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.databinding.ActivityConvertBinding;
-import org.dolphinemu.dolphinemu.fragments.ConvertFragment;
-import org.dolphinemu.dolphinemu.utils.InsetsHelper;
-import org.dolphinemu.dolphinemu.utils.ThemeHelper;
-
-public class ConvertActivity extends AppCompatActivity
-{
- private static final String ARG_GAME_PATH = "game_path";
-
- private ActivityConvertBinding mBinding;
-
- public static void launch(Context context, String gamePath)
- {
- Intent launcher = new Intent(context, ConvertActivity.class);
- launcher.putExtra(ARG_GAME_PATH, gamePath);
- context.startActivity(launcher);
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState)
- {
- ThemeHelper.setTheme(this);
-
- super.onCreate(savedInstanceState);
-
- mBinding = ActivityConvertBinding.inflate(getLayoutInflater());
- setContentView(mBinding.getRoot());
-
- WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
-
- String path = getIntent().getStringExtra(ARG_GAME_PATH);
-
- ConvertFragment fragment = (ConvertFragment) getSupportFragmentManager()
- .findFragmentById(R.id.fragment_convert);
- if (fragment == null)
- {
- fragment = ConvertFragment.newInstance(path);
- getSupportFragmentManager().beginTransaction().add(R.id.fragment_convert, fragment).commit();
- }
-
- mBinding.toolbarConvertLayout.setTitle(getString(R.string.convert_convert));
- setSupportActionBar(mBinding.toolbarConvert);
- getSupportActionBar().setDisplayHomeAsUpEnabled(true);
-
- setInsets();
- ThemeHelper.enableScrollTint(this, mBinding.toolbarConvert, mBinding.appbarConvert);
- }
-
- @Override
- public boolean onSupportNavigateUp()
- {
- onBackPressed();
- return true;
- }
-
- private void setInsets()
- {
- ViewCompat.setOnApplyWindowInsetsListener(mBinding.appbarConvert, (v, windowInsets) ->
- {
- Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
-
- InsetsHelper.insetAppBar(insets, mBinding.appbarConvert);
-
- mBinding.scrollViewConvert.setPadding(insets.left, 0, insets.right, insets.bottom);
-
- InsetsHelper.applyNavbarWorkaround(insets.bottom, mBinding.workaroundView);
- ThemeHelper.setNavigationBarColor(this,
- MaterialColors.getColor(mBinding.appbarConvert, R.attr.colorSurface));
-
- return windowInsets;
- });
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.kt
new file mode 100644
index 0000000000..2fcc939983
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/activities/ConvertActivity.kt
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.activities
+
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.ViewCompat
+import androidx.core.view.WindowCompat
+import androidx.core.view.WindowInsetsCompat
+import com.google.android.material.color.MaterialColors
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.databinding.ActivityConvertBinding
+import org.dolphinemu.dolphinemu.fragments.ConvertFragment
+import org.dolphinemu.dolphinemu.fragments.ConvertFragment.Companion.newInstance
+import org.dolphinemu.dolphinemu.utils.InsetsHelper
+import org.dolphinemu.dolphinemu.utils.ThemeHelper
+
+class ConvertActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityConvertBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ ThemeHelper.setTheme(this)
+
+ super.onCreate(savedInstanceState)
+
+ binding = ActivityConvertBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ WindowCompat.setDecorFitsSystemWindows(window, false)
+
+ val path = intent.getStringExtra(ARG_GAME_PATH)
+
+ var fragment = supportFragmentManager
+ .findFragmentById(R.id.fragment_convert) as ConvertFragment?
+ if (fragment == null) {
+ fragment = newInstance(path!!)
+ supportFragmentManager.beginTransaction().add(R.id.fragment_convert, fragment).commit()
+ }
+
+ binding.toolbarConvertLayout.title = getString(R.string.convert_convert)
+ setSupportActionBar(binding.toolbarConvert)
+ supportActionBar!!.setDisplayHomeAsUpEnabled(true)
+
+ setInsets()
+ ThemeHelper.enableScrollTint(this, binding.toolbarConvert, binding.appbarConvert)
+ }
+
+ override fun onSupportNavigateUp(): Boolean {
+ onBackPressed()
+ return true
+ }
+
+ private fun setInsets() {
+ ViewCompat.setOnApplyWindowInsetsListener(binding.appbarConvert) { _: View?, windowInsets: WindowInsetsCompat ->
+ val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
+
+ InsetsHelper.insetAppBar(insets, binding.appbarConvert)
+
+ binding.scrollViewConvert.setPadding(insets.left, 0, insets.right, insets.bottom)
+
+ InsetsHelper.applyNavbarWorkaround(insets.bottom, binding.workaroundView)
+ ThemeHelper.setNavigationBarColor(
+ this,
+ MaterialColors.getColor(binding.appbarConvert, R.attr.colorSurface)
+ )
+
+ windowInsets
+ }
+ }
+
+ companion object {
+ private const val ARG_GAME_PATH = "game_path"
+
+ @JvmStatic
+ fun launch(context: Context, gamePath: String) {
+ val launcher = Intent(context, ConvertActivity::class.java)
+ launcher.putExtra(ARG_GAME_PATH, gamePath)
+ context.startActivity(launcher)
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java
deleted file mode 100644
index f18f276663..0000000000
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.java
+++ /dev/null
@@ -1,535 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-package org.dolphinemu.dolphinemu.fragments;
-
-import android.app.Activity;
-import android.content.Context;
-import android.content.Intent;
-import android.os.Build;
-import android.os.Bundle;
-import android.provider.DocumentsContract;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.StringRes;
-import androidx.appcompat.app.AlertDialog;
-import androidx.fragment.app.Fragment;
-
-import com.google.android.material.dialog.MaterialAlertDialogBuilder;
-import com.google.android.material.progressindicator.LinearProgressIndicator;
-import com.google.android.material.textfield.MaterialAutoCompleteTextView;
-import com.google.android.material.textfield.TextInputLayout;
-
-import org.dolphinemu.dolphinemu.NativeLibrary;
-import org.dolphinemu.dolphinemu.R;
-import org.dolphinemu.dolphinemu.databinding.DialogProgressBinding;
-import org.dolphinemu.dolphinemu.databinding.FragmentConvertBinding;
-import org.dolphinemu.dolphinemu.model.GameFile;
-import org.dolphinemu.dolphinemu.services.GameFileCacheManager;
-import org.dolphinemu.dolphinemu.ui.platform.Platform;
-
-import java.io.File;
-import java.util.ArrayList;
-
-public class ConvertFragment extends Fragment implements View.OnClickListener
-{
- private static class DropdownValue implements AdapterView.OnItemClickListener
- {
- private int mValuesId = -1;
- private int mCurrentPosition = 0;
- private ArrayList<Runnable> mCallbacks = new ArrayList<>();
-
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id)
- {
- if (mCurrentPosition != position)
- setPosition(position);
- }
-
- int getPosition()
- {
- return mCurrentPosition;
- }
-
- void setPosition(int position)
- {
- mCurrentPosition = position;
- for (Runnable callback : mCallbacks)
- callback.run();
- }
-
- void populate(int valuesId)
- {
- mValuesId = valuesId;
- }
-
- boolean hasValues()
- {
- return mValuesId != -1;
- }
-
- int getValue(Context context)
- {
- return context.getResources().getIntArray(mValuesId)[mCurrentPosition];
- }
-
- int getValueOr(Context context, int defaultValue)
- {
- return hasValues() ? getValue(context) : defaultValue;
- }
-
- void addCallback(Runnable callback)
- {
- mCallbacks.add(callback);
- }
- }
-
- private static final String ARG_GAME_PATH = "game_path";
-
- private static final String KEY_FORMAT = "convert_format";
- private static final String KEY_BLOCK_SIZE = "convert_block_size";
- private static final String KEY_COMPRESSION = "convert_compression";
- private static final String KEY_COMPRESSION_LEVEL = "convert_compression_level";
- private static final String KEY_REMOVE_JUNK_DATA = "remove_junk_data";
-
- private static final int REQUEST_CODE_SAVE_FILE = 0;
-
- private static final int BLOB_TYPE_ISO = 0;
- private static final int BLOB_TYPE_GCZ = 3;
- private static final int BLOB_TYPE_WIA = 7;
- private static final int BLOB_TYPE_RVZ = 8;
-
- private static final int COMPRESSION_NONE = 0;
- private static final int COMPRESSION_PURGE = 1;
- private static final int COMPRESSION_BZIP2 = 2;
- private static final int COMPRESSION_LZMA = 3;
- private static final int COMPRESSION_LZMA2 = 4;
- private static final int COMPRESSION_ZSTD = 5;
-
- private DropdownValue mFormat = new DropdownValue();
- private DropdownValue mBlockSize = new DropdownValue();
- private DropdownValue mCompression = new DropdownValue();
- private DropdownValue mCompressionLevel = new DropdownValue();
-
- private GameFile gameFile;
-
- private volatile boolean mCanceled;
- private volatile Thread mThread = null;
-
- private FragmentConvertBinding mBinding;
-
- public static ConvertFragment newInstance(String gamePath)
- {
- Bundle args = new Bundle();
- args.putString(ARG_GAME_PATH, gamePath);
-
- ConvertFragment fragment = new ConvertFragment();
- fragment.setArguments(args);
- return fragment;
- }
-
- @Override
- public void onCreate(Bundle savedInstanceState)
- {
- super.onCreate(savedInstanceState);
- gameFile = GameFileCacheManager.addOrGet(requireArguments().getString(ARG_GAME_PATH));
- }
-
- @Override
- public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState)
- {
- mBinding = FragmentConvertBinding.inflate(inflater, container, false);
- return mBinding.getRoot();
- }
-
- @Override
- public void onViewCreated(@NonNull View view, Bundle savedInstanceState)
- {
- // TODO: Remove workaround for text filtering issue in material components when fixed
- // https://github.com/material-components/material-components-android/issues/1464
- mBinding.dropdownFormat.setSaveEnabled(false);
- mBinding.dropdownBlockSize.setSaveEnabled(false);
- mBinding.dropdownCompression.setSaveEnabled(false);
- mBinding.dropdownCompressionLevel.setSaveEnabled(false);
-
- populateFormats();
- populateBlockSize();
- populateCompression();
- populateCompressionLevel();
- populateRemoveJunkData();
-
- mFormat.addCallback(this::populateBlockSize);
- mFormat.addCallback(this::populateCompression);
- mFormat.addCallback(this::populateCompressionLevel);
- mCompression.addCallback(this::populateCompressionLevel);
- mFormat.addCallback(this::populateRemoveJunkData);
-
- mBinding.buttonConvert.setOnClickListener(this);
-
- if (savedInstanceState != null)
- {
- setDropdownSelection(mBinding.dropdownFormat, mFormat, savedInstanceState.getInt(KEY_FORMAT));
- setDropdownSelection(mBinding.dropdownBlockSize, mBlockSize,
- savedInstanceState.getInt(KEY_BLOCK_SIZE));
- setDropdownSelection(mBinding.dropdownCompression, mCompression,
- savedInstanceState.getInt(KEY_COMPRESSION));
- setDropdownSelection(mBinding.dropdownCompressionLevel, mCompressionLevel,
- savedInstanceState.getInt(KEY_COMPRESSION_LEVEL));
-
- mBinding.switchRemoveJunkData.setChecked(
- savedInstanceState.getBoolean(KEY_REMOVE_JUNK_DATA));
- }
- }
-
- @Override
- public void onSaveInstanceState(@NonNull Bundle outState)
- {
- outState.putInt(KEY_FORMAT, mFormat.getPosition());
- outState.putInt(KEY_BLOCK_SIZE, mBlockSize.getPosition());
- outState.putInt(KEY_COMPRESSION, mCompression.getPosition());
- outState.putInt(KEY_COMPRESSION_LEVEL, mCompressionLevel.getPosition());
-
- outState.putBoolean(KEY_REMOVE_JUNK_DATA, mBinding.switchRemoveJunkData.isChecked());
- }
-
- private void setDropdownSelection(MaterialAutoCompleteTextView dropdown,
- DropdownValue valueWrapper, int selection)
- {
- if (dropdown.getAdapter() != null)
- {
- dropdown.setText(dropdown.getAdapter().getItem(selection).toString(), false);
- }
- valueWrapper.setPosition(selection);
- }
-
- @Override
- public void onStop()
- {
- super.onStop();
-
- mCanceled = true;
- joinThread();
- }
-
- @Override
- public void onDestroyView()
- {
- super.onDestroyView();
- mBinding = null;
- }
-
- private void populateDropdown(TextInputLayout layout, MaterialAutoCompleteTextView dropdown,
- int entriesId, int valuesId, DropdownValue valueWrapper)
- {
- ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(requireContext(),
- entriesId, R.layout.support_simple_spinner_dropdown_item);
- dropdown.setAdapter(adapter);
-
- valueWrapper.populate(valuesId);
- dropdown.setOnItemClickListener(valueWrapper);
-
- layout.setEnabled(adapter.getCount() > 1);
- }
-
- private void clearDropdown(TextInputLayout layout, MaterialAutoCompleteTextView dropdown,
- DropdownValue valueWrapper)
- {
- dropdown.setAdapter(null);
- layout.setEnabled(false);
-
- valueWrapper.populate(-1);
- valueWrapper.setPosition(0);
- dropdown.setText(null, false);
- dropdown.setOnItemClickListener(valueWrapper);
- }
-
- private void populateFormats()
- {
- populateDropdown(mBinding.format, mBinding.dropdownFormat, R.array.convertFormatEntries,
- R.array.convertFormatValues, mFormat);
- if (gameFile.getBlobType() == BLOB_TYPE_ISO)
- {
- setDropdownSelection(mBinding.dropdownFormat, mFormat,
- mBinding.dropdownFormat.getAdapter().getCount() - 1);
- }
- mBinding.dropdownFormat.setText(
- mBinding.dropdownFormat.getAdapter().getItem(mFormat.getPosition()).toString(),
- false);
- }
-
- private void populateBlockSize()
- {
- switch (mFormat.getValue(requireContext()))
- {
- case BLOB_TYPE_GCZ:
- // In the equivalent DolphinQt code, we have some logic for avoiding block sizes that can
- // trigger bugs in Dolphin versions older than 5.0-11893, but it was too annoying to port.
- // TODO: Port it?
- populateDropdown(mBinding.blockSize, mBinding.dropdownBlockSize,
- R.array.convertBlockSizeGczEntries,
- R.array.convertBlockSizeGczValues, mBlockSize);
- mBlockSize.setPosition(0);
- mBinding.dropdownBlockSize.setText(
- mBinding.dropdownBlockSize.getAdapter().getItem(0).toString(), false);
- break;
- case BLOB_TYPE_WIA:
- populateDropdown(mBinding.blockSize, mBinding.dropdownBlockSize,
- R.array.convertBlockSizeWiaEntries,
- R.array.convertBlockSizeWiaValues, mBlockSize);
- mBlockSize.setPosition(0);
- mBinding.dropdownBlockSize.setText(
- mBinding.dropdownBlockSize.getAdapter().getItem(0).toString(), false);
- break;
- case BLOB_TYPE_RVZ:
- populateDropdown(mBinding.blockSize, mBinding.dropdownBlockSize,
- R.array.convertBlockSizeRvzEntries,
- R.array.convertBlockSizeRvzValues, mBlockSize);
- mBlockSize.setPosition(2);
- mBinding.dropdownBlockSize.setText(
- mBinding.dropdownBlockSize.getAdapter().getItem(2).toString(), false);
- break;
- default:
- clearDropdown(mBinding.blockSize, mBinding.dropdownBlockSize, mBlockSize);
- }
- }
-
- private void populateCompression()
- {
- switch (mFormat.getValue(requireContext()))
- {
- case BLOB_TYPE_GCZ:
- populateDropdown(mBinding.compression, mBinding.dropdownCompression,
- R.array.convertCompressionGczEntries, R.array.convertCompressionGczValues,
- mCompression);
- mCompression.setPosition(0);
- mBinding.dropdownCompression.setText(
- mBinding.dropdownCompression.getAdapter().getItem(0).toString(), false);
- break;
- case BLOB_TYPE_WIA:
- populateDropdown(mBinding.compression, mBinding.dropdownCompression,
- R.array.convertCompressionWiaEntries, R.array.convertCompressionWiaValues,
- mCompression);
- mCompression.setPosition(0);
- mBinding.dropdownCompression.setText(
- mBinding.dropdownCompression.getAdapter().getItem(0).toString(), false);
- break;
- case BLOB_TYPE_RVZ:
- populateDropdown(mBinding.compression, mBinding.dropdownCompression,
- R.array.convertCompressionRvzEntries, R.array.convertCompressionRvzValues,
- mCompression);
- mCompression.setPosition(4);
- mBinding.dropdownCompression.setText(
- mBinding.dropdownCompression.getAdapter().getItem(4).toString(), false);
- break;
- default:
- clearDropdown(mBinding.compression, mBinding.dropdownCompression, mCompression);
- }
- }
-
- private void populateCompressionLevel()
- {
- switch (mCompression.getValueOr(requireContext(), COMPRESSION_NONE))
- {
- case COMPRESSION_BZIP2:
- case COMPRESSION_LZMA:
- case COMPRESSION_LZMA2:
- populateDropdown(mBinding.compressionLevel, mBinding.dropdownCompressionLevel,
- R.array.convertCompressionLevelEntries, R.array.convertCompressionLevelValues,
- mCompressionLevel);
- mCompressionLevel.setPosition(4);
- mBinding.dropdownCompressionLevel.setText(
- mBinding.dropdownCompressionLevel.getAdapter().getItem(4).toString(), false);
- break;
- case COMPRESSION_ZSTD:
- // TODO: Query DiscIO for the supported compression levels, like we do in DolphinQt?
- populateDropdown(mBinding.compressionLevel, mBinding.dropdownCompressionLevel,
- R.array.convertCompressionLevelZstdEntries,
- R.array.convertCompressionLevelZstdValues, mCompressionLevel);
- mCompressionLevel.setPosition(4);
- mBinding.dropdownCompressionLevel.setText(
- mBinding.dropdownCompressionLevel.getAdapter().getItem(4).toString(), false);
- break;
- default:
- clearDropdown(mBinding.compressionLevel, mBinding.dropdownCompressionLevel,
- mCompressionLevel);
- }
- }
-
- private void populateRemoveJunkData()
- {
- boolean scrubbingAllowed = mFormat.getValue(requireContext()) != BLOB_TYPE_RVZ &&
- !gameFile.isDatelDisc();
-
- mBinding.switchRemoveJunkData.setEnabled(scrubbingAllowed);
- if (!scrubbingAllowed)
- mBinding.switchRemoveJunkData.setChecked(false);
- }
-
- @Override
- public void onClick(View view)
- {
- boolean scrub = mBinding.switchRemoveJunkData.isChecked();
- int format = mFormat.getValue(requireContext());
-
- Runnable action = this::showSavePrompt;
-
- if (gameFile.isNKit())
- {
- action = addAreYouSureDialog(action, R.string.convert_warning_nkit);
- }
-
- if (!scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc() &&
- gameFile.getPlatform() == Platform.WII.toInt())
- {
- action = addAreYouSureDialog(action, R.string.convert_warning_gcz);
- }
-
- if (scrub && format == BLOB_TYPE_ISO)
- {
- action = addAreYouSureDialog(action, R.string.convert_warning_iso);
- }
-
- action.run();
- }
-
- private Runnable addAreYouSureDialog(Runnable action, @StringRes int warning_text)
- {
- return () ->
- {
- Context context = requireContext();
- new MaterialAlertDialogBuilder(context)
- .setMessage(warning_text)
- .setPositiveButton(R.string.yes, (dialog, i) -> action.run())
- .setNegativeButton(R.string.no, null)
- .show();
- };
- }
-
- private void showSavePrompt()
- {
- String originalPath = gameFile.getPath();
-
- StringBuilder filename = new StringBuilder(new File(originalPath).getName());
- int dotIndex = filename.lastIndexOf(".");
- if (dotIndex != -1)
- filename.setLength(dotIndex);
- switch (mFormat.getValue(requireContext()))
- {
- case BLOB_TYPE_ISO:
- filename.append(".iso");
- break;
- case BLOB_TYPE_GCZ:
- filename.append(".gcz");
- break;
- case BLOB_TYPE_WIA:
- filename.append(".wia");
- break;
- case BLOB_TYPE_RVZ:
- filename.append(".rvz");
- break;
- }
-
- Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("application/octet-stream");
- intent.putExtra(Intent.EXTRA_TITLE, filename.toString());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
- intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, originalPath);
- startActivityForResult(intent, REQUEST_CODE_SAVE_FILE);
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent data)
- {
- if (requestCode == REQUEST_CODE_SAVE_FILE && resultCode == Activity.RESULT_OK)
- {
- convert(data.getData().toString());
- }
- }
-
- private void convert(String outPath)
- {
- final int PROGRESS_RESOLUTION = 1000;
-
- Context context = requireContext();
-
- joinThread();
-
- mCanceled = false;
-
- DialogProgressBinding dialogProgressBinding =
- DialogProgressBinding.inflate(getLayoutInflater(), null, false);
- dialogProgressBinding.updateProgress.setMax(PROGRESS_RESOLUTION);
-
- AlertDialog progressDialog = new MaterialAlertDialogBuilder(context)
- .setTitle(R.string.convert_converting)
- .setOnCancelListener((dialog) -> mCanceled = true)
- .setNegativeButton(getString(R.string.cancel), (dialog, i) -> dialog.dismiss())
- .setView(dialogProgressBinding.getRoot())
- .show();
-
- mThread = new Thread(() ->
- {
- boolean success = NativeLibrary.ConvertDiscImage(gameFile.getPath(), outPath,
- gameFile.getPlatform(), mFormat.getValue(context), mBlockSize.getValueOr(context, 0),
- mCompression.getValueOr(context, 0), mCompressionLevel.getValueOr(context, 0),
- mBinding.switchRemoveJunkData.isChecked(), (text, completion) ->
- {
- requireActivity().runOnUiThread(() ->
- {
- progressDialog.setMessage(text);
- dialogProgressBinding.updateProgress.setProgress(
- (int) (completion * PROGRESS_RESOLUTION));
- });
- return !mCanceled;
- });
-
- if (!mCanceled)
- {
- requireActivity().runOnUiThread(() ->
- {
- progressDialog.dismiss();
-
- MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(context);
- if (success)
- {
- builder.setMessage(R.string.convert_success_message)
- .setCancelable(false)
- .setPositiveButton(R.string.ok, (dialog, i) ->
- {
- dialog.dismiss();
- requireActivity().finish();
- });
- }
- else
- {
- builder.setMessage(R.string.convert_failure_message)
- .setPositiveButton(R.string.ok, (dialog, i) -> dialog.dismiss());
- }
- builder.show();
- });
- }
- });
-
- mThread.start();
- }
-
- private void joinThread()
- {
- if (mThread != null)
- {
- try
- {
- mThread.join();
- }
- catch (InterruptedException ignored)
- {
- }
- }
- }
-}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt
new file mode 100644
index 0000000000..01a5d97743
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/ConvertFragment.kt
@@ -0,0 +1,535 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.fragments
+
+import android.app.Activity
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Build
+import android.os.Bundle
+import android.provider.DocumentsContract
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.AdapterView.OnItemClickListener
+import android.widget.ArrayAdapter
+import androidx.annotation.StringRes
+import androidx.fragment.app.Fragment
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import com.google.android.material.textfield.MaterialAutoCompleteTextView
+import com.google.android.material.textfield.TextInputLayout
+import org.dolphinemu.dolphinemu.NativeLibrary
+import org.dolphinemu.dolphinemu.R
+import org.dolphinemu.dolphinemu.databinding.DialogProgressBinding
+import org.dolphinemu.dolphinemu.databinding.FragmentConvertBinding
+import org.dolphinemu.dolphinemu.model.GameFile
+import org.dolphinemu.dolphinemu.services.GameFileCacheManager
+import org.dolphinemu.dolphinemu.ui.platform.Platform
+import java.io.File
+
+class ConvertFragment : Fragment(), View.OnClickListener {
+ private class DropdownValue : OnItemClickListener {
+ private var valuesId = -1
+ var position = 0
+ set(position) {
+ field = position
+ for (callback in callbacks) callback.run()
+ }
+ private val callbacks = ArrayList<Runnable>()
+
+ override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
+ if (this.position != position) this.position = position
+ }
+
+ fun populate(valuesId: Int) {
+ this.valuesId = valuesId
+ }
+
+ fun hasValues(): Boolean {
+ return valuesId != -1
+ }
+
+ fun getValue(context: Context): Int {
+ return context.resources.getIntArray(valuesId)[position]
+ }
+
+ fun getValueOr(context: Context, defaultValue: Int): Int {
+ return if (hasValues()) getValue(context) else defaultValue
+ }
+
+ fun addCallback(callback: Runnable) {
+ callbacks.add(callback)
+ }
+ }
+
+ private val format = DropdownValue()
+ private val blockSize = DropdownValue()
+ private val compression = DropdownValue()
+ private val compressionLevel = DropdownValue()
+ private lateinit var gameFile: GameFile
+
+ @Volatile
+ private var canceled = false
+
+ @Volatile
+ private var thread: Thread? = null
+
+ private var _binding: FragmentConvertBinding? = null
+ val binding get() = _binding!!
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ gameFile = GameFileCacheManager.addOrGet(requireArguments().getString(ARG_GAME_PATH))
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentConvertBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ // TODO: Remove workaround for text filtering issue in material components when fixed
+ // https://github.com/material-components/material-components-android/issues/1464
+ binding.dropdownFormat.isSaveEnabled = false
+ binding.dropdownBlockSize.isSaveEnabled = false
+ binding.dropdownCompression.isSaveEnabled = false
+ binding.dropdownCompressionLevel.isSaveEnabled = false
+
+ populateFormats()
+ populateBlockSize()
+ populateCompression()
+ populateCompressionLevel()
+ populateRemoveJunkData()
+
+ format.addCallback { populateBlockSize() }
+ format.addCallback { populateCompression() }
+ format.addCallback { populateCompressionLevel() }
+ compression.addCallback { populateCompressionLevel() }
+ format.addCallback { populateRemoveJunkData() }
+
+ binding.buttonConvert.setOnClickListener(this)
+
+ if (savedInstanceState != null) {
+ setDropdownSelection(
+ binding.dropdownFormat, format, savedInstanceState.getInt(
+ KEY_FORMAT
+ )
+ )
+ setDropdownSelection(
+ binding.dropdownBlockSize, blockSize,
+ savedInstanceState.getInt(KEY_BLOCK_SIZE)
+ )
+ setDropdownSelection(
+ binding.dropdownCompression, compression,
+ savedInstanceState.getInt(KEY_COMPRESSION)
+ )
+ setDropdownSelection(
+ binding.dropdownCompressionLevel, compressionLevel,
+ savedInstanceState.getInt(KEY_COMPRESSION_LEVEL)
+ )
+ binding.switchRemoveJunkData.isChecked = savedInstanceState.getBoolean(
+ KEY_REMOVE_JUNK_DATA
+ )
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ outState.putInt(KEY_FORMAT, format.position)
+ outState.putInt(KEY_BLOCK_SIZE, blockSize.position)
+ outState.putInt(KEY_COMPRESSION, compression.position)
+ outState.putInt(KEY_COMPRESSION_LEVEL, compressionLevel.position)
+ outState.putBoolean(KEY_REMOVE_JUNK_DATA, binding.switchRemoveJunkData.isChecked)
+ }
+
+ private fun setDropdownSelection(
+ dropdown: MaterialAutoCompleteTextView,
+ valueWrapper: DropdownValue,
+ selection: Int
+ ) {
+ if (dropdown.adapter != null) {
+ dropdown.setText(dropdown.adapter.getItem(selection).toString(), false)
+ }
+ valueWrapper.position = selection
+ }
+
+ override fun onStop() {
+ super.onStop()
+ canceled = true
+ joinThread()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ private fun populateDropdown(
+ layout: TextInputLayout,
+ dropdown: MaterialAutoCompleteTextView,
+ entriesId: Int,
+ valuesId: Int,
+ valueWrapper: DropdownValue
+ ) {
+ val adapter = ArrayAdapter.createFromResource(
+ requireContext(),
+ entriesId,
+ R.layout.support_simple_spinner_dropdown_item
+ )
+ dropdown.setAdapter(adapter)
+
+ valueWrapper.populate(valuesId)
+ dropdown.onItemClickListener = valueWrapper
+
+ layout.isEnabled = adapter.count > 1
+ }
+
+ private fun clearDropdown(
+ layout: TextInputLayout,
+ dropdown: MaterialAutoCompleteTextView,
+ valueWrapper: DropdownValue
+ ) {
+ dropdown.setAdapter(null)
+ layout.isEnabled = false
+
+ valueWrapper.populate(-1)
+ valueWrapper.position = 0
+ dropdown.setText(null, false)
+ dropdown.onItemClickListener = valueWrapper
+ }
+
+ private fun populateFormats() {
+ populateDropdown(
+ binding.format,
+ binding.dropdownFormat,
+ R.array.convertFormatEntries,
+ R.array.convertFormatValues,
+ format
+ )
+ if (gameFile.blobType == BLOB_TYPE_ISO) {
+ setDropdownSelection(
+ binding.dropdownFormat,
+ format,
+ binding.dropdownFormat.adapter.count - 1
+ )
+ }
+ binding.dropdownFormat.setText(
+ binding.dropdownFormat.adapter.getItem(format.position).toString(), false
+ )
+ }
+
+ private fun populateBlockSize() {
+ when (format.getValue(requireContext())) {
+ BLOB_TYPE_GCZ -> {
+ // In the equivalent DolphinQt code, we have some logic for avoiding block sizes that can
+ // trigger bugs in Dolphin versions older than 5.0-11893, but it was too annoying to port.
+ // TODO: Port it?
+ populateDropdown(
+ binding.blockSize,
+ binding.dropdownBlockSize,
+ R.array.convertBlockSizeGczEntries,
+ R.array.convertBlockSizeGczValues,
+ blockSize
+ )
+ blockSize.position = 0
+ binding.dropdownBlockSize.setText(
+ binding.dropdownBlockSize.adapter.getItem(0).toString(), false
+ )
+ }
+ BLOB_TYPE_WIA -> {
+ populateDropdown(
+ binding.blockSize,
+ binding.dropdownBlockSize,
+ R.array.convertBlockSizeWiaEntries,
+ R.array.convertBlockSizeWiaValues,
+ blockSize
+ )
+ blockSize.position = 0
+ binding.dropdownBlockSize.setText(
+ binding.dropdownBlockSize.adapter.getItem(0).toString(), false
+ )
+ }
+ BLOB_TYPE_RVZ -> {
+ populateDropdown(
+ binding.blockSize,
+ binding.dropdownBlockSize,
+ R.array.convertBlockSizeRvzEntries,
+ R.array.convertBlockSizeRvzValues,
+ blockSize
+ )
+ blockSize.position = 2
+ binding.dropdownBlockSize.setText(
+ binding.dropdownBlockSize.adapter.getItem(2).toString(), false
+ )
+ }
+ else -> clearDropdown(binding.blockSize, binding.dropdownBlockSize, blockSize)
+ }
+ }
+
+ private fun populateCompression() {
+ when (format.getValue(requireContext())) {
+ BLOB_TYPE_GCZ -> {
+ populateDropdown(
+ binding.compression,
+ binding.dropdownCompression,
+ R.array.convertCompressionGczEntries,
+ R.array.convertCompressionGczValues,
+ compression
+ )
+ compression.position = 0
+ binding.dropdownCompression.setText(
+ binding.dropdownCompression.adapter.getItem(0).toString(), false
+ )
+ }
+ BLOB_TYPE_WIA -> {
+ populateDropdown(
+ binding.compression,
+ binding.dropdownCompression,
+ R.array.convertCompressionWiaEntries,
+ R.array.convertCompressionWiaValues,
+ compression
+ )
+ compression.position = 0
+ binding.dropdownCompression.setText(
+ binding.dropdownCompression.adapter.getItem(0).toString(), false
+ )
+ }
+ BLOB_TYPE_RVZ -> {
+ populateDropdown(
+ binding.compression,
+ binding.dropdownCompression,
+ R.array.convertCompressionRvzEntries,
+ R.array.convertCompressionRvzValues,
+ compression
+ )
+ compression.position = 4
+ binding.dropdownCompression.setText(
+ binding.dropdownCompression.adapter.getItem(4).toString(), false
+ )
+ }
+ else -> clearDropdown(
+ binding.compression,
+ binding.dropdownCompression,
+ compression
+ )
+ }
+ }
+
+ private fun populateCompressionLevel() {
+ when (compression.getValueOr(requireContext(), COMPRESSION_NONE)) {
+ COMPRESSION_BZIP2, COMPRESSION_LZMA, COMPRESSION_LZMA2 -> {
+ populateDropdown(
+ binding.compressionLevel,
+ binding.dropdownCompressionLevel,
+ R.array.convertCompressionLevelEntries,
+ R.array.convertCompressionLevelValues,
+ compressionLevel
+ )
+ compressionLevel.position = 4
+ binding.dropdownCompressionLevel.setText(
+ binding.dropdownCompressionLevel.adapter.getItem(
+ 4
+ ).toString(), false
+ )
+ }
+ COMPRESSION_ZSTD -> {
+ // TODO: Query DiscIO for the supported compression levels, like we do in DolphinQt?
+ populateDropdown(
+ binding.compressionLevel,
+ binding.dropdownCompressionLevel,
+ R.array.convertCompressionLevelZstdEntries,
+ R.array.convertCompressionLevelZstdValues,
+ compressionLevel
+ )
+ compressionLevel.position = 4
+ binding.dropdownCompressionLevel.setText(
+ binding.dropdownCompressionLevel.adapter.getItem(
+ 4
+ ).toString(), false
+ )
+ }
+ else -> clearDropdown(
+ binding.compressionLevel,
+ binding.dropdownCompressionLevel,
+ compressionLevel
+ )
+ }
+ }
+
+ private fun populateRemoveJunkData() {
+ val scrubbingAllowed = format.getValue(requireContext()) != BLOB_TYPE_RVZ &&
+ !gameFile.isDatelDisc
+
+ binding.switchRemoveJunkData.isEnabled = scrubbingAllowed
+ if (!scrubbingAllowed) binding.switchRemoveJunkData.isChecked = false
+ }
+
+ override fun onClick(view: View) {
+ val scrub = binding.switchRemoveJunkData.isChecked
+ val format = format.getValue(requireContext())
+
+ var action = Runnable { showSavePrompt() }
+
+ if (gameFile.isNKit) {
+ action = addAreYouSureDialog(action, R.string.convert_warning_nkit)
+ }
+
+ if (!scrub && format == BLOB_TYPE_GCZ && !gameFile.isDatelDisc && gameFile.platform == Platform.WII.toInt()) {
+ action = addAreYouSureDialog(action, R.string.convert_warning_gcz)
+ }
+
+ if (scrub && format == BLOB_TYPE_ISO) {
+ action = addAreYouSureDialog(action, R.string.convert_warning_iso)
+ }
+
+ action.run()
+ }
+
+ private fun addAreYouSureDialog(action: Runnable, @StringRes warning_text: Int): Runnable {
+ return Runnable {
+ val context = requireContext()
+ MaterialAlertDialogBuilder(context)
+ .setMessage(warning_text)
+ .setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> action.run() }
+ .setNegativeButton(R.string.no, null)
+ .show()
+ }
+ }
+
+ private fun showSavePrompt() {
+ val originalPath = gameFile.path
+
+ val filename = StringBuilder(File(originalPath).name)
+ val dotIndex = filename.lastIndexOf(".")
+ if (dotIndex != -1) filename.setLength(dotIndex)
+ when (format.getValue(requireContext())) {
+ BLOB_TYPE_ISO -> filename.append(".iso")
+ BLOB_TYPE_GCZ -> filename.append(".gcz")
+ BLOB_TYPE_WIA -> filename.append(".wia")
+ BLOB_TYPE_RVZ -> filename.append(".rvz")
+ }
+
+ val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
+ intent.addCategory(Intent.CATEGORY_OPENABLE)
+ intent.type = "application/octet-stream"
+ intent.putExtra(Intent.EXTRA_TITLE, filename.toString())
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) intent.putExtra(
+ DocumentsContract.EXTRA_INITIAL_URI,
+ originalPath
+ )
+ startActivityForResult(intent, REQUEST_CODE_SAVE_FILE)
+ }
+
+ override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == REQUEST_CODE_SAVE_FILE && resultCode == Activity.RESULT_OK) {
+ convert(data!!.data.toString())
+ }
+ }
+
+ private fun convert(outPath: String) {
+ val context = requireContext()
+
+ joinThread()
+
+ canceled = false
+
+ val dialogProgressBinding = DialogProgressBinding.inflate(layoutInflater, null, false)
+ dialogProgressBinding.updateProgress.max = PROGRESS_RESOLUTION
+
+ val progressDialog = MaterialAlertDialogBuilder(context)
+ .setTitle(R.string.convert_converting)
+ .setOnCancelListener { canceled = true }
+ .setNegativeButton(getString(R.string.cancel)) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
+ .setView(dialogProgressBinding.root)
+ .show()
+
+ thread = Thread {
+ val success = NativeLibrary.ConvertDiscImage(
+ gameFile.path,
+ outPath,
+ gameFile.platform,
+ format.getValue(context),
+ blockSize.getValueOr(context, 0),
+ compression.getValueOr(context, 0),
+ compressionLevel.getValueOr(context, 0),
+ binding.switchRemoveJunkData.isChecked
+ ) { text: String?, completion: Float ->
+ requireActivity().runOnUiThread {
+ progressDialog.setMessage(text)
+ dialogProgressBinding.updateProgress.progress =
+ (completion * PROGRESS_RESOLUTION).toInt()
+ }
+ !canceled
+ }
+
+ if (!canceled) {
+ requireActivity().runOnUiThread {
+ progressDialog.dismiss()
+
+ val builder = MaterialAlertDialogBuilder(context)
+ if (success) {
+ builder.setMessage(R.string.convert_success_message)
+ .setCancelable(false)
+ .setPositiveButton(R.string.ok) { dialog: DialogInterface, _: Int ->
+ dialog.dismiss()
+ requireActivity().finish()
+ }
+ } else {
+ builder.setMessage(R.string.convert_failure_message)
+ .setPositiveButton(R.string.ok) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
+ }
+ builder.show()
+ }
+ }
+ }
+ thread!!.start()
+ }
+
+ private fun joinThread() {
+ if (thread != null) {
+ try {
+ thread!!.join()
+ } catch (ignored: InterruptedException) {
+ }
+ }
+ }
+
+ companion object {
+ private const val ARG_GAME_PATH = "game_path"
+
+ private const val KEY_FORMAT = "convert_format"
+ private const val KEY_BLOCK_SIZE = "convert_block_size"
+ private const val KEY_COMPRESSION = "convert_compression"
+ private const val KEY_COMPRESSION_LEVEL = "convert_compression_level"
+ private const val KEY_REMOVE_JUNK_DATA = "remove_junk_data"
+
+ private const val REQUEST_CODE_SAVE_FILE = 0
+
+ private const val BLOB_TYPE_ISO = 0
+ private const val BLOB_TYPE_GCZ = 3
+ private const val BLOB_TYPE_WIA = 7
+ private const val BLOB_TYPE_RVZ = 8
+
+ private const val COMPRESSION_NONE = 0
+ private const val COMPRESSION_PURGE = 1
+ private const val COMPRESSION_BZIP2 = 2
+ private const val COMPRESSION_LZMA = 3
+ private const val COMPRESSION_LZMA2 = 4
+ private const val COMPRESSION_ZSTD = 5
+
+ private const val PROGRESS_RESOLUTION = 1000
+
+ @JvmStatic
+ fun newInstance(gamePath: String): ConvertFragment {
+ val args = Bundle()
+ args.putString(ARG_GAME_PATH, gamePath)
+ val fragment = ConvertFragment()
+ fragment.arguments = args
+ return fragment
+ }
+ }
+}