This file was deleted.

@@ -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"
)
}
}
}

This file was deleted.

@@ -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)
}
}
}

This file was deleted.

@@ -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"
}
}

This file was deleted.

@@ -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
}

This file was deleted.

Large diffs are not rendered by default.

This file was deleted.

@@ -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
}
}
}

This file was deleted.

Large diffs are not rendered by default.

This file was deleted.

@@ -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)
}

This file was deleted.

@@ -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()
}
@@ -18,12 +18,15 @@ class DateTimeSettingViewHolder(
private val binding: ListItemSettingBinding,
adapter: SettingsAdapter
) : SettingViewHolder(binding.root, adapter) {
private var mItem: DateTimeChoiceSetting? = null
lateinit var setting: DateTimeChoiceSetting

override val item: SettingsItem
get() = setting

override fun bind(item: SettingsItem) {
mItem = item as DateTimeChoiceSetting
val inputTime = mItem!!.getSelectedValue()
binding.textSettingName.text = item.getName()
setting = item as DateTimeChoiceSetting
val inputTime = setting.getSelectedValue()
binding.textSettingName.text = item.name

if (!TextUtils.isEmpty(inputTime)) {
val epochTime = inputTime.substring(2).toLong(16)
@@ -32,21 +35,17 @@ class DateTimeSettingViewHolder(
val dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
binding.textSettingDescription.text = dateFormatter.format(zonedTime)
} else {
binding.textSettingDescription.text = item.getDescription()
binding.textSettingDescription.text = item.description
}
setStyle(binding.textSettingName, mItem)
setStyle(binding.textSettingName, setting)
}

override fun onClick(clicked: View) {
if (!mItem!!.isEditable) {
if (!setting.isEditable) {
showNotRuntimeEditableError()
return
}
adapter.onDateTimeClick(mItem, bindingAdapterPosition)
setStyle(binding.textSettingName, mItem)
}

override fun getItem(): SettingsItem? {
return mItem
adapter.onDateTimeClick(setting, bindingAdapterPosition)
setStyle(binding.textSettingName, setting)
}
}

This file was deleted.

@@ -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)
}
}

This file was deleted.

@@ -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)
)
}
}

This file was deleted.

@@ -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
}
}

This file was deleted.

@@ -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)
}
}

This file was deleted.

@@ -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()
}
}
}

This file was deleted.

@@ -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()
}
}