Merge pull request #11619 from t895/kotlin-convert
Android: Convert Convert Activity to Kotlin
This commit is contained in:
commit
5d00bc088a
|
@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue