| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,132 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui | ||
|
|
||
| import org.dolphinemu.dolphinemu.features.input.model.controlleremu.EmulatedController | ||
|
|
||
| enum class MenuTag { | ||
| SETTINGS("settings"), | ||
| CONFIG("config"), | ||
| CONFIG_GENERAL("config_general"), | ||
| CONFIG_INTERFACE("config_interface"), | ||
| CONFIG_AUDIO("config_audio"), | ||
| CONFIG_PATHS("config_paths"), | ||
| CONFIG_GAME_CUBE("config_gamecube"), | ||
| CONFIG_SERIALPORT1("config_serialport1"), | ||
| CONFIG_WII("config_wii"), | ||
| CONFIG_ADVANCED("config_advanced"), | ||
| CONFIG_LOG("config_log"), | ||
| DEBUG("debug"), | ||
| GRAPHICS("graphics"), | ||
| ENHANCEMENTS("enhancements"), | ||
| STEREOSCOPY("stereoscopy"), | ||
| HACKS("hacks"), | ||
| STATISTICS("statistics"), | ||
| ADVANCED_GRAPHICS("advanced_graphics"), | ||
| GCPAD_TYPE("gc_pad_type"), | ||
| WIIMOTE("wiimote"), | ||
| WIIMOTE_EXTENSION("wiimote_extension"), | ||
| GCPAD_1("gcpad", 0), | ||
| GCPAD_2("gcpad", 1), | ||
| GCPAD_3("gcpad", 2), | ||
| GCPAD_4("gcpad", 3), | ||
| WIIMOTE_1("wiimote", 0), | ||
| WIIMOTE_2("wiimote", 1), | ||
| WIIMOTE_3("wiimote", 2), | ||
| WIIMOTE_4("wiimote", 3), | ||
| WIIMOTE_EXTENSION_1("wiimote_extension", 0), | ||
| WIIMOTE_EXTENSION_2("wiimote_extension", 1), | ||
| WIIMOTE_EXTENSION_3("wiimote_extension", 2), | ||
| WIIMOTE_EXTENSION_4("wiimote_extension", 3), | ||
| WIIMOTE_GENERAL_1("wiimote_general", 0), | ||
| WIIMOTE_GENERAL_2("wiimote_general", 1), | ||
| WIIMOTE_GENERAL_3("wiimote_general", 2), | ||
| WIIMOTE_GENERAL_4("wiimote_general", 3), | ||
| WIIMOTE_MOTION_SIMULATION_1("wiimote_motion_simulation", 0), | ||
| WIIMOTE_MOTION_SIMULATION_2("wiimote_motion_simulation", 1), | ||
| WIIMOTE_MOTION_SIMULATION_3("wiimote_motion_simulation", 2), | ||
| WIIMOTE_MOTION_SIMULATION_4("wiimote_motion_simulation", 3), | ||
| WIIMOTE_MOTION_INPUT_1("wiimote_motion_input", 0), | ||
| WIIMOTE_MOTION_INPUT_2("wiimote_motion_input", 1), | ||
| WIIMOTE_MOTION_INPUT_3("wiimote_motion_input", 2), | ||
| WIIMOTE_MOTION_INPUT_4("wiimote_motion_input", 3); | ||
|
|
||
| var tag: String | ||
| private set | ||
| var subType = -1 | ||
| private set | ||
|
|
||
| constructor(tag: String) { | ||
| this.tag = tag | ||
| } | ||
|
|
||
| constructor(tag: String, subtype: Int) { | ||
| this.tag = tag | ||
| subType = subtype | ||
| } | ||
|
|
||
| override fun toString(): String { | ||
| return if (subType != -1) { | ||
| tag + subType | ||
| } else tag | ||
| } | ||
|
|
||
| val correspondingEmulatedController: EmulatedController | ||
| get() = if (isGCPadMenu) EmulatedController.getGcPad(subType) else if (isWiimoteMenu) EmulatedController.getWiimote( | ||
| subType | ||
| ) else throw UnsupportedOperationException() | ||
|
|
||
| val isSerialPort1Menu: Boolean | ||
| get() = this == CONFIG_SERIALPORT1 | ||
|
|
||
| val isGCPadMenu: Boolean | ||
| get() = this == GCPAD_1 || this == GCPAD_2 || this == GCPAD_3 || this == GCPAD_4 | ||
|
|
||
| val isWiimoteMenu: Boolean | ||
| get() = this == WIIMOTE_1 || this == WIIMOTE_2 || this == WIIMOTE_3 || this == WIIMOTE_4 | ||
|
|
||
| val isWiimoteExtensionMenu: Boolean | ||
| get() = this == WIIMOTE_EXTENSION_1 || this == WIIMOTE_EXTENSION_2 || this == WIIMOTE_EXTENSION_3 || this == WIIMOTE_EXTENSION_4 | ||
|
|
||
| companion object { | ||
| @JvmStatic | ||
| fun getGCPadMenuTag(subtype: Int): MenuTag { | ||
| return getMenuTag("gcpad", subtype) | ||
| } | ||
|
|
||
| @JvmStatic | ||
| fun getWiimoteMenuTag(subtype: Int): MenuTag { | ||
| return getMenuTag("wiimote", subtype) | ||
| } | ||
|
|
||
| @JvmStatic | ||
| fun getWiimoteExtensionMenuTag(subtype: Int): MenuTag { | ||
| return getMenuTag("wiimote_extension", subtype) | ||
| } | ||
|
|
||
| @JvmStatic | ||
| fun getWiimoteGeneralMenuTag(subtype: Int): MenuTag { | ||
| return getMenuTag("wiimote_general", subtype) | ||
| } | ||
|
|
||
| @JvmStatic | ||
| fun getWiimoteMotionSimulationMenuTag(subtype: Int): MenuTag { | ||
| return getMenuTag("wiimote_motion_simulation", subtype) | ||
| } | ||
|
|
||
| @JvmStatic | ||
| fun getWiimoteMotionInputMenuTag(subtype: Int): MenuTag { | ||
| return getMenuTag("wiimote_motion_input", subtype) | ||
| } | ||
|
|
||
| private fun getMenuTag(tag: String, subtype: Int): MenuTag { | ||
| for (menuTag in values()) { | ||
| if (menuTag.tag == tag && menuTag.subType == subtype) return menuTag | ||
| } | ||
| throw IllegalArgumentException( | ||
| "You are asking for a menu that is not available or " + | ||
| "passing a wrong subtype" | ||
| ) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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) | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,147 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui | ||
|
|
||
| import android.os.Bundle | ||
| import android.text.TextUtils | ||
| import androidx.appcompat.app.AppCompatActivity | ||
| import org.dolphinemu.dolphinemu.features.settings.model.Settings | ||
| import org.dolphinemu.dolphinemu.utils.AfterDirectoryInitializationRunner | ||
| import org.dolphinemu.dolphinemu.utils.Log | ||
|
|
||
| class SettingsActivityPresenter( | ||
| private val activityView: SettingsActivityView, | ||
| var settings: Settings? | ||
| ) { | ||
| private var shouldSave = false | ||
| private var menuTag: MenuTag? = null | ||
| private var gameId: String? = null | ||
| private var revision = 0 | ||
| private var isWii = false | ||
| private lateinit var activity: AppCompatActivity | ||
|
|
||
| fun onCreate( | ||
| savedInstanceState: Bundle?, | ||
| menuTag: MenuTag?, | ||
| gameId: String?, | ||
| revision: Int, | ||
| isWii: Boolean, | ||
| activity: AppCompatActivity | ||
| ) { | ||
| this.menuTag = menuTag | ||
| this.gameId = gameId | ||
| this.revision = revision | ||
| this.isWii = isWii | ||
| this.activity = activity | ||
| shouldSave = savedInstanceState != null && savedInstanceState.getBoolean(KEY_SHOULD_SAVE) | ||
| } | ||
|
|
||
| fun onDestroy() { | ||
| if (settings != null) { | ||
| settings!!.close() | ||
| settings = null | ||
| } | ||
| } | ||
|
|
||
| fun onStart() { | ||
| prepareDolphinDirectoriesIfNeeded() | ||
| } | ||
|
|
||
| private fun loadSettingsUI() { | ||
| activityView.hideLoading() | ||
| if (!settings!!.areSettingsLoaded()) { | ||
| if (!TextUtils.isEmpty(gameId)) { | ||
| settings!!.loadSettings(gameId!!, revision, isWii) | ||
| if (settings!!.gameIniContainsJunk()) { | ||
| activityView.showGameIniJunkDeletionQuestion() | ||
| } | ||
| } else { | ||
| settings!!.loadSettings(isWii) | ||
| } | ||
| } | ||
| activityView.showSettingsFragment(menuTag!!, null, false, gameId!!) | ||
| activityView.onSettingsFileLoaded(settings!!) | ||
| } | ||
|
|
||
| private fun prepareDolphinDirectoriesIfNeeded() { | ||
| activityView.showLoading() | ||
| AfterDirectoryInitializationRunner().runWithLifecycle(activity) { loadSettingsUI() } | ||
| } | ||
|
|
||
| fun clearGameSettings() { | ||
| settings!!.clearGameSettings() | ||
| onSettingChanged() | ||
| } | ||
|
|
||
| fun onStop(finishing: Boolean) { | ||
| if (settings != null && finishing && shouldSave) { | ||
| Log.debug("[SettingsActivity] Settings activity stopping. Saving settings to INI...") | ||
| settings!!.saveSettings(activity) | ||
| } | ||
| } | ||
|
|
||
| fun onSettingChanged() { | ||
| shouldSave = true | ||
| } | ||
|
|
||
| fun saveState(outState: Bundle) { | ||
| outState.putBoolean(KEY_SHOULD_SAVE, shouldSave) | ||
| } | ||
|
|
||
| fun onMenuTagAction(menuTag: MenuTag, value: Int) { | ||
| if (menuTag.isSerialPort1Menu) { | ||
| // Not disabled or dummy | ||
| if (value != 0 && value != 255) { | ||
| val bundle = Bundle() | ||
| bundle.putInt(SettingsFragmentPresenter.ARG_SERIALPORT1_TYPE, value) | ||
| activityView.showSettingsFragment(menuTag, bundle, true, gameId!!) | ||
| } | ||
| } | ||
| if (menuTag.isGCPadMenu) { | ||
| // Not disabled | ||
| if (value != 0) | ||
| { | ||
| val bundle = Bundle() | ||
| bundle.putInt(SettingsFragmentPresenter.ARG_CONTROLLER_TYPE, value) | ||
| activityView.showSettingsFragment(menuTag, bundle, true, gameId!!) | ||
| } | ||
| } | ||
| if (menuTag.isWiimoteMenu) { | ||
| // Emulated Wii Remote | ||
| if (value == 1) { | ||
| activityView.showSettingsFragment(menuTag, null, true, gameId!!) | ||
| } | ||
| } | ||
| if (menuTag.isWiimoteExtensionMenu) { | ||
| // Not disabled | ||
| if (value != 0) { | ||
| val bundle = Bundle() | ||
| bundle.putInt(SettingsFragmentPresenter.ARG_CONTROLLER_TYPE, value) | ||
| activityView.showSettingsFragment(menuTag, bundle, true, gameId!!) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean { | ||
| if (menuTag.isSerialPort1Menu) { | ||
| // Not disabled or dummy | ||
| return value != 0 && value != 255 | ||
| } | ||
| if (menuTag.isGCPadMenu) { | ||
| // Not disabled | ||
| return value != 0 | ||
| } | ||
| if (menuTag.isWiimoteMenu) { | ||
| // Emulated Wii Remote | ||
| return value == 1 | ||
| } | ||
| return if (menuTag.isWiimoteExtensionMenu) { | ||
| // Not disabled | ||
| value != 0 | ||
| } else false | ||
| } | ||
|
|
||
| companion object { | ||
| private const val KEY_SHOULD_SAVE = "should_save" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui | ||
|
|
||
| import android.os.Bundle | ||
| import androidx.fragment.app.DialogFragment | ||
| import org.dolphinemu.dolphinemu.features.settings.model.Settings | ||
|
|
||
| /** | ||
| * Abstraction for the Activity that manages SettingsFragments. | ||
| */ | ||
| interface SettingsActivityView { | ||
| /** | ||
| * Show a new SettingsFragment. | ||
| * | ||
| * @param menuTag Identifier for the settings group that should be displayed. | ||
| * @param addToStack Whether or not this fragment should replace a previous one. | ||
| */ | ||
| fun showSettingsFragment( | ||
| menuTag: MenuTag, | ||
| extras: Bundle?, | ||
| addToStack: Boolean, | ||
| gameId: String | ||
| ) | ||
|
|
||
| /** | ||
| * Shows a DialogFragment. | ||
| * | ||
| * Only one can be shown at a time. | ||
| */ | ||
| fun showDialogFragment(fragment: DialogFragment) | ||
|
|
||
| /** | ||
| * Called by a contained Fragment to get access to the Setting HashMap | ||
| * loaded from disk, so that each Fragment doesn't need to perform its own | ||
| * read operation. | ||
| * | ||
| * @return A possibly null HashMap of Settings. | ||
| */ | ||
| val settings: Settings | ||
|
|
||
| /** | ||
| * Called when an asynchronous load operation completes. | ||
| * | ||
| * @param settings The (possibly null) result of the ini load operation. | ||
| */ | ||
| fun onSettingsFileLoaded(settings: Settings) | ||
|
|
||
| /** | ||
| * Called when an asynchronous load operation fails. | ||
| */ | ||
| fun onSettingsFileNotFound() | ||
|
|
||
| /** | ||
| * Display a popup text message on screen. | ||
| * | ||
| * @param message The contents of the onscreen message. | ||
| */ | ||
| fun showToastMessage(message: String) | ||
|
|
||
| /** | ||
| * End the activity. | ||
| */ | ||
| fun finish() | ||
|
|
||
| /** | ||
| * Called by a containing Fragment to tell the Activity that a Setting was changed; | ||
| * unless this has been called, the Activity will not save to disk. | ||
| */ | ||
| fun onSettingChanged() | ||
|
|
||
| /** | ||
| * Refetches the values of all controller settings. | ||
| * | ||
| * To be used when loading an input profile or performing some other action that changes all | ||
| * controller settings at once. | ||
| */ | ||
| fun onControllerSettingsChanged() | ||
|
|
||
| /** | ||
| * Called by a containing Fragment to tell the containing Activity that the user wants to open the | ||
| * MenuTag associated with a Setting. | ||
| * | ||
| * @param menuTag The MenuTag of the Setting. | ||
| * @param value The current value of the Setting. | ||
| */ | ||
| fun onMenuTagAction(menuTag: MenuTag, value: Int) | ||
|
|
||
| /** | ||
| * Returns whether anything will happen when the user wants to open the MenuTag associated with a | ||
| * Setting, given the current value of the Setting. | ||
| * | ||
| * @param menuTag The MenuTag of the Setting. | ||
| * @param value The current value of the Setting. | ||
| */ | ||
| fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean | ||
|
|
||
| /** | ||
| * Show loading dialog while loading the settings | ||
| */ | ||
| fun showLoading() | ||
|
|
||
| /** | ||
| * Hide the loading dialog | ||
| */ | ||
| fun hideLoading() | ||
|
|
||
| /** | ||
| * Tell the user that there is junk in the game INI and ask if they want to delete the whole file. | ||
| */ | ||
| fun showGameIniJunkDeletionQuestion() | ||
|
|
||
| /** | ||
| * Accesses the material toolbar layout and changes the title | ||
| */ | ||
| fun setToolbarTitle(title: String) | ||
| /** | ||
| * Returns whether the input mapping dialog should detect inputs from all devices, | ||
| * not just the device configured for the controller. | ||
| */ | ||
| /** | ||
| * Sets whether the input mapping dialog should detect inputs from all devices, | ||
| * not just the device configured for the controller. | ||
| */ | ||
| var isMappingAllDevices: Boolean | ||
|
|
||
| /** | ||
| * Shows or hides a warning telling the user that they're using incompatible controller settings. | ||
| * The warning is hidden by default. | ||
| * | ||
| * @param visible Whether the warning should be visible. | ||
| * @return The height of the warning view, or 0 if the view is now invisible. | ||
| */ | ||
| fun setOldControllerSettingsWarningVisibility(visible: Boolean): Int | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,242 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui | ||
|
|
||
| import android.content.Context | ||
| import android.os.Bundle | ||
| import android.view.LayoutInflater | ||
| import android.view.View | ||
| import android.view.ViewGroup | ||
| import androidx.core.view.ViewCompat | ||
| import androidx.core.view.WindowInsetsCompat | ||
| import androidx.core.view.updatePadding | ||
| import androidx.fragment.app.DialogFragment | ||
| import androidx.fragment.app.Fragment | ||
| import androidx.fragment.app.FragmentActivity | ||
| import androidx.recyclerview.widget.LinearLayoutManager | ||
| import org.dolphinemu.dolphinemu.R | ||
| import org.dolphinemu.dolphinemu.databinding.FragmentSettingsBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.Settings | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.utils.SerializableHelper.serializable | ||
| import java.util.* | ||
| import kotlin.collections.ArrayList | ||
|
|
||
| class SettingsFragment : Fragment(), SettingsFragmentView { | ||
| private lateinit var presenter: SettingsFragmentPresenter | ||
| private var activityView: SettingsActivityView? = null | ||
|
|
||
| private lateinit var menuTag: MenuTag | ||
|
|
||
| override val fragmentActivity: FragmentActivity | ||
| get() = requireActivity() | ||
|
|
||
| override var adapter: SettingsAdapter? = null | ||
|
|
||
| private var oldControllerSettingsWarningHeight = 0 | ||
|
|
||
| private var binding: FragmentSettingsBinding? = null | ||
|
|
||
| override fun onAttach(context: Context) { | ||
| super.onAttach(context) | ||
|
|
||
| activityView = context as SettingsActivityView | ||
| } | ||
|
|
||
| override fun onCreate(savedInstanceState: Bundle?) { | ||
| super.onCreate(savedInstanceState) | ||
|
|
||
| menuTag = requireArguments().serializable(ARGUMENT_MENU_TAG)!! | ||
|
|
||
| val gameId = requireArguments().getString(ARGUMENT_GAME_ID) | ||
| presenter = SettingsFragmentPresenter(this, requireContext()) | ||
| adapter = SettingsAdapter(this, requireContext()) | ||
|
|
||
| presenter.onCreate(menuTag, gameId, requireArguments()) | ||
| } | ||
|
|
||
| override fun onCreateView( | ||
| inflater: LayoutInflater, | ||
| container: ViewGroup?, | ||
| savedInstanceState: Bundle? | ||
| ): View { | ||
| binding = FragmentSettingsBinding.inflate(inflater, container, false) | ||
| return binding!!.root | ||
| } | ||
|
|
||
| override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||
| if (titles.containsKey(menuTag)) { | ||
| activityView!!.setToolbarTitle(getString(titles[menuTag]!!)) | ||
| } | ||
|
|
||
| val manager = LinearLayoutManager(activity) | ||
|
|
||
| val recyclerView = binding!!.listSettings | ||
| recyclerView.adapter = adapter | ||
| recyclerView.layoutManager = manager | ||
|
|
||
| val divider = SettingsDividerItemDecoration(requireActivity()) | ||
| recyclerView.addItemDecoration(divider) | ||
|
|
||
| setInsets() | ||
|
|
||
| val activity = activity as SettingsActivityView? | ||
| presenter.onViewCreated(menuTag, activity!!.settings) | ||
| } | ||
|
|
||
| override fun onDestroyView() { | ||
| super.onDestroyView() | ||
| binding = null | ||
| } | ||
|
|
||
| override fun onDetach() { | ||
| super.onDetach() | ||
| activityView = null | ||
|
|
||
| if (adapter != null) { | ||
| adapter!!.closeDialog() | ||
| } | ||
| } | ||
|
|
||
| override fun onSettingsFileLoaded(settings: Settings) { | ||
| presenter.settings = settings | ||
| } | ||
|
|
||
| override fun showSettingsList(settingsList: ArrayList<SettingsItem>) { | ||
| adapter!!.setSettings(settingsList) | ||
| } | ||
|
|
||
| override fun loadDefaultSettings() { | ||
| presenter.loadDefaultSettings() | ||
| } | ||
|
|
||
| override fun loadSubMenu(menuKey: MenuTag) { | ||
| activityView!!.showSettingsFragment( | ||
| menuKey, | ||
| null, | ||
| true, | ||
| requireArguments().getString(ARGUMENT_GAME_ID)!! | ||
| ) | ||
| } | ||
|
|
||
| override fun showDialogFragment(fragment: DialogFragment) { | ||
| activityView!!.showDialogFragment(fragment) | ||
| } | ||
|
|
||
| override fun showToastMessage(message: String) { | ||
| activityView!!.showToastMessage(message) | ||
| } | ||
|
|
||
| override val settings: Settings? | ||
| get() = presenter.settings | ||
|
|
||
| override fun onSettingChanged() { | ||
| activityView!!.onSettingChanged() | ||
| } | ||
|
|
||
| override fun onControllerSettingsChanged() { | ||
| adapter!!.notifyAllSettingsChanged() | ||
| presenter.updateOldControllerSettingsWarningVisibility() | ||
| } | ||
|
|
||
| override fun onMenuTagAction(menuTag: MenuTag, value: Int) { | ||
| activityView!!.onMenuTagAction(menuTag, value) | ||
| } | ||
|
|
||
| override fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean { | ||
| return activityView!!.hasMenuTagActionForValue(menuTag, value) | ||
| } | ||
|
|
||
| override var isMappingAllDevices: Boolean | ||
| get() = activityView!!.isMappingAllDevices | ||
| set(allDevices) { | ||
| activityView!!.isMappingAllDevices = allDevices | ||
| } | ||
|
|
||
| override fun setOldControllerSettingsWarningVisibility(visible: Boolean) { | ||
| oldControllerSettingsWarningHeight = | ||
| activityView!!.setOldControllerSettingsWarningVisibility(visible) | ||
|
|
||
| // Trigger the insets listener we've registered | ||
| binding!!.listSettings.requestApplyInsets() | ||
| } | ||
|
|
||
| private fun setInsets() { | ||
| ViewCompat.setOnApplyWindowInsetsListener(binding!!.listSettings) { v: View, windowInsets: WindowInsetsCompat -> | ||
| val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) | ||
| val listSpacing = resources.getDimensionPixelSize(R.dimen.spacing_list) | ||
| v.updatePadding(bottom = insets.bottom + listSpacing + oldControllerSettingsWarningHeight) | ||
| windowInsets | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| private const val ARGUMENT_MENU_TAG = "menu_tag" | ||
| private const val ARGUMENT_GAME_ID = "game_id" | ||
| private val titles: MutableMap<MenuTag, Int> = EnumMap(MenuTag::class.java) | ||
|
|
||
| init { | ||
| titles[MenuTag.SETTINGS] = R.string.settings | ||
| titles[MenuTag.CONFIG] = R.string.config | ||
| titles[MenuTag.CONFIG_GENERAL] = R.string.general_submenu | ||
| titles[MenuTag.CONFIG_INTERFACE] = R.string.interface_submenu | ||
| titles[MenuTag.CONFIG_AUDIO] = R.string.audio_submenu | ||
| titles[MenuTag.CONFIG_PATHS] = R.string.paths_submenu | ||
| titles[MenuTag.CONFIG_GAME_CUBE] = R.string.gamecube_submenu | ||
| titles[MenuTag.CONFIG_SERIALPORT1] = R.string.serialport1_submenu | ||
| titles[MenuTag.CONFIG_WII] = R.string.wii_submenu | ||
| titles[MenuTag.CONFIG_ADVANCED] = R.string.advanced_submenu | ||
| titles[MenuTag.DEBUG] = R.string.debug_submenu | ||
| titles[MenuTag.GRAPHICS] = R.string.graphics_settings | ||
| titles[MenuTag.ENHANCEMENTS] = R.string.enhancements_submenu | ||
| titles[MenuTag.STEREOSCOPY] = R.string.stereoscopy_submenu | ||
| titles[MenuTag.HACKS] = R.string.hacks_submenu | ||
| titles[MenuTag.STATISTICS] = R.string.statistics_submenu | ||
| titles[MenuTag.ADVANCED_GRAPHICS] = R.string.advanced_graphics_submenu | ||
| titles[MenuTag.CONFIG_LOG] = R.string.log_submenu | ||
| titles[MenuTag.GCPAD_TYPE] = R.string.gcpad_settings | ||
| titles[MenuTag.WIIMOTE] = R.string.wiimote_settings | ||
| titles[MenuTag.WIIMOTE_EXTENSION] = R.string.wiimote_extensions | ||
| titles[MenuTag.GCPAD_1] = R.string.controller_0 | ||
| titles[MenuTag.GCPAD_2] = R.string.controller_1 | ||
| titles[MenuTag.GCPAD_3] = R.string.controller_2 | ||
| titles[MenuTag.GCPAD_4] = R.string.controller_3 | ||
| titles[MenuTag.WIIMOTE_1] = R.string.wiimote_0 | ||
| titles[MenuTag.WIIMOTE_2] = R.string.wiimote_1 | ||
| titles[MenuTag.WIIMOTE_3] = R.string.wiimote_2 | ||
| titles[MenuTag.WIIMOTE_4] = R.string.wiimote_3 | ||
| titles[MenuTag.WIIMOTE_EXTENSION_1] = R.string.wiimote_extension_0 | ||
| titles[MenuTag.WIIMOTE_EXTENSION_2] = R.string.wiimote_extension_1 | ||
| titles[MenuTag.WIIMOTE_EXTENSION_3] = R.string.wiimote_extension_2 | ||
| titles[MenuTag.WIIMOTE_EXTENSION_4] = R.string.wiimote_extension_3 | ||
| titles[MenuTag.WIIMOTE_GENERAL_1] = R.string.wiimote_general | ||
| titles[MenuTag.WIIMOTE_GENERAL_2] = R.string.wiimote_general | ||
| titles[MenuTag.WIIMOTE_GENERAL_3] = R.string.wiimote_general | ||
| titles[MenuTag.WIIMOTE_GENERAL_4] = R.string.wiimote_general | ||
| titles[MenuTag.WIIMOTE_MOTION_SIMULATION_1] = R.string.wiimote_motion_simulation | ||
| titles[MenuTag.WIIMOTE_MOTION_SIMULATION_2] = R.string.wiimote_motion_simulation | ||
| titles[MenuTag.WIIMOTE_MOTION_SIMULATION_3] = R.string.wiimote_motion_simulation | ||
| titles[MenuTag.WIIMOTE_MOTION_SIMULATION_4] = R.string.wiimote_motion_simulation | ||
| titles[MenuTag.WIIMOTE_MOTION_INPUT_1] = R.string.wiimote_motion_input | ||
| titles[MenuTag.WIIMOTE_MOTION_INPUT_2] = R.string.wiimote_motion_input | ||
| titles[MenuTag.WIIMOTE_MOTION_INPUT_3] = R.string.wiimote_motion_input | ||
| titles[MenuTag.WIIMOTE_MOTION_INPUT_4] = R.string.wiimote_motion_input | ||
| } | ||
|
|
||
| @JvmStatic | ||
| fun newInstance(menuTag: MenuTag?, gameId: String?, extras: Bundle?): Fragment { | ||
| val fragment = SettingsFragment() | ||
|
|
||
| val arguments = Bundle() | ||
| if (extras != null) { | ||
| arguments.putAll(extras) | ||
| } | ||
|
|
||
| arguments.putSerializable(ARGUMENT_MENU_TAG, menuTag) | ||
| arguments.putString(ARGUMENT_GAME_ID, gameId) | ||
|
|
||
| fragment.arguments = arguments | ||
| return fragment | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui | ||
|
|
||
| import androidx.fragment.app.DialogFragment | ||
| import androidx.fragment.app.FragmentActivity | ||
| import org.dolphinemu.dolphinemu.features.settings.model.Settings | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
|
|
||
| /** | ||
| * Abstraction for a screen showing a list of settings. Instances of | ||
| * this type of view will each display a layer of the Setting hierarchy. | ||
| */ | ||
| interface SettingsFragmentView { | ||
| /** | ||
| * Called by the containing Activity to notify the Fragment that an | ||
| * asynchronous load operation completed. | ||
| * | ||
| * @param settings The (possibly null) result of the ini load operation. | ||
| */ | ||
| fun onSettingsFileLoaded(settings: Settings) | ||
|
|
||
| /** | ||
| * Pass an ArrayList of settings to the View so that it can be displayed on screen. | ||
| * | ||
| * @param settingsList The settings to display | ||
| */ | ||
| fun showSettingsList(settingsList: ArrayList<SettingsItem>) | ||
|
|
||
| /** | ||
| * Called by the containing Activity when an asynchronous load operation fails. | ||
| * Instructs the Fragment to load the settings screen with defaults selected. | ||
| */ | ||
| fun loadDefaultSettings() | ||
|
|
||
| /** | ||
| * @return The Fragment's containing activity. | ||
| */ | ||
| val fragmentActivity: FragmentActivity | ||
|
|
||
| /** | ||
| * @return The Fragment's SettingsAdapter. | ||
| */ | ||
| val adapter: SettingsAdapter? | ||
|
|
||
| /** | ||
| * Tell the Fragment to tell the containing Activity to show a new | ||
| * Fragment containing a submenu of settings. | ||
| * | ||
| * @param menuKey Identifier for the settings group that should be shown. | ||
| */ | ||
| fun loadSubMenu(menuKey: MenuTag) | ||
| fun showDialogFragment(fragment: DialogFragment) | ||
|
|
||
| /** | ||
| * Tell the Fragment to tell the containing activity to display a toast message. | ||
| * | ||
| * @param message Text to be shown in the Toast | ||
| */ | ||
| fun showToastMessage(message: String) | ||
|
|
||
| /** | ||
| * @return The backing settings store. | ||
| */ | ||
| val settings: Settings? | ||
|
|
||
| /** | ||
| * Have the fragment tell the containing Activity that a Setting was modified. | ||
| */ | ||
| fun onSettingChanged() | ||
|
|
||
| /** | ||
| * Refetches the values of all controller settings. | ||
| * | ||
| * To be used when loading an input profile or performing some other action that changes all | ||
| * controller settings at once. | ||
| */ | ||
| fun onControllerSettingsChanged() | ||
|
|
||
| /** | ||
| * Have the fragment tell the containing Activity that the user wants to open the MenuTag | ||
| * associated with a Setting. | ||
| * | ||
| * @param menuTag The MenuTag of the Setting. | ||
| * @param value The current value of the Setting. | ||
| */ | ||
| fun onMenuTagAction(menuTag: MenuTag, value: Int) | ||
|
|
||
| /** | ||
| * Returns whether anything will happen when the user wants to open the MenuTag associated with a | ||
| * stringSetting, given the current value of the Setting. | ||
| * | ||
| * @param menuTag The MenuTag of the Setting. | ||
| * @param value The current value of the Setting. | ||
| */ | ||
| fun hasMenuTagActionForValue(menuTag: MenuTag, value: Int): Boolean | ||
| /** | ||
| * Returns whether the input mapping dialog should detect inputs from all devices, | ||
| * not just the device configured for the controller. | ||
| */ | ||
| /** | ||
| * Sets whether the input mapping dialog should detect inputs from all devices, | ||
| * not just the device configured for the controller. | ||
| */ | ||
| var isMappingAllDevices: Boolean | ||
|
|
||
| /** | ||
| * Shows or hides a warning telling the user that they're using incompatible controller settings. | ||
| * The warning is hidden by default. | ||
| * | ||
| * @param visible Whether the warning should be visible. | ||
| */ | ||
| fun setOldControllerSettingsWarningVisibility(visible: Boolean) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui | ||
|
|
||
| import androidx.lifecycle.ViewModel | ||
| import org.dolphinemu.dolphinemu.features.settings.model.Settings | ||
|
|
||
| class SettingsViewModel : ViewModel() { | ||
| val settings = Settings() | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui.viewholder | ||
|
|
||
| import android.text.TextUtils | ||
| import android.view.View | ||
| import org.dolphinemu.dolphinemu.R | ||
| import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.FilePicker | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
| import org.dolphinemu.dolphinemu.ui.main.MainPresenter | ||
| import org.dolphinemu.dolphinemu.utils.DirectoryInitialization | ||
| import org.dolphinemu.dolphinemu.utils.FileBrowserHelper | ||
|
|
||
| class FilePickerViewHolder( | ||
| private val binding: ListItemSettingBinding, | ||
| adapter: SettingsAdapter? | ||
| ) : SettingViewHolder(binding.getRoot(), adapter!!) { | ||
| lateinit var setting: FilePicker | ||
|
|
||
| override val item: SettingsItem | ||
| get() = setting | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| setting = item as FilePicker | ||
|
|
||
| var path = setting.getSelectedValue() | ||
|
|
||
| if (FileBrowserHelper.isPathEmptyOrValid(path)) { | ||
| itemView.background = binding.getRoot().background | ||
| } else { | ||
| itemView.setBackgroundResource(R.drawable.invalid_setting_background) | ||
| } | ||
|
|
||
| binding.textSettingName.text = setting.name | ||
|
|
||
| if (!TextUtils.isEmpty(setting.description)) { | ||
| binding.textSettingDescription.text = setting.description | ||
| } else { | ||
| if (TextUtils.isEmpty(path)) { | ||
| val defaultPathRelative = setting.defaultPathRelativeToUserDirectory | ||
| if (defaultPathRelative != null) { | ||
| path = DirectoryInitialization.getUserDirectory() + defaultPathRelative | ||
| } | ||
| } | ||
| binding.textSettingDescription.text = path | ||
| } | ||
|
|
||
| setStyle(binding.textSettingName, setting) | ||
| } | ||
|
|
||
| override fun onClick(clicked: View) { | ||
| if (!setting.isEditable) { | ||
| showNotRuntimeEditableError() | ||
| return | ||
| } | ||
|
|
||
| val position = bindingAdapterPosition | ||
| if (setting.requestType == MainPresenter.REQUEST_DIRECTORY) { | ||
| adapter.onFilePickerDirectoryClick(setting, position) | ||
| } else { | ||
| adapter.onFilePickerFileClick(setting, position) | ||
| } | ||
|
|
||
| setStyle(binding.textSettingName, setting) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui.viewholder | ||
|
|
||
| import android.text.method.LinkMovementMethod | ||
| import com.google.android.material.color.MaterialColors | ||
| import org.dolphinemu.dolphinemu.R | ||
| import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
|
|
||
| class HeaderHyperLinkViewHolder( | ||
| private val binding: ListItemHeaderBinding, | ||
| adapter: SettingsAdapter? | ||
| ) : HeaderViewHolder(binding, adapter) { | ||
| init { | ||
| itemView.setOnClickListener(null) | ||
| } | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| super.bind(item) | ||
| binding.textHeaderName.movementMethod = LinkMovementMethod.getInstance() | ||
| binding.textHeaderName.setLinkTextColor( | ||
| MaterialColors.getColor(itemView, R.attr.colorTertiary) | ||
| ) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui.viewholder | ||
|
|
||
| import android.view.View | ||
| import org.dolphinemu.dolphinemu.databinding.ListItemHeaderBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
|
|
||
| open class HeaderViewHolder( | ||
| private val binding: ListItemHeaderBinding, | ||
| adapter: SettingsAdapter? | ||
| ) : SettingViewHolder(binding.root, adapter!!) { | ||
| override val item: SettingsItem? = null | ||
|
|
||
| init { | ||
| itemView.setOnClickListener(null) | ||
| } | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| binding.textHeaderName.text = item.name | ||
| } | ||
|
|
||
| override fun onClick(clicked: View) { | ||
| // no-op | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui.viewholder | ||
|
|
||
| import android.text.TextUtils | ||
| import android.view.View | ||
| import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.InputStringSetting | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
|
|
||
| class InputStringSettingViewHolder( | ||
| private val binding: ListItemSettingBinding, | ||
| adapter: SettingsAdapter | ||
| ) : SettingViewHolder(binding.getRoot(), adapter) { | ||
| private lateinit var setting: InputStringSetting | ||
|
|
||
| override val item: SettingsItem | ||
| get() = setting | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| setting = item as InputStringSetting | ||
|
|
||
| val inputString = setting.selectedValue | ||
|
|
||
| binding.textSettingName.text = setting.name | ||
|
|
||
| if (!TextUtils.isEmpty(inputString)) { | ||
| binding.textSettingDescription.text = inputString | ||
| } else { | ||
| binding.textSettingDescription.text = setting.description | ||
| } | ||
|
|
||
| setStyle(binding.textSettingName, setting) | ||
| } | ||
|
|
||
| override fun onClick(clicked: View) { | ||
| if (!setting.isEditable) { | ||
| showNotRuntimeEditableError() | ||
| return | ||
| } | ||
|
|
||
| val position = bindingAdapterPosition | ||
|
|
||
| adapter.onInputStringClick(setting, position) | ||
|
|
||
| setStyle(binding.textSettingName, setting) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui.viewholder | ||
|
|
||
| import android.content.Context | ||
| import android.content.DialogInterface | ||
| import android.view.View | ||
| import android.widget.Toast | ||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| import org.dolphinemu.dolphinemu.R | ||
| import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.RunRunnable | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
|
|
||
| class RunRunnableViewHolder( | ||
| private val mBinding: ListItemSettingBinding, adapter: SettingsAdapter?, | ||
| private val mContext: Context | ||
| ) : SettingViewHolder(mBinding.getRoot(), adapter!!) { | ||
| private lateinit var setting: RunRunnable | ||
|
|
||
| override val item: SettingsItem | ||
| get() = setting | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| setting = item as RunRunnable | ||
|
|
||
| mBinding.textSettingName.text = setting.name | ||
| mBinding.textSettingDescription.text = setting.description | ||
|
|
||
| setStyle(mBinding.textSettingName, setting) | ||
| } | ||
|
|
||
| override fun onClick(clicked: View) { | ||
| if (!setting.isEditable) { | ||
| showNotRuntimeEditableError() | ||
| return | ||
| } | ||
|
|
||
| val alertTextID = setting.alertText | ||
|
|
||
| if (alertTextID > 0) { | ||
| MaterialAlertDialogBuilder(mContext) | ||
| .setTitle(setting.name) | ||
| .setMessage(alertTextID) | ||
| .setPositiveButton(R.string.ok) { dialog: DialogInterface, _: Int -> | ||
| runRunnable() | ||
| dialog.dismiss() | ||
| } | ||
| .setNegativeButton(R.string.cancel) { dialog: DialogInterface, _: Int -> dialog.dismiss() } | ||
| .show() | ||
| } else { | ||
| runRunnable() | ||
| } | ||
| } | ||
|
|
||
| private fun runRunnable() { | ||
| setting.runnable.run() | ||
|
|
||
| if (setting.toastTextAfterRun > 0) { | ||
| Toast.makeText( | ||
| mContext, | ||
| mContext.getString(setting.toastTextAfterRun), | ||
| Toast.LENGTH_SHORT | ||
| ).show() | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui.viewholder | ||
|
|
||
| import android.content.DialogInterface | ||
| import android.graphics.Paint | ||
| import android.graphics.Typeface | ||
| import android.view.View | ||
| import android.view.View.OnLongClickListener | ||
| import android.widget.TextView | ||
| import android.widget.Toast | ||
| import androidx.recyclerview.widget.RecyclerView | ||
| import com.google.android.material.dialog.MaterialAlertDialogBuilder | ||
| import org.dolphinemu.dolphinemu.DolphinApplication | ||
| import org.dolphinemu.dolphinemu.R | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
|
|
||
| abstract class SettingViewHolder(itemView: View, protected val adapter: SettingsAdapter) : | ||
| RecyclerView.ViewHolder(itemView), View.OnClickListener, OnLongClickListener { | ||
|
|
||
| init { | ||
| itemView.setOnClickListener(this) | ||
| itemView.setOnLongClickListener(this) | ||
| } | ||
|
|
||
| protected fun setStyle(textView: TextView, settingsItem: SettingsItem) { | ||
| val overridden = settingsItem.isOverridden | ||
| textView.setTypeface(null, if (overridden) Typeface.BOLD else Typeface.NORMAL) | ||
|
|
||
| if (!settingsItem.isEditable) textView.paintFlags = | ||
| textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG | ||
| } | ||
|
|
||
| /** | ||
| * Called by the adapter to set this ViewHolder's child views to display the list item | ||
| * it must now represent. | ||
| * | ||
| * @param item The list item that should be represented by this ViewHolder. | ||
| */ | ||
| abstract fun bind(item: SettingsItem) | ||
|
|
||
| /** | ||
| * Called when this ViewHolder's view is clicked on. Implementations should usually pass | ||
| * this event up to the adapter. | ||
| * | ||
| * @param clicked The view that was clicked on. | ||
| */ | ||
| abstract override fun onClick(clicked: View) | ||
|
|
||
| protected abstract val item: SettingsItem? | ||
|
|
||
| override fun onLongClick(clicked: View): Boolean { | ||
| val item = item | ||
|
|
||
| if (item == null || !item.canClear()) return false | ||
|
|
||
| if (!item.isEditable) { | ||
| showNotRuntimeEditableError() | ||
| return true | ||
| } | ||
|
|
||
| val context = clicked.context | ||
|
|
||
| MaterialAlertDialogBuilder(context) | ||
| .setMessage(R.string.setting_clear_confirm) | ||
| .setPositiveButton(R.string.ok) { dialog: DialogInterface, _: Int -> | ||
| adapter.clearSetting(item) | ||
| bind(item) | ||
| Toast.makeText( | ||
| context, | ||
| R.string.setting_cleared, | ||
| Toast.LENGTH_SHORT | ||
| ).show() | ||
| dialog.dismiss() | ||
| } | ||
| .setNegativeButton(R.string.cancel) { dialog: DialogInterface, _: Int -> dialog.dismiss() } | ||
| .show() | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| protected fun showIplNotAvailableError() { | ||
| Toast.makeText( | ||
| DolphinApplication.getAppContext(), | ||
| R.string.ipl_not_found, | ||
| Toast.LENGTH_SHORT | ||
| ).show() | ||
| } | ||
|
|
||
| protected fun showNotRuntimeEditableError() { | ||
| Toast.makeText( | ||
| DolphinApplication.getAppContext(), | ||
| R.string.setting_not_runtime_editable, | ||
| Toast.LENGTH_SHORT | ||
| ).show() | ||
| } | ||
| } |