summaryrefslogtreecommitdiff
path: root/Source/Android/app/src/main/java/org
diff options
context:
space:
mode:
authorJosJuice <josjuice@gmail.com>2025-11-22 17:00:03 +0100
committerGitHub <noreply@github.com>2025-11-22 17:00:03 +0100
commitf4f7424a9f059a25ec09b22a280d03fcf0e751eb (patch)
treeda9b25179aad7e305bada139bb2062951e68542d /Source/Android/app/src/main/java/org
parente526c1ffde96e619bc764167f6fc337db74551ed (diff)
parentddced1a070848d935bd1d140eed549a4cae89566 (diff)
Merge pull request #13583 from LillyJadeKatrin/retroachievements-android
Android Support for RetroAchievements
Diffstat (limited to 'Source/Android/app/src/main/java/org')
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/AchievementModel.kt29
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt43
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt2
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt12
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/LoginDialog.kt63
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt1
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt1
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt114
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt3
-rw-r--r--Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt6
10 files changed, 271 insertions, 3 deletions
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/AchievementModel.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/AchievementModel.kt
new file mode 100644
index 0000000000..1ea53ddbec
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/AchievementModel.kt
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.settings.model
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+object AchievementModel {
+ @JvmStatic
+ external fun init()
+
+ suspend fun asyncLogin(password: String): Boolean {
+ return withContext(Dispatchers.IO) {
+ login(password)
+ }
+ }
+
+ @JvmStatic
+ private external fun login(password: String): Boolean
+
+ @JvmStatic
+ external fun logout()
+
+ @JvmStatic
+ external fun isHardcoreModeActive(): Boolean
+
+ @JvmStatic
+ external fun shutdown()
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt
index 0b6945ff13..1f8ef6e94b 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/BooleanSetting.kt
@@ -883,6 +883,42 @@ enum class BooleanSetting(
Settings.SECTION_LOGGER_OPTIONS,
"WriteToFile",
false
+ ),
+ ACHIEVEMENTS_ENABLED(
+ Settings.FILE_ACHIEVEMENTS,
+ Settings.SECTION_ACHIEVEMENTS,
+ "Enabled",
+ false
+ ),
+ ACHIEVEMENTS_HARDCORE_ENABLED(
+ Settings.FILE_ACHIEVEMENTS,
+ Settings.SECTION_ACHIEVEMENTS,
+ "HardcoreEnabled",
+ false
+ ),
+ ACHIEVEMENTS_UNOFFICIAL_ENABLED(
+ Settings.FILE_ACHIEVEMENTS,
+ Settings.SECTION_ACHIEVEMENTS,
+ "UnofficialEnabled",
+ false
+ ),
+ ACHIEVEMENTS_ENCORE_ENABLED(
+ Settings.FILE_ACHIEVEMENTS,
+ Settings.SECTION_ACHIEVEMENTS,
+ "EncoreEnabled",
+ false
+ ),
+ ACHIEVEMENTS_SPECTATOR_ENABLED(
+ Settings.FILE_ACHIEVEMENTS,
+ Settings.SECTION_ACHIEVEMENTS,
+ "SpectatorEnabled",
+ false
+ ),
+ ACHIEVEMENTS_PROGRESS_ENABLED(
+ Settings.FILE_ACHIEVEMENTS,
+ Settings.SECTION_ACHIEVEMENTS,
+ "ProgressEnabled",
+ true
);
override val isOverridden: Boolean
@@ -943,7 +979,12 @@ enum class BooleanSetting(
MAIN_TIME_TRACKING,
MAIN_EMULATE_SKYLANDER_PORTAL,
MAIN_EMULATE_INFINITY_BASE,
- MAIN_EMULATE_WII_SPEAK
+ MAIN_EMULATE_WII_SPEAK,
+ ACHIEVEMENTS_ENABLED,
+ ACHIEVEMENTS_HARDCORE_ENABLED,
+ ACHIEVEMENTS_UNOFFICIAL_ENABLED,
+ ACHIEVEMENTS_ENCORE_ENABLED,
+ ACHIEVEMENTS_SPECTATOR_ENABLED
)
private val NOT_RUNTIME_EDITABLE: Set<BooleanSetting> =
HashSet(listOf(*NOT_RUNTIME_EDITABLE_ARRAY))
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt
index 32ad11b3e1..9f91a4244c 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/Settings.kt
@@ -104,6 +104,7 @@ class Settings : Closeable {
const val FILE_GFX = "GFX"
const val FILE_LOGGER = "Logger"
const val FILE_WIIMOTE = "WiimoteNew"
+ const val FILE_ACHIEVEMENTS = "RetroAchievements"
const val FILE_GAME_SETTINGS_ONLY = "GameSettingsOnly"
const val SECTION_INI_ANDROID = "Android"
const val SECTION_INI_ANDROID_OVERLAY_BUTTONS = "AndroidOverlayButtons"
@@ -122,5 +123,6 @@ class Settings : Closeable {
const val SECTION_EMULATED_USB_DEVICES = "EmulatedUSBDevices"
const val SECTION_STEREOSCOPY = "Stereoscopy"
const val SECTION_ANALYTICS = "Analytics"
+ const val SECTION_ACHIEVEMENTS = "Achievements"
}
}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt
index b150c845e1..29640aa92f 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/model/StringSetting.kt
@@ -80,6 +80,18 @@ enum class StringSetting(
Settings.SECTION_GFX_SETTINGS,
"DriverLibName",
""
+ ),
+ ACHIEVEMENTS_USERNAME(
+ Settings.FILE_ACHIEVEMENTS,
+ Settings.SECTION_ACHIEVEMENTS,
+ "Username",
+ ""
+ ),
+ ACHIEVEMENTS_API_TOKEN(
+ Settings.FILE_ACHIEVEMENTS,
+ Settings.SECTION_ACHIEVEMENTS,
+ "ApiToken",
+ ""
);
override val isOverridden: Boolean
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/LoginDialog.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/LoginDialog.kt
new file mode 100644
index 0000000000..9f1d3efd5f
--- /dev/null
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/LoginDialog.kt
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.dolphinemu.dolphinemu.features.settings.ui
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.DialogFragment
+import androidx.lifecycle.lifecycleScope
+import kotlinx.coroutines.launch
+import org.dolphinemu.dolphinemu.databinding.DialogLoginBinding
+import org.dolphinemu.dolphinemu.dialogs.AlertMessage
+import org.dolphinemu.dolphinemu.features.settings.model.AchievementModel.asyncLogin
+import org.dolphinemu.dolphinemu.features.settings.model.NativeConfig
+import org.dolphinemu.dolphinemu.features.settings.model.StringSetting
+
+class LoginDialog(val parent: SettingsFragmentPresenter) : DialogFragment() {
+ private var _binding: DialogLoginBinding? = null
+ private val binding get() = _binding!!
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = DialogLoginBinding.inflate(inflater, container, false)
+ binding.usernameInput.setText(
+ StringSetting.ACHIEVEMENTS_USERNAME.string
+ )
+ return binding.getRoot()
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ binding.buttonCancel.setOnClickListener { onCancelClicked() }
+ binding.buttonLogin.setOnClickListener { onLoginClicked() }
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ private fun onCancelClicked() {
+ dismiss()
+ }
+
+ private fun onLoginClicked() {
+ binding.loginFailed.visibility = View.GONE
+ binding.loginInProgress.visibility = View.VISIBLE
+ StringSetting.ACHIEVEMENTS_USERNAME.setString(NativeConfig.LAYER_BASE_OR_CURRENT,
+ binding.usernameInput.text.toString())
+ lifecycleScope.launch {
+ if (asyncLogin(binding.passwordInput.text.toString())) {
+ parent.loadSettingsList()
+ dismiss()
+ } else {
+ binding.loginInProgress.visibility = View.GONE
+ binding.loginFailed.visibility = View.VISIBLE
+ }
+ }
+ }
+}
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt
index 0bfcb96d6c..74556b07d9 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/MenuTag.kt
@@ -14,6 +14,7 @@ enum class MenuTag {
CONFIG_GAME_CUBE("config_gamecube"),
CONFIG_SERIALPORT1("config_serialport1"),
CONFIG_WII("config_wii"),
+ CONFIG_ACHIEVEMENTS("config_achievements"),
CONFIG_ADVANCED("config_advanced"),
CONFIG_LOG("config_log"),
DEBUG("debug"),
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
index 2992dd3211..1289d30f61 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragment.kt
@@ -268,6 +268,7 @@ class SettingsFragment : Fragment(), SettingsFragmentView {
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_ACHIEVEMENTS] = R.string.achievements_submenu
titles[MenuTag.CONFIG_ADVANCED] = R.string.advanced_submenu
titles[MenuTag.DEBUG] = R.string.debug_submenu
titles[MenuTag.GRAPHICS] = R.string.graphics_settings
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
index a47901debb..a4327b9645 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/SettingsFragmentPresenter.kt
@@ -31,6 +31,7 @@ import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialog
import org.dolphinemu.dolphinemu.features.input.ui.ProfileDialogPresenter
import org.dolphinemu.dolphinemu.features.settings.model.*
import org.dolphinemu.dolphinemu.features.settings.model.view.*
+import org.dolphinemu.dolphinemu.features.settings.model.AchievementModel.logout
import org.dolphinemu.dolphinemu.model.GpuDriverMetadata
import org.dolphinemu.dolphinemu.utils.*
import kotlin.collections.ArrayList
@@ -98,7 +99,7 @@ class SettingsFragmentPresenter(
}
}
- private fun loadSettingsList() {
+ fun loadSettingsList() {
val sl = ArrayList<SettingsItem>()
when (menuTag) {
MenuTag.SETTINGS -> addTopLevelSettings(sl)
@@ -109,6 +110,7 @@ class SettingsFragmentPresenter(
MenuTag.CONFIG_PATHS -> addPathsSettings(sl)
MenuTag.CONFIG_GAME_CUBE -> addGameCubeSettings(sl)
MenuTag.CONFIG_WII -> addWiiSettings(sl)
+ MenuTag.CONFIG_ACHIEVEMENTS -> addAchievementSettings(sl);
MenuTag.CONFIG_ADVANCED -> addAdvancedSettings(sl)
MenuTag.GRAPHICS -> addGraphicsSettings(sl)
MenuTag.CONFIG_SERIALPORT1 -> addSerialPortSubSettings(sl, serialPort1Type)
@@ -198,6 +200,7 @@ class SettingsFragmentPresenter(
sl.add(SubmenuSetting(context, R.string.paths_submenu, MenuTag.CONFIG_PATHS))
sl.add(SubmenuSetting(context, R.string.gamecube_submenu, MenuTag.CONFIG_GAME_CUBE))
sl.add(SubmenuSetting(context, R.string.wii_submenu, MenuTag.CONFIG_WII))
+ sl.add(SubmenuSetting(context, R.string.achievements_submenu, MenuTag.CONFIG_ACHIEVEMENTS))
sl.add(SubmenuSetting(context, R.string.advanced_submenu, MenuTag.CONFIG_ADVANCED))
sl.add(SubmenuSetting(context, R.string.log_submenu, MenuTag.CONFIG_LOG))
sl.add(SubmenuSetting(context, R.string.debug_submenu, MenuTag.DEBUG))
@@ -246,7 +249,7 @@ class SettingsFragmentPresenter(
FloatSetting.MAIN_EMULATION_SPEED,
R.string.speed_limit,
0,
- 0f,
+ if (AchievementModel.isHardcoreModeActive()) 100f else 0f,
200f,
"%",
1f,
@@ -912,6 +915,113 @@ class SettingsFragmentPresenter(
)
}
+ private fun addAchievementSettings(sl: ArrayList<SettingsItem>) {
+ val achievementsEnabledSetting: AbstractBooleanSetting = object : AbstractBooleanSetting {
+ override val boolean: Boolean
+ get() = BooleanSetting.ACHIEVEMENTS_ENABLED.boolean
+
+ override fun setBoolean(settings: Settings, newValue: Boolean) {
+ BooleanSetting.ACHIEVEMENTS_ENABLED.setBoolean(settings, newValue)
+ if (newValue)
+ AchievementModel.init()
+ else
+ AchievementModel.shutdown()
+ loadSettingsList()
+ }
+
+ override val isOverridden: Boolean
+ get() = BooleanSetting.ACHIEVEMENTS_ENABLED.isOverridden
+
+ override val isRuntimeEditable: Boolean
+ get() = BooleanSetting.ACHIEVEMENTS_ENABLED.isRuntimeEditable
+
+ override fun delete(settings: Settings): Boolean {
+ val result = BooleanSetting.ACHIEVEMENTS_ENABLED.delete(settings)
+ AchievementModel.shutdown()
+ loadSettingsList()
+ return result
+ }
+ }
+
+ sl.add(
+ SwitchSetting(
+ context,
+ achievementsEnabledSetting,
+ R.string.achievements_enabled,
+ 0
+ )
+ )
+ if (BooleanSetting.ACHIEVEMENTS_ENABLED.boolean) {
+ if (StringSetting.ACHIEVEMENTS_API_TOKEN.string == "") {
+ sl.add(
+ RunRunnable(
+ context,
+ R.string.achievements_login,
+ 0,
+ 0,
+ 0,
+ false
+ ) {
+ fragmentView.showDialogFragment(LoginDialog(this))
+ loadSettingsList()
+ })
+ } else {
+ sl.add(
+ RunRunnable(
+ context,
+ R.string.achievements_logout,
+ 0,
+ 0,
+ 0,
+ false
+ ) {
+ logout()
+ loadSettingsList()
+ })
+ }
+ sl.add(
+ SwitchSetting(
+ context,
+ BooleanSetting.ACHIEVEMENTS_HARDCORE_ENABLED,
+ R.string.achievements_hardcore_enabled,
+ 0
+ )
+ )
+ sl.add(
+ SwitchSetting(
+ context,
+ BooleanSetting.ACHIEVEMENTS_UNOFFICIAL_ENABLED,
+ R.string.achievements_unofficial_enabled,
+ 0
+ )
+ )
+ sl.add(
+ SwitchSetting(
+ context,
+ BooleanSetting.ACHIEVEMENTS_ENCORE_ENABLED,
+ R.string.achievements_encore_enabled,
+ 0
+ )
+ )
+ sl.add(
+ SwitchSetting(
+ context,
+ BooleanSetting.ACHIEVEMENTS_SPECTATOR_ENABLED,
+ R.string.achievements_spectator_enabled,
+ 0
+ )
+ )
+ sl.add(
+ SwitchSetting(
+ context,
+ BooleanSetting.ACHIEVEMENTS_PROGRESS_ENABLED,
+ R.string.achievements_progress_enabled,
+ 0
+ )
+ )
+ }
+ }
+
private fun addAdvancedSettings(sl: ArrayList<SettingsItem>) {
val SYNC_GPU_NEVER = 0
val SYNC_GPU_ON_IDLE_SKIP = 1
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
index ddcf1d39b5..2907b948b6 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/features/settings/ui/viewholder/SwitchSettingViewHolder.kt
@@ -32,6 +32,9 @@ class SwitchSettingViewHolder(
binding.textSettingName.text = item.name
binding.textSettingDescription.text = item.description
+ // Make sure we don't trigger any listener set earlier
+ binding.settingSwitch.setOnCheckedChangeListener(null)
+
binding.settingSwitch.isChecked = setting.isChecked
binding.settingSwitch.isEnabled = setting.isEditable
diff --git a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt
index b668e08da8..6bf9c7c147 100644
--- a/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt
+++ b/Source/Android/app/src/main/java/org/dolphinemu/dolphinemu/fragments/MenuFragment.kt
@@ -19,6 +19,7 @@ import org.dolphinemu.dolphinemu.NativeLibrary
import org.dolphinemu.dolphinemu.R
import org.dolphinemu.dolphinemu.activities.EmulationActivity
import org.dolphinemu.dolphinemu.databinding.FragmentIngameMenuBinding
+import org.dolphinemu.dolphinemu.features.settings.model.AchievementModel
import org.dolphinemu.dolphinemu.features.settings.model.BooleanSetting
import org.dolphinemu.dolphinemu.features.settings.model.IntSetting
import org.dolphinemu.dolphinemu.utils.InsetsHelper
@@ -114,11 +115,16 @@ class MenuFragment : Fragment(), View.OnClickListener {
override fun onResume() {
super.onResume()
val savestatesEnabled = BooleanSetting.MAIN_ENABLE_SAVESTATES.boolean
+ val hardcoreEnabled = AchievementModel.isHardcoreModeActive()
val savestateVisibility = if (savestatesEnabled) View.VISIBLE else View.GONE
binding.menuQuicksave.visibility = savestateVisibility
binding.menuQuickload.visibility = savestateVisibility
binding.menuEmulationSaveRoot.visibility = savestateVisibility
binding.menuEmulationLoadRoot.visibility = savestateVisibility
+ // While technically the option is still enabled, AchievementManager
+ // will block the load and send a message to the screen.
+ binding.menuQuickload.paint.isStrikeThruText = hardcoreEnabled
+ binding.menuEmulationLoadRoot.paint.isStrikeThruText = hardcoreEnabled
}
override fun onDestroyView() {