Android: Convert SettingsActivity to Kotlin

This commit is contained in:
Charles Lombardo 2023-03-15 15:33:39 -04:00
parent 4cbbe15e77
commit 673c8d9cb2
2 changed files with 323 additions and 401 deletions

View File

@ -1,401 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.settings.ui;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.provider.Settings;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import org.dolphinemu.dolphinemu.NativeLibrary;
import org.dolphinemu.dolphinemu.R;
import org.dolphinemu.dolphinemu.databinding.ActivitySettingsBinding;
import org.dolphinemu.dolphinemu.ui.main.MainPresenter;
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper;
import org.dolphinemu.dolphinemu.utils.InsetsHelper;
import org.dolphinemu.dolphinemu.utils.ThemeHelper;
import java.util.Set;
public final class SettingsActivity extends AppCompatActivity implements SettingsActivityView
{
private static final String ARG_MENU_TAG = "menu_tag";
private static final String ARG_GAME_ID = "game_id";
private static final String ARG_REVISION = "revision";
private static final String ARG_IS_WII = "is_wii";
private static final String KEY_MAPPING_ALL_DEVICES = "all_devices";
private static final String FRAGMENT_TAG = "settings";
private static final String FRAGMENT_DIALOG_TAG = "settings_dialog";
private SettingsActivityPresenter mPresenter;
private AlertDialog dialog;
private CollapsingToolbarLayout mToolbarLayout;
private ActivitySettingsBinding mBinding;
private boolean mMappingAllDevices = false;
public static void launch(Context context, MenuTag menuTag, String gameId, int revision,
boolean isWii)
{
Intent settings = new Intent(context, SettingsActivity.class);
settings.putExtra(ARG_MENU_TAG, menuTag);
settings.putExtra(ARG_GAME_ID, gameId);
settings.putExtra(ARG_REVISION, revision);
settings.putExtra(ARG_IS_WII, isWii);
context.startActivity(settings);
}
public static void launch(Context context, MenuTag menuTag)
{
Intent settings = new Intent(context, SettingsActivity.class);
settings.putExtra(ARG_MENU_TAG, menuTag);
settings.putExtra(ARG_IS_WII, !NativeLibrary.IsRunning() || NativeLibrary.IsEmulatingWii());
context.startActivity(settings);
}
@Override
protected void onCreate(Bundle savedInstanceState)
{
ThemeHelper.setTheme(this);
super.onCreate(savedInstanceState);
// If we came here from the game list, we don't want to rescan when returning to the game list.
// But if we came here after UserDataActivity restarted the app, we do want to rescan.
if (savedInstanceState == null)
{
MainPresenter.skipRescanningLibrary();
}
else
{
mMappingAllDevices = savedInstanceState.getBoolean(KEY_MAPPING_ALL_DEVICES);
}
mBinding = ActivitySettingsBinding.inflate(getLayoutInflater());
setContentView(mBinding.getRoot());
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
Intent launcher = getIntent();
String gameID = launcher.getStringExtra(ARG_GAME_ID);
if (gameID == null)
gameID = "";
int revision = launcher.getIntExtra(ARG_REVISION, 0);
boolean isWii = launcher.getBooleanExtra(ARG_IS_WII, true);
MenuTag menuTag = (MenuTag) launcher.getSerializableExtra(ARG_MENU_TAG);
mPresenter = new SettingsActivityPresenter(this, getSettings());
mPresenter.onCreate(savedInstanceState, menuTag, gameID, revision, isWii, this);
mToolbarLayout = mBinding.toolbarSettingsLayout;
setSupportActionBar(mBinding.toolbarSettings);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
// TODO: Remove this when CollapsingToolbarLayouts are fixed by Google
// https://github.com/material-components/material-components-android/issues/1310
ViewCompat.setOnApplyWindowInsetsListener(mToolbarLayout, null);
setInsets();
ThemeHelper.enableScrollTint(this, mBinding.toolbarSettings, mBinding.appbarSettings);
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu_settings, menu);
return true;
}
@Override
protected void onSaveInstanceState(@NonNull Bundle outState)
{
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState);
mPresenter.saveState(outState);
outState.putBoolean(KEY_MAPPING_ALL_DEVICES, mMappingAllDevices);
}
@Override
protected void onStart()
{
super.onStart();
mPresenter.onStart();
}
/**
* If this is called, the user has left the settings screen (potentially through the
* home button) and will expect their changes to be persisted.
*/
@Override
protected void onStop()
{
super.onStop();
mPresenter.onStop(isFinishing());
}
@Override
protected void onDestroy()
{
super.onDestroy();
mPresenter.onDestroy();
}
@Override
public void showSettingsFragment(MenuTag menuTag, Bundle extras, boolean addToStack,
String gameID)
{
if (!addToStack && getFragment() != null)
return;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (addToStack)
{
if (areSystemAnimationsEnabled())
{
transaction.setCustomAnimations(
R.anim.anim_settings_fragment_in,
R.anim.anim_settings_fragment_out,
0,
R.anim.anim_pop_settings_fragment_out);
}
transaction.addToBackStack(null);
}
transaction.replace(R.id.frame_content_settings,
SettingsFragment.newInstance(menuTag, gameID, extras), FRAGMENT_TAG);
transaction.commit();
}
@Override
public void showDialogFragment(DialogFragment fragment)
{
fragment.show(getSupportFragmentManager(), FRAGMENT_DIALOG_TAG);
}
private boolean areSystemAnimationsEnabled()
{
float duration = Settings.Global.getFloat(
getContentResolver(),
Settings.Global.ANIMATOR_DURATION_SCALE, 1);
float transition = Settings.Global.getFloat(
getContentResolver(),
Settings.Global.TRANSITION_ANIMATION_SCALE, 1);
return duration != 0 && transition != 0;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent result)
{
super.onActivityResult(requestCode, resultCode, result);
// If the user picked a file, as opposed to just backing out.
if (resultCode == RESULT_OK)
{
if (requestCode != MainPresenter.REQUEST_DIRECTORY)
{
Uri uri = canonicalizeIfPossible(result.getData());
Set<String> validExtensions = requestCode == MainPresenter.REQUEST_GAME_FILE ?
FileBrowserHelper.GAME_EXTENSIONS : FileBrowserHelper.RAW_EXTENSION;
int flags = Intent.FLAG_GRANT_READ_URI_PERMISSION;
if (requestCode != MainPresenter.REQUEST_GAME_FILE)
flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
int takeFlags = flags & result.getFlags();
FileBrowserHelper.runAfterExtensionCheck(this, uri, validExtensions, () ->
{
getContentResolver().takePersistableUriPermission(uri, takeFlags);
getFragment().getAdapter().onFilePickerConfirmation(uri.toString());
});
}
else
{
String path = FileBrowserHelper.getSelectedPath(result);
getFragment().getAdapter().onFilePickerConfirmation(path);
}
}
}
@NonNull
private Uri canonicalizeIfPossible(@NonNull Uri uri)
{
Uri canonicalizedUri = getContentResolver().canonicalize(uri);
return canonicalizedUri != null ? canonicalizedUri : uri;
}
@Override
public void showLoading()
{
if (dialog == null)
{
dialog = new MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.load_settings))
.setView(R.layout.dialog_indeterminate_progress)
.create();
}
dialog.show();
}
@Override
public void hideLoading()
{
dialog.dismiss();
}
@Override
public void showGameIniJunkDeletionQuestion()
{
new MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.game_ini_junk_title))
.setMessage(getString(R.string.game_ini_junk_question))
.setPositiveButton(R.string.yes, (dialogInterface, i) -> mPresenter.clearGameSettings())
.setNegativeButton(R.string.no, null)
.show();
}
@Override
public org.dolphinemu.dolphinemu.features.settings.model.Settings getSettings()
{
return new ViewModelProvider(this).get(SettingsViewModel.class).getSettings();
}
@Override
public void onSettingsFileLoaded(
org.dolphinemu.dolphinemu.features.settings.model.Settings settings)
{
SettingsFragmentView fragment = getFragment();
if (fragment != null)
{
fragment.onSettingsFileLoaded(settings);
}
}
@Override
public void onSettingsFileNotFound()
{
SettingsFragmentView fragment = getFragment();
if (fragment != null)
{
fragment.loadDefaultSettings();
}
}
@Override
public void showToastMessage(String message)
{
Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
}
@Override
public void onSettingChanged()
{
mPresenter.onSettingChanged();
}
@Override
public void onControllerSettingsChanged()
{
getFragment().onControllerSettingsChanged();
}
@Override
public void onMenuTagAction(@NonNull MenuTag menuTag, int value)
{
mPresenter.onMenuTagAction(menuTag, value);
}
@Override
public boolean hasMenuTagActionForValue(@NonNull MenuTag menuTag, int value)
{
return mPresenter.hasMenuTagActionForValue(menuTag, value);
}
@Override
public boolean onSupportNavigateUp()
{
onBackPressed();
return true;
}
private SettingsFragment getFragment()
{
return (SettingsFragment) getSupportFragmentManager().findFragmentByTag(FRAGMENT_TAG);
}
public void setToolbarTitle(String title)
{
mBinding.toolbarSettingsLayout.setTitle(title);
}
@Override
public void setMappingAllDevices(boolean allDevices)
{
mMappingAllDevices = allDevices;
}
@Override
public boolean isMappingAllDevices()
{
return mMappingAllDevices;
}
@Override
public int setOldControllerSettingsWarningVisibility(boolean visible)
{
// We use INVISIBLE instead of GONE to avoid getting a stale height for the return value
mBinding.oldControllerSettingsWarning.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
return visible ? mBinding.oldControllerSettingsWarning.getHeight() : 0;
}
private void setInsets()
{
ViewCompat.setOnApplyWindowInsetsListener(mBinding.appbarSettings, (v, windowInsets) ->
{
Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars());
InsetsHelper.insetAppBar(insets, mBinding.appbarSettings);
mBinding.frameContentSettings.setPadding(insets.left, 0, insets.right, 0);
int textPadding = getResources().getDimensionPixelSize(R.dimen.spacing_large);
mBinding.oldControllerSettingsWarning.setPadding(textPadding + insets.left, textPadding,
textPadding + insets.right, textPadding + insets.bottom);
InsetsHelper.applyNavbarWorkaround(insets.bottom, mBinding.workaroundView);
ThemeHelper.setNavigationBarColor(this,
MaterialColors.getColor(mBinding.appbarSettings, R.attr.colorSurface));
return windowInsets;
});
}
}

