diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/SelectUserDirectoryDialogFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/SelectUserDirectoryDialogFragment.kt index 038d6d314..634e7b372 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/SelectUserDirectoryDialogFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/SelectUserDirectoryDialogFragment.kt @@ -1,4 +1,4 @@ -// Copyright 2023 Citra Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -22,9 +22,10 @@ class SelectUserDirectoryDialogFragment : DialogFragment() { mainActivity = requireActivity() as MainActivity isCancelable = false + return MaterialAlertDialogBuilder(requireContext()) .setTitle(R.string.select_citra_user_folder) - .setMessage(R.string.cannot_skip_directory_description) + .setMessage(R.string.selecting_user_directory_without_write_permissions) .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> mainActivity?.openCitraDirectory?.launch(null) } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/UpdateUserDirectoryDialogFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/UpdateUserDirectoryDialogFragment.kt new file mode 100644 index 000000000..db8e772e2 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/UpdateUserDirectoryDialogFragment.kt @@ -0,0 +1,113 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra.citra_emu.fragments + +import android.app.Dialog +import android.content.DialogInterface +import android.content.SharedPreferences +import android.net.Uri +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.widget.LinearLayout +import android.widget.RadioButton +import android.widget.RadioGroup +import android.widget.TextView +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.ViewModelProvider +import androidx.preference.PreferenceManager +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.citra.citra_emu.CitraApplication +import org.citra.citra_emu.R +import org.citra.citra_emu.ui.main.MainActivity +import org.citra.citra_emu.utils.CitraDirectoryUtils +import org.citra.citra_emu.utils.DirectoryInitialization +import org.citra.citra_emu.utils.PermissionsHandler +import org.citra.citra_emu.viewmodel.HomeViewModel + +class UpdateUserDirectoryDialogFragment : DialogFragment() { + private lateinit var mainActivity: MainActivity + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + mainActivity = requireActivity() as MainActivity + + isCancelable = false + val preferences: SharedPreferences = + PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) + val ld = preferences.getString("LIME3DS_DIRECTORY","") + val cd = preferences.getString("CITRA_DIRECTORY","") + val dialogView = LayoutInflater.from(requireContext()) + .inflate(R.layout.dialog_select_which_directory, null) + + val radioGroup = dialogView.findViewById(R.id.radioGroup) + + val choices = listOf( + getString(R.string.keep_current_azahar_directory) to Uri.parse(cd).path, + getString(R.string.use_prior_lime3ds_directory) to Uri.parse(ld).path + ) + var selected = -1 // 0 = current, 1 = prior, -1 = no selection + + choices.forEachIndexed { index, (label, subtext) -> + val container = LinearLayout(requireContext()).apply { + orientation = LinearLayout.VERTICAL + setPadding(0, 16, 0, 16) + } + + val radioButton = RadioButton(requireContext()).apply { + text = label + id = View.generateViewId() + } + + val subTextView = TextView(requireContext()).apply { + text = subtext + setPadding(64, 4, 0, 0) // indent for visual hierarchy + setTextAppearance(android.R.style.TextAppearance_Small) + } + + container.addView(radioButton) + container.addView(subTextView) + radioGroup.addView(container) + + // RadioGroup expects RadioButtons directly, so we need to manage selection ourselves + radioButton.setOnClickListener { + selected = index + // Manually uncheck others + for (i in 0 until radioGroup.childCount) { + val child = radioGroup.getChildAt(i) as LinearLayout + val rb = child.getChildAt(0) as RadioButton + rb.isChecked = i == index + } + } + } + + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.select_citra_user_folder) + .setView(dialogView) + .setPositiveButton(android.R.string.ok) { _: DialogInterface, _: Int -> + if (selected == 1) { + PermissionsHandler.setCitraDirectory(ld) + } + if (selected >= 0) { + CitraDirectoryUtils.removeLimeDirectoryPreference() + DirectoryInitialization.resetCitraDirectoryState() + DirectoryInitialization.start() + } + + ViewModelProvider(mainActivity)[HomeViewModel::class.java].setPickingUserDir(false) + ViewModelProvider(mainActivity)[HomeViewModel::class.java].setUserDir(this.requireActivity(),PermissionsHandler.citraDirectory.path!!) + } + .show() + } + + companion object { + const val TAG = "UpdateUserDirectoryDialogFragment" + + fun newInstance(activity: FragmentActivity): UpdateUserDirectoryDialogFragment { + ViewModelProvider(activity)[HomeViewModel::class.java].setPickingUserDir(true) + return UpdateUserDirectoryDialogFragment() + } + } +} diff --git a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt index cfb3006b9..651c325b3 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt @@ -1,4 +1,4 @@ -// Copyright Citra Emulator Project / Lime3DS Emulator Project +// Copyright Citra Emulator Project / Azahar Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. @@ -34,10 +34,8 @@ import androidx.work.OutOfQuotaPolicy import androidx.work.WorkManager import com.google.android.material.color.MaterialColors import com.google.android.material.navigation.NavigationBarView -import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch import org.citra.citra_emu.R -import org.citra.citra_emu.activities.EmulationActivity import org.citra.citra_emu.contracts.OpenFileResultContract import org.citra.citra_emu.databinding.ActivityMainBinding import org.citra.citra_emu.features.settings.model.Settings @@ -45,8 +43,10 @@ import org.citra.citra_emu.features.settings.model.SettingsViewModel import org.citra.citra_emu.features.settings.ui.SettingsActivity import org.citra.citra_emu.features.settings.utils.SettingsFile import org.citra.citra_emu.fragments.SelectUserDirectoryDialogFragment +import org.citra.citra_emu.fragments.UpdateUserDirectoryDialogFragment import org.citra.citra_emu.utils.CiaInstallWorker import org.citra.citra_emu.utils.CitraDirectoryHelper +import org.citra.citra_emu.utils.CitraDirectoryUtils import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.FileBrowserHelper import org.citra.citra_emu.utils.InsetsHelper @@ -66,13 +66,17 @@ class MainActivity : AppCompatActivity(), ThemeProvider { override fun onCreate(savedInstanceState: Bundle?) { val splashScreen = installSplashScreen() + CitraDirectoryUtils.attemptAutomaticUpdateDirectory() splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areCitraDirectoriesReady() && - PermissionsHandler.hasWriteAccess(this) + PermissionsHandler.hasWriteAccess(this) && + !CitraDirectoryUtils.needToUpdateManually() } + if (PermissionsHandler.hasWriteAccess(applicationContext) && - DirectoryInitialization.areCitraDirectoriesReady()) { + DirectoryInitialization.areCitraDirectoriesReady() && + !CitraDirectoryUtils.needToUpdateManually()) { settingsViewModel.settings.loadSettings() } @@ -185,6 +189,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider { ) { SelectUserDirectoryDialogFragment.newInstance(this) .show(supportFragmentManager, SelectUserDirectoryDialogFragment.TAG) + } else if (!firstTimeSetup && !homeViewModel.isPickingUserDir.value && CitraDirectoryUtils.needToUpdateManually()) { + UpdateUserDirectoryDialogFragment.newInstance(this) + .show(supportFragmentManager,UpdateUserDirectoryDialogFragment.TAG) } } diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/CitraDirectoryUtils.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/CitraDirectoryUtils.kt new file mode 100644 index 000000000..1f59ca841 --- /dev/null +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/CitraDirectoryUtils.kt @@ -0,0 +1,46 @@ +// Copyright Citra Emulator Project / Azahar Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.citra.citra_emu.utils + +import android.content.SharedPreferences +import androidx.preference.PreferenceManager +import org.citra.citra_emu.CitraApplication + +object CitraDirectoryUtils { + const val CITRA_DIRECTORY = "CITRA_DIRECTORY" + const val LIME3DS_DIRECTORY = "LIME3DS_DIRECTORY" + val preferences: SharedPreferences = + PreferenceManager.getDefaultSharedPreferences(CitraApplication.appContext) + + fun needToUpdateManually(): Boolean { + val directoryString = preferences.getString(CITRA_DIRECTORY, "") + val limeDirectoryString = preferences.getString(LIME3DS_DIRECTORY,"") + return (directoryString != "" && limeDirectoryString != "" && directoryString != limeDirectoryString) + } + + fun attemptAutomaticUpdateDirectory() { + val directoryString = preferences.getString(CITRA_DIRECTORY, "") + val limeDirectoryString = preferences.getString(LIME3DS_DIRECTORY,"") + if (needToUpdateManually()) { + return; + } + if (directoryString == "" && limeDirectoryString != "") { + // Upgrade from Lime3DS to Azahar + PermissionsHandler.setCitraDirectory(limeDirectoryString) + removeLimeDirectoryPreference() + DirectoryInitialization.resetCitraDirectoryState() + DirectoryInitialization.start() + + } else if (directoryString != "" && directoryString == limeDirectoryString) { + // Both the Lime3DS and Azahar directories are the same, + // so delete the obsolete Lime3DS value. + removeLimeDirectoryPreference() + } + } + + fun removeLimeDirectoryPreference() { + preferences.edit().remove(LIME3DS_DIRECTORY).apply() + } +} diff --git a/src/android/app/src/main/res/layout/dialog_select_which_directory.xml b/src/android/app/src/main/res/layout/dialog_select_which_directory.xml new file mode 100644 index 000000000..c81354917 --- /dev/null +++ b/src/android/app/src/main/res/layout/dialog_select_which_directory.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index c77afdaeb..60034f49e 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -93,9 +93,13 @@ Cancel Select User Folder user data directory with the button below.]]> + You appear to have user directories set for both Lime3DS and Azahar. This is likely because you upgraded to Azahar, and when prompted chose a different user directory than what was being used for Lime3DS.\n\nThis may have resulted in you thinking you lost saves or other settings - we apologize if that happened.\n\nWould you like to go back to using your original Lime3DS user directory, restoring settings and save games from Lime3DS, or keep your current Azahar user directory?\n\nNeither directory will be deleted, regardless of your choice, and you may freely switch between them using the Select User Folder option. + Keep Current Azahar Directory + Use Prior Lime3DS Directory Select You can\'t skip this step This step is required to allow Azahar to work. Please select a directory and then you can continue. + You have lost write permissions on your user data directory, where saves and other information are stored. This can happen after some app or Android updates. Please re-select the directory to regain permissions so you can continue. https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage Theme Settings Configure your theme preferences for Azahar. @@ -822,4 +826,5 @@ MAC Address Regenerate MAC Address This will replace your current MAC address with a new one. It is not recommended to do this if you got the MAC address from your real console using the setup tool. Continue? +