| 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.text.TextUtils | ||
| import android.view.View | ||
| import org.dolphinemu.dolphinemu.databinding.ListItemSettingBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSetting | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SingleChoiceSettingDynamicDescriptions | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.StringSingleChoiceSetting | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.MenuTag | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
|
|
||
| class SingleChoiceViewHolder( | ||
| private val binding: ListItemSettingBinding, | ||
| adapter: SettingsAdapter | ||
| ) : SettingViewHolder(binding.getRoot(), adapter) { | ||
| override var item: SettingsItem? = null | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| this.item = item | ||
|
|
||
| binding.textSettingName.text = item.name | ||
|
|
||
| if (!TextUtils.isEmpty(item.description)) { | ||
| binding.textSettingDescription.text = item.description | ||
| } else if (item is SingleChoiceSetting) { | ||
| val selected = item.selectedValue | ||
| val resources = binding.textSettingDescription.context.resources | ||
| val choices = resources.getStringArray(item.choicesId) | ||
| val values = resources.getIntArray(item.valuesId) | ||
| for (i in values.indices) { | ||
| if (values[i] == selected) { | ||
| binding.textSettingDescription.text = choices[i] | ||
| } | ||
| } | ||
| } else if (item is StringSingleChoiceSetting) { | ||
| val choice = item.selectedChoice | ||
| binding.textSettingDescription.text = choice | ||
| } else if (item is SingleChoiceSettingDynamicDescriptions) { | ||
| val selected = item.selectedValue | ||
| val resMgr = binding.textSettingDescription.context.resources | ||
| val choices = resMgr.getStringArray(item.descriptionChoicesId) | ||
| val values = resMgr.getIntArray(item.descriptionValuesId) | ||
| for (i in values.indices) { | ||
| if (values[i] == selected) { | ||
| binding.textSettingDescription.text = choices[i] | ||
| } | ||
| } | ||
| } | ||
|
|
||
| var menuTag: MenuTag? = null | ||
| var selectedValue = 0 | ||
| if (item is SingleChoiceSetting) { | ||
| menuTag = item.menuTag | ||
| selectedValue = item.selectedValue | ||
| } else if (item is StringSingleChoiceSetting) { | ||
| menuTag = item.menuTag | ||
| selectedValue = item.selectedValueIndex | ||
| } | ||
|
|
||
| if (menuTag != null && adapter.hasMenuTagActionForValue(menuTag, selectedValue)) { | ||
| binding.buttonMoreSettings.visibility = View.VISIBLE | ||
|
|
||
| binding.buttonMoreSettings.setOnClickListener { | ||
| adapter.onMenuTagAction(menuTag, selectedValue) | ||
| } | ||
| } else { | ||
| binding.buttonMoreSettings.visibility = View.GONE | ||
| } | ||
| setStyle(binding.textSettingName, item) | ||
| } | ||
|
|
||
| override fun onClick(clicked: View) { | ||
| if (!item?.isEditable!!) { | ||
| showNotRuntimeEditableError() | ||
| return | ||
| } | ||
|
|
||
| val position = bindingAdapterPosition | ||
| when (item) { | ||
| is SingleChoiceSetting -> { | ||
| adapter.onSingleChoiceClick(item as SingleChoiceSetting, position) | ||
| } | ||
| is StringSingleChoiceSetting -> { | ||
| adapter.onStringSingleChoiceClick(item as StringSingleChoiceSetting, position) | ||
| } | ||
| is SingleChoiceSettingDynamicDescriptions -> { | ||
| adapter.onSingleChoiceDynamicDescriptionsClick( | ||
| item as SingleChoiceSettingDynamicDescriptions, | ||
| position | ||
| ) | ||
| } | ||
| } | ||
| setStyle(binding.textSettingName, item!!) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,51 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui.viewholder | ||
|
|
||
| import android.content.Context | ||
| 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.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SliderSetting | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
|
|
||
| class SliderViewHolder( | ||
| private val binding: ListItemSettingBinding, adapter: SettingsAdapter?, | ||
| private val context: Context | ||
| ) : SettingViewHolder(binding.getRoot(), adapter!!) { | ||
| private lateinit var setting: SliderSetting | ||
|
|
||
| override val item: SettingsItem | ||
| get() = setting | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| setting = item as SliderSetting | ||
|
|
||
| binding.textSettingName.text = item.name | ||
|
|
||
| if (!TextUtils.isEmpty(item.description)) { | ||
| binding.textSettingDescription.text = item.description | ||
| } else { | ||
| binding.textSettingDescription.text = context.getString( | ||
| R.string.slider_setting_value, | ||
| setting.selectedValue, | ||
| setting.units | ||
| ) | ||
| } | ||
|
|
||
| setStyle(binding.textSettingName, setting) | ||
| } | ||
|
|
||
| override fun onClick(clicked: View) { | ||
| if (!setting.isEditable) { | ||
| showNotRuntimeEditableError() | ||
| return | ||
| } | ||
|
|
||
| adapter.onSliderClick(setting, bindingAdapterPosition) | ||
|
|
||
| setStyle(binding.textSettingName, setting) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| // 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.ListItemSubmenuBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SubmenuSetting | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
|
|
||
| class SubmenuViewHolder( | ||
| private val mBinding: ListItemSubmenuBinding, | ||
| adapter: SettingsAdapter | ||
| ) : SettingViewHolder(mBinding.root, adapter) { | ||
| private lateinit var setting: SubmenuSetting | ||
|
|
||
| override val item: SettingsItem | ||
| get() = setting | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| setting = item as SubmenuSetting | ||
| mBinding.textSettingName.text = item.name | ||
| } | ||
|
|
||
| override fun onClick(clicked: View) { | ||
| adapter.onSubmenuClick(setting) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.ui.viewholder | ||
|
|
||
| import android.view.View | ||
| import android.widget.CompoundButton | ||
| import org.dolphinemu.dolphinemu.databinding.ListItemSettingSwitchBinding | ||
| import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SettingsItem | ||
| import org.dolphinemu.dolphinemu.features.settings.model.view.SwitchSetting | ||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsAdapter | ||
| import org.dolphinemu.dolphinemu.utils.DirectoryInitialization | ||
| import java.io.File | ||
| import java.util.* | ||
|
|
||
| class SwitchSettingViewHolder( | ||
| private val binding: ListItemSettingSwitchBinding, | ||
| adapter: SettingsAdapter | ||
| ) : SettingViewHolder(binding.root, adapter) { | ||
| private lateinit var setting: SwitchSetting | ||
|
|
||
| override val item: SettingsItem | ||
| get() = setting | ||
|
|
||
| private var iplExists = false | ||
|
|
||
| override fun bind(item: SettingsItem) { | ||
| setting = item as SwitchSetting | ||
|
|
||
| binding.textSettingName.text = item.name | ||
| binding.textSettingDescription.text = item.description | ||
|
|
||
| binding.settingSwitch.isChecked = setting.isChecked | ||
| binding.settingSwitch.isEnabled = setting.isEditable | ||
|
|
||
| // Check for IPL to make sure user can skip. | ||
| if (setting.setting === BooleanSetting.MAIN_SKIP_IPL) { | ||
| val iplDirs = ArrayList(listOf("USA", "JAP", "EUR")) | ||
| for (dir in iplDirs) { | ||
| val iplFile = File( | ||
| DirectoryInitialization.getUserDirectory(), | ||
| File.separator + "GC" + File.separator + dir + File.separator + "IPL.bin" | ||
| ) | ||
| if (iplFile.exists()) { | ||
| iplExists = true | ||
| break | ||
| } | ||
| } | ||
| binding.settingSwitch.isEnabled = iplExists || !setting.isChecked | ||
| } | ||
|
|
||
| binding.settingSwitch.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean -> | ||
| // If a user has skip IPL disabled previously and deleted their IPL file, we need to allow | ||
| // them to skip it or else their game will appear broken. However, once this is enabled, we | ||
| // need to disable the option again to prevent the same issue from occurring. | ||
| if (setting.setting === BooleanSetting.MAIN_SKIP_IPL && !iplExists && isChecked) { | ||
| binding.settingSwitch.isEnabled = false | ||
| } | ||
|
|
||
| adapter.onBooleanClick(setting, binding.settingSwitch.isChecked) | ||
|
|
||
| setStyle(binding.textSettingName, setting) | ||
| } | ||
| setStyle(binding.textSettingName, setting) | ||
| } | ||
|
|
||
| override fun onClick(clicked: View) { | ||
| if (!setting.isEditable) { | ||
| showNotRuntimeEditableError() | ||
| return | ||
| } | ||
|
|
||
| if (setting.setting === BooleanSetting.MAIN_SKIP_IPL && !iplExists) { | ||
| if (setting.isChecked) { | ||
| showIplNotAvailableError() | ||
| return | ||
| } | ||
| } | ||
|
|
||
| binding.settingSwitch.toggle() | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.settings.utils | ||
|
|
||
| import org.dolphinemu.dolphinemu.features.settings.ui.SettingsActivityView | ||
| import org.dolphinemu.dolphinemu.utils.BiMap | ||
| import org.dolphinemu.dolphinemu.utils.DirectoryInitialization | ||
| import org.dolphinemu.dolphinemu.utils.IniFile | ||
| import org.dolphinemu.dolphinemu.utils.Log | ||
| import java.io.File | ||
|
|
||
| /** | ||
| * Contains static methods for interacting with .ini files in which settings are stored. | ||
| */ | ||
| object SettingsFile { | ||
| const val KEY_ISO_PATH_BASE = "ISOPath" | ||
| const val KEY_ISO_PATHS = "ISOPaths" | ||
| private val sectionsMap = BiMap<String?, String?>() | ||
|
|
||
| init { | ||
| sectionsMap.apply { | ||
| add("Hardware", "Video_Hardware") | ||
| add("Settings", "Video_Settings") | ||
| add("Enhancements", "Video_Enhancements") | ||
| add("Stereoscopy", "Video_Stereoscopy") | ||
| add("Hacks", "Video_Hacks") | ||
| add("GameSpecific", "Video") | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Reads a given .ini file from disk and returns it. | ||
| * If unsuccessful, outputs an error telling why it failed. | ||
| * | ||
| * @param file The ini file to load the settings from | ||
| * @param ini The object to load into | ||
| * @param view The current view. | ||
| */ | ||
| private fun readFile(file: File, ini: IniFile, view: SettingsActivityView) { | ||
| if (!ini.load(file, true)) { | ||
| Log.error("[SettingsFile] Error reading from: " + file.absolutePath) | ||
| view.onSettingsFileNotFound() | ||
| } | ||
| } | ||
|
|
||
| fun readFile(fileName: String, ini: IniFile, view: SettingsActivityView) { | ||
| readFile(getSettingsFile(fileName), ini, view) | ||
| } | ||
|
|
||
| /** | ||
| * Reads a given .ini file from disk and returns it. | ||
| * If unsuccessful, outputs an error telling why it failed. | ||
| * | ||
| * @param gameId the id of the game to load settings for. | ||
| * @param ini The object to load into | ||
| * @param view The current view. | ||
| */ | ||
| fun readCustomGameSettings( | ||
| gameId: String, | ||
| ini: IniFile, | ||
| view: SettingsActivityView | ||
| ) { | ||
| readFile(getCustomGameSettingsFile(gameId), ini, view) | ||
| } | ||
|
|
||
| /** | ||
| * Saves a given .ini file on disk. | ||
| * If unsuccessful, outputs an error telling why it failed. | ||
| * | ||
| * @param fileName The target filename without a path or extension. | ||
| * @param ini The IniFile we want to serialize. | ||
| * @param view The current view. | ||
| */ | ||
| fun saveFile(fileName: String, ini: IniFile, view: SettingsActivityView) { | ||
| if (!ini.save(getSettingsFile(fileName))) { | ||
| Log.error("[SettingsFile] Error saving to: $fileName.ini") | ||
| view.showToastMessage("Error saving $fileName.ini") | ||
| } | ||
| } | ||
|
|
||
| fun saveCustomGameSettings(gameId: String, ini: IniFile) { | ||
| ini.save(getCustomGameSettingsFile(gameId)) | ||
| } | ||
|
|
||
| fun mapSectionNameFromIni(generalSectionName: String): String? { | ||
| return if (sectionsMap.getForward(generalSectionName) != null) { | ||
| sectionsMap.getForward(generalSectionName) | ||
| } else generalSectionName | ||
| } | ||
|
|
||
| fun mapSectionNameToIni(generalSectionName: String): String? { | ||
| return if (sectionsMap.getBackward(generalSectionName) != null) { | ||
| sectionsMap.getBackward(generalSectionName) | ||
| } else generalSectionName | ||
| } | ||
|
|
||
| @JvmStatic | ||
| fun getSettingsFile(fileName: String): File { | ||
| return File(DirectoryInitialization.getUserDirectory() + "/Config/" + fileName + ".ini") | ||
| } | ||
|
|
||
| private fun getCustomGameSettingsFile(gameId: String): File { | ||
| return File( | ||
| DirectoryInitialization.getUserDirectory() + "/GameSettings/" + gameId + ".ini" | ||
| ) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.utils | ||
|
|
||
| import android.content.Intent | ||
| import android.os.Build | ||
| import android.os.Bundle | ||
| import java.io.Serializable | ||
|
|
||
| object SerializableHelper { | ||
| inline fun <reified T : Serializable> Intent.serializable(key: String): T? { | ||
| return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | ||
| getSerializableExtra(key, T::class.java) | ||
| else | ||
| getSerializableExtra(key) as T? | ||
| } | ||
|
|
||
| inline fun <reified T : Serializable> Bundle.serializable(key: String): T? { | ||
| return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) | ||
| getSerializable(key, T::class.java) | ||
| else | ||
| getSerializable(key) as T? | ||
| } | ||
| } |