View File

@ -0,0 +1,323 @@
// SPDX-License-Identifier: GPL-2.0-or-later
package org.dolphinemu.dolphinemu.features.settings.ui
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.Menu
import android.view.View
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.ViewCompat
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.ViewModelProvider
import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.color.MaterialColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.databinding.ActivitySettingsBinding
import org.dolphinemu.dolphinemu.features.settings.model.Settings
import org.dolphinemu.dolphinemu.features.settings.ui.SettingsFragment.Companion.newInstance
import org.dolphinemu.dolphinemu.ui.main.MainPresenter
import org.dolphinemu.dolphinemu.utils.FileBrowserHelper
import org.dolphinemu.dolphinemu.utils.InsetsHelper
import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable
import org.dolphinemu.dolphinemu.utils.ThemeHelper.enableScrollTint
import org.dolphinemu.dolphinemu.utils.ThemeHelper.setNavigationBarColor
import org.dolphinemu.dolphinemu.utils.ThemeHelper.setTheme
class SettingsActivity : AppCompatActivity(), SettingsActivityView {
private var presenter: SettingsActivityPresenter? = null
private var dialog: AlertDialog? = null
private var toolbarLayout: CollapsingToolbarLayout? = null
private var binding: ActivitySettingsBinding? = null
override var isMappingAllDevices = false
override val settings: Settings
get() = ViewModelProvider(this)[SettingsViewModel::class.java].settings
private val fragment: SettingsFragment?
get() = supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) as SettingsFragment?
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(this)
super.onCreate(savedInstanceState)
// If we came here from the game list, we don't want to rescan when returning to the game list.
// But if we came here after UserDataActivity restarted the app, we do want to rescan.
if (savedInstanceState == null) {
MainPresenter.skipRescanningLibrary()
} else {
isMappingAllDevices = savedInstanceState.getBoolean(KEY_MAPPING_ALL_DEVICES)
}
binding = ActivitySettingsBinding.inflate(layoutInflater)
setContentView(binding!!.root)
WindowCompat.setDecorFitsSystemWindows(window, false)
val launcher = intent
var gameID = launcher.getStringExtra(ARG_GAME_ID)
if (gameID == null) gameID = ""
val revision = launcher.getIntExtra(ARG_REVISION, 0)
val isWii = launcher.getBooleanExtra(ARG_IS_WII, true)
val menuTag = launcher.serializable<MenuTag>(ARG_MENU_TAG)
presenter = SettingsActivityPresenter(this, settings)
presenter!!.onCreate(savedInstanceState, menuTag, gameID, revision, isWii, this)
toolbarLayout = binding!!.toolbarSettingsLayout
setSupportActionBar(binding!!.toolbarSettings)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
// TODO: Remove this when CollapsingToolbarLayouts are fixed by Google
// https://github.com/material-components/material-components-android/issues/1310
ViewCompat.setOnApplyWindowInsetsListener(toolbarLayout!!, null)
setInsets()
enableScrollTint(this, binding!!.toolbarSettings, binding!!.appbarSettings)
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
val inflater = menuInflater
inflater.inflate(R.menu.menu_settings, menu)
return true
}
override fun onSaveInstanceState(outState: Bundle) {
// Critical: If super method is not called, rotations will be busted.
super.onSaveInstanceState(outState)
presenter!!.saveState(outState)
outState.putBoolean(KEY_MAPPING_ALL_DEVICES, isMappingAllDevices)
}
override fun onStart() {
super.onStart()
presenter!!.onStart()
}
/**
* If this is called, the user has left the settings screen (potentially through the
* home button) and will expect their changes to be persisted.
*/
override fun onStop() {
super.onStop()
presenter!!.onStop(isFinishing)
}
override fun onDestroy() {
super.onDestroy()
presenter!!.onDestroy()
}
override fun showSettingsFragment(
menuTag: MenuTag,
extras: Bundle?,
addToStack: Boolean,
gameId: String
) {
if (!addToStack && fragment != null) return
val transaction = supportFragmentManager.beginTransaction()
if (addToStack) {
if (areSystemAnimationsEnabled()) {
transaction.setCustomAnimations(
R.anim.anim_settings_fragment_in,
R.anim.anim_settings_fragment_out,
0,
R.anim.anim_pop_settings_fragment_out
)
}
transaction.addToBackStack(null)
}
transaction.replace(
R.id.frame_content_settings,
newInstance(menuTag, gameId, extras), FRAGMENT_TAG
)
transaction.commit()
}
override fun showDialogFragment(fragment: DialogFragment) {
fragment.show(supportFragmentManager, FRAGMENT_DIALOG_TAG)
}
private fun areSystemAnimationsEnabled(): Boolean {
val duration = android.provider.Settings.Global.getFloat(
contentResolver,
android.provider.Settings.Global.ANIMATOR_DURATION_SCALE,
1f
)
val transition = android.provider.Settings.Global.getFloat(
contentResolver,
android.provider.Settings.Global.TRANSITION_ANIMATION_SCALE,
1f
)
return duration != 0f && transition != 0f
}
override fun onActivityResult(requestCode: Int, resultCode: Int, result: Intent?) {
super.onActivityResult(requestCode, resultCode, result)
// If the user picked a file, as opposed to just backing out.
if (resultCode == RESULT_OK) {
if (requestCode != MainPresenter.REQUEST_DIRECTORY) {
val uri = canonicalizeIfPossible(result!!.data!!)
val validExtensions: Set<String> =
if (requestCode == MainPresenter.REQUEST_GAME_FILE) FileBrowserHelper.GAME_EXTENSIONS else FileBrowserHelper.RAW_EXTENSION
var flags = Intent.FLAG_GRANT_READ_URI_PERMISSION
if (requestCode != MainPresenter.REQUEST_GAME_FILE) flags =
flags or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
val takeFlags = flags and result.flags
FileBrowserHelper.runAfterExtensionCheck(this, uri, validExtensions) {
contentResolver.takePersistableUriPermission(uri, takeFlags)
fragment!!.adapter!!.onFilePickerConfirmation(uri.toString())
}
} else {
val path = FileBrowserHelper.getSelectedPath(result)
fragment!!.adapter!!.onFilePickerConfirmation(path!!)
}
}
}
private fun canonicalizeIfPossible(uri: Uri): Uri {
val canonicalizedUri = contentResolver.canonicalize(uri)
return canonicalizedUri ?: uri
}
override fun showLoading() {
if (dialog == null) {
dialog = MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.load_settings))
.setView(R.layout.dialog_indeterminate_progress)
.create()
}
dialog!!.show()
}
override fun hideLoading() {
dialog!!.dismiss()
}
override fun showGameIniJunkDeletionQuestion() {
MaterialAlertDialogBuilder(this)
.setTitle(getString(R.string.game_ini_junk_title))
.setMessage(getString(R.string.game_ini_junk_question))
.setPositiveButton(R.string.yes) { _: DialogInterface?, _: Int -> presenter!!.clearGameSettings() }
.setNegativeButton(R.string.no, null)
.show()
}
override fun onSettingsFileLoaded(settings: Settings) {
val fragment: SettingsFragmentView? = fragment
fragment?.onSettingsFileLoaded(settings)
}
override fun onSettingsFileNotFound() {
val fragment: SettingsFragmentView? = fragment
fragment?.loadDefaultSettings()
}
override fun showToastMessage(message: String) {
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}
override fun onSettingChanged() {
presenter!!.onSettingChanged()
}
override fun onControllerSettingsChanged() {
fragment!!.onControllerSettingsChanged()
}
override fun onMenuTagAction(menuTag: MenuTag, value: Int) {
presenter!!.onMenuTagAction(menuTag, value)
}
override fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean {
return presenter!!.hasMenuTagActionForValue(menuTag, value)
}
override fun onSupportNavigateUp(): Boolean {
onBackPressed()
return true
}
override fun setToolbarTitle(title: String) {
binding!!.toolbarSettingsLayout.title = title
}
override fun setOldControllerSettingsWarningVisibility(visible: Boolean): Int {
// We use INVISIBLE instead of GONE to avoid getting a stale height for the return value
binding!!.oldControllerSettingsWarning.visibility =
if (visible) View.VISIBLE else View.INVISIBLE
return if (visible) binding!!.oldControllerSettingsWarning.height else 0
}
private fun setInsets() {
ViewCompat.setOnApplyWindowInsetsListener(binding!!.appbarSettings) { _: View?, windowInsets: WindowInsetsCompat ->
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
InsetsHelper.insetAppBar(insets, binding!!.appbarSettings)
binding!!.frameContentSettings.setPadding(insets.left, 0, insets.right, 0)
val textPadding = resources.getDimensionPixelSize(R.dimen.spacing_large)
binding!!.oldControllerSettingsWarning.setPadding(
textPadding + insets.left,
textPadding,
textPadding + insets.right,
textPadding + insets.bottom
)
InsetsHelper.applyNavbarWorkaround(insets.bottom, binding!!.workaroundView)
setNavigationBarColor(
this,
MaterialColors.getColor(binding!!.appbarSettings, R.attr.colorSurface)
)
windowInsets
}
}
companion object {
private const val ARG_MENU_TAG = "menu_tag"
private const val ARG_GAME_ID = "game_id"
private const val ARG_REVISION = "revision"
private const val ARG_IS_WII = "is_wii"
private const val KEY_MAPPING_ALL_DEVICES = "all_devices"
private const val FRAGMENT_TAG = "settings"
private const val FRAGMENT_DIALOG_TAG = "settings_dialog"
@JvmStatic
fun launch(
context: Context,
menuTag: MenuTag?,
gameId: String?,
revision: Int,
isWii: Boolean
) {
val settings = Intent(context, SettingsActivity::class.java)
settings.putExtra(ARG_MENU_TAG, menuTag)
settings.putExtra(ARG_GAME_ID, gameId)
settings.putExtra(ARG_REVISION, revision)
settings.putExtra(ARG_IS_WII, isWii)
context.startActivity(settings)
}
@JvmStatic
fun launch(context: Context, menuTag: MenuTag?) {
val settings = Intent(context, SettingsActivity::class.java)
settings.putExtra(ARG_MENU_TAG, menuTag)
settings.putExtra(
ARG_IS_WII,
!NativeLibrary.IsRunning() || NativeLibrary.IsEmulatingWii()
)
context.startActivity(settings)
}
}
}