From ac9cc53ba08e2f18901e63628e9a46031d09957c Mon Sep 17 00:00:00 2001 From: Kleidis <167202775+kleidis@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:29:09 +0100 Subject: [PATCH 1/6] Refactor SetupFragment to support multiple buttons in one page --- .../citra_emu/fragments/SetupFragment.kt | 442 +++++++++++------- .../src/main/res/drawable/ic_permission.xml | 9 + .../app/src/main/res/values/strings.xml | 4 + 3 files changed, 281 insertions(+), 174 deletions(-) create mode 100644 src/android/app/src/main/res/drawable/ic_permission.xml diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/SetupFragment.kt index d7a3b0f2b..2b21d82e8 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/SetupFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/SetupFragment.kt @@ -35,9 +35,11 @@ import org.citra.citra_emu.R import org.citra.citra_emu.adapters.SetupAdapter import org.citra.citra_emu.databinding.FragmentSetupBinding import org.citra.citra_emu.features.settings.model.Settings +import org.citra.citra_emu.model.ButtonState +import org.citra.citra_emu.model.PageButton +import org.citra.citra_emu.model.PageState import org.citra.citra_emu.model.SetupCallback import org.citra.citra_emu.model.SetupPage -import org.citra.citra_emu.model.StepState import org.citra.citra_emu.ui.main.MainActivity import org.citra.citra_emu.utils.CitraDirectoryHelper import org.citra.citra_emu.utils.GameHelper @@ -113,150 +115,194 @@ class SetupFragment : Fragment() { 0, true, R.string.get_started, - { pageForward() } - ) - ) - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - add( - SetupPage( - R.drawable.ic_notification, - R.string.notifications, - R.string.notifications_description, - 0, - false, - R.string.give_permission, - { - notificationCallback = it - permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) - }, - false, - true, - { - if (NotificationManagerCompat.from(requireContext()) - .areNotificationsEnabled() - ) { - StepState.STEP_COMPLETE - } else { - StepState.STEP_INCOMPLETE - } - }, - R.string.notification_warning, - R.string.notification_warning_description, - 0 - ) - ) - } - - add( - SetupPage( - R.drawable.ic_microphone, - R.string.microphone_permission, - R.string.microphone_permission_description, - 0, - false, - R.string.give_permission, - { - microphoneCallback = it - permissionLauncher.launch(Manifest.permission.RECORD_AUDIO) - }, - false, - false, - { - if ( - ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.RECORD_AUDIO - ) == PackageManager.PERMISSION_GRANTED - ) { - StepState.STEP_COMPLETE - } else { - StepState.STEP_INCOMPLETE - } + pageButton = mutableListOf().apply { + add( + PageButton( + R.drawable.ic_arrow_forward, + R.string.get_started, + 0, + buttonAction = { + pageForward() + }, + buttonState = { + ButtonState.BUTTON_ACTION_UNDEFINED + } + ) + ) } ) ) + add( SetupPage( - R.drawable.ic_camera, - R.string.camera_permission, - R.string.camera_permission_description, + R.drawable.ic_permission, + R.string.permissions, + R.string.permissions_description, 0, false, - R.string.give_permission, - { - cameraCallback = it - permissionLauncher.launch(Manifest.permission.CAMERA) - }, - false, - false, - { - if ( - ContextCompat.checkSelfPermission( - requireContext(), - Manifest.permission.CAMERA - ) == PackageManager.PERMISSION_GRANTED - ) { - StepState.STEP_COMPLETE - } else { - StepState.STEP_INCOMPLETE + 0, + pageButton = mutableListOf().apply { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + add( + PageButton( + R.drawable.ic_notification, + R.string.notifications, + R.string.notifications_description, + buttonAction = { + pageButtonCallback = it + permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) + }, + buttonState = { + if (NotificationManagerCompat.from(requireContext()) + .areNotificationsEnabled() + ) { + ButtonState.BUTTON_ACTION_COMPLETE + } else { + ButtonState.BUTTON_ACTION_INCOMPLETE + } + }, + isUnskippable = false, + hasWarning = true, + R.string.notification_warning, + R.string.notification_warning_description, + ) + ) } - } - ) - ) - add( - SetupPage( - R.drawable.ic_home, - R.string.select_citra_user_folder, - R.string.select_citra_user_folder_description, - 0, - true, - R.string.select, - { - userDirCallback = it - openCitraDirectory.launch(null) - }, - true, - true, - { - if (PermissionsHandler.hasWriteAccess(requireContext())) { - StepState.STEP_COMPLETE - } else { - StepState.STEP_INCOMPLETE - } - }, - R.string.cannot_skip, - R.string.cannot_skip_directory_description, - R.string.cannot_skip_directory_help - ) - ) - add( - SetupPage( - R.drawable.ic_controller, - R.string.games, - R.string.games_description, - 0, - true, - R.string.select, - { - gamesDirCallback = it - getGamesDirectory.launch( - Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data + add( + PageButton( + R.drawable.ic_microphone, + R.string.microphone_permission, + R.string.microphone_permission_description, + buttonAction = { + pageButtonCallback = it + permissionLauncher.launch(Manifest.permission.RECORD_AUDIO) + }, + buttonState = { + if (ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.RECORD_AUDIO + ) == PackageManager.PERMISSION_GRANTED + ) { + ButtonState.BUTTON_ACTION_COMPLETE + } else { + ButtonState.BUTTON_ACTION_INCOMPLETE + } + }, + ) + ) + add( + PageButton( + R.drawable.ic_camera, + R.string.camera_permission, + R.string.camera_permission_description, + buttonAction = { + pageButtonCallback = it + permissionLauncher.launch(Manifest.permission.CAMERA) + }, + buttonState = { + if (ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED + ) { + ButtonState.BUTTON_ACTION_COMPLETE + } else { + ButtonState.BUTTON_ACTION_INCOMPLETE + } + }, + ) ) }, - false, + ) { + if ( + ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.RECORD_AUDIO + ) == PackageManager.PERMISSION_GRANTED && + ContextCompat.checkSelfPermission( + requireContext(), + Manifest.permission.CAMERA + ) == PackageManager.PERMISSION_GRANTED && + NotificationManagerCompat.from(requireContext()) + .areNotificationsEnabled() + ) { + PageState.PAGE_STEPS_COMPLETE + } else { + PageState.PAGE_STEPS_INCOMPLETE + } + } + ) + add( + SetupPage( + R.drawable.ic_folder, + R.string.select_emulalator_data_folder, + R.string.select_emulalator_data_folder_description, + 0, true, - { - if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) { - StepState.STEP_COMPLETE - } else { - StepState.STEP_INCOMPLETE - } + R.string.select, + pageButton = mutableListOf().apply { + add( + PageButton( + R.drawable.ic_home, + R.string.select_citra_user_folder, + R.string.select_citra_user_folder_description, + buttonAction = { + pageButtonCallback = it + openCitraDirectory.launch(null) + }, + buttonState = { + if (PermissionsHandler.hasWriteAccess(requireContext())) { + ButtonState.BUTTON_ACTION_COMPLETE + } else { + ButtonState.BUTTON_ACTION_INCOMPLETE + } + }, + isUnskippable = true, + hasWarning = false, + R.string.cannot_skip, + R.string.cannot_skip_directory_description, + R.string.cannot_skip_directory_help + + ) + ) + add( + PageButton( + R.drawable.ic_controller, + R.string.games, + R.string.games_description, + buttonAction = { + pageButtonCallback = it + getGamesDirectory.launch( + Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data + ) + }, + buttonState = { + if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) { + ButtonState.BUTTON_ACTION_COMPLETE + } else { + ButtonState.BUTTON_ACTION_INCOMPLETE + } + }, + isUnskippable = false, + hasWarning = true, + R.string.add_games_warning, + R.string.add_games_warning_description, + ) + ) + }, - R.string.add_games_warning, - R.string.add_games_warning_description, - R.string.add_games_warning_help - ) + ) { + if (PermissionsHandler.hasWriteAccess(requireContext()) && preferences.getString( + GameHelper.KEY_GAME_PATH, + "" + )!!.isNotEmpty() + ) { + PageState.PAGE_STEPS_COMPLETE + + } else { + PageState.PAGE_STEPS_INCOMPLETE + } + } ) add( SetupPage( @@ -266,7 +312,22 @@ class SetupFragment : Fragment() { R.drawable.ic_arrow_forward, false, R.string.text_continue, - { finishSetup() } + pageButton = mutableListOf().apply { + add( + PageButton( + R.drawable.ic_arrow_forward, + R.string.text_continue, + 0, + buttonAction = { + finishSetup() + }, + buttonState = { + ButtonState.BUTTON_ACTION_UNDEFINED + } + + ) + ) + } ) ) } @@ -303,35 +364,47 @@ class SetupFragment : Fragment() { val index = binding.viewPager2.currentItem val currentPage = pages[index] - // Checks if the user has completed the task on the current page - if (currentPage.hasWarning || currentPage.isUnskippable) { - val stepState = currentPage.stepCompleted.invoke() - if (stepState == StepState.STEP_COMPLETE || - stepState == StepState.STEP_UNDEFINED - ) { - pageForward() - return@setOnClickListener - } + // This allows multiple sets of warning messages to be displayed om the same dialog if necessary + val warningMessages = + mutableListOf>() // title, description, helpLink - if (currentPage.isUnskippable) { - MessageDialogFragment.newInstance( - currentPage.warningTitleId, - currentPage.warningDescriptionId, - currentPage.warningHelpLinkId - ).show(childFragmentManager, MessageDialogFragment.TAG) - return@setOnClickListener - } + currentPage.pageButton?.forEach { button -> + if (button.hasWarning || button.isUnskippable) { + val buttonState = button.buttonState() + if (buttonState == ButtonState.BUTTON_ACTION_COMPLETE) { + return@forEach + } - if (!hasBeenWarned[index]) { - SetupWarningDialogFragment.newInstance( - currentPage.warningTitleId, - currentPage.warningDescriptionId, - currentPage.warningHelpLinkId, - index - ).show(childFragmentManager, SetupWarningDialogFragment.TAG) - return@setOnClickListener + if (button.isUnskippable) { + MessageDialogFragment.newInstance( + button.warningTitleId, + button.warningDescriptionId, + button.warningHelpLinkId + ).show(childFragmentManager, MessageDialogFragment.TAG) + return@setOnClickListener + } + + if (!hasBeenWarned[index]) { + warningMessages.add( + Triple( + button.warningTitleId, + button.warningDescriptionId, + button.warningHelpLinkId + ) + ) + } } } + + if (warningMessages.isNotEmpty()) { + SetupWarningDialogFragment.newInstance( + warningMessages.map { it.first }.toIntArray(), + warningMessages.map { it.second }.toIntArray(), + warningMessages.map { it.third }.toIntArray(), + index + ).show(childFragmentManager, SetupWarningDialogFragment.TAG) + return@setOnClickListener + } pageForward() } binding.buttonBack.setOnClickListener { pageBackward() } @@ -352,6 +425,25 @@ class SetupFragment : Fragment() { } setInsets() + + binding.buttonBack.setOnClickListener { pageBackward() } + + if (savedInstanceState != null) { + val nextIsVisible = savedInstanceState.getBoolean(KEY_NEXT_VISIBILITY) + val backIsVisible = savedInstanceState.getBoolean(KEY_BACK_VISIBILITY) + hasBeenWarned = savedInstanceState.getBooleanArray(KEY_HAS_BEEN_WARNED)!! + + if (nextIsVisible) { + binding.buttonNext.visibility = View.VISIBLE + } + if (backIsVisible) { + binding.buttonBack.visibility = View.VISIBLE + } + } else { + hasBeenWarned = BooleanArray(pages.size) + } + + setInsets() } override fun onSaveInstanceState(outState: Bundle) { @@ -366,19 +458,24 @@ class SetupFragment : Fragment() { _binding = null } - private lateinit var notificationCallback: SetupCallback - private lateinit var microphoneCallback: SetupCallback - private lateinit var cameraCallback: SetupCallback + private lateinit var pageButtonCallback: SetupCallback + private val checkForButtonState: () -> Unit = { + val page = pages[binding.viewPager2.currentItem] + page.pageButton?.forEach { + if (it.buttonState() == ButtonState.BUTTON_ACTION_COMPLETE) { + pageButtonCallback.onStepCompleted(it.titleId, pageFullyCompleted = false) + } + + if (page.pageSteps() == PageState.PAGE_STEPS_COMPLETE) { + pageButtonCallback.onStepCompleted(0, pageFullyCompleted = true) + } + } + } private val permissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted -> if (isGranted) { - val page = pages[binding.viewPager2.currentItem] - when (page.titleId) { - R.string.notifications -> notificationCallback.onStepCompleted() - R.string.microphone_permission -> microphoneCallback.onStepCompleted() - R.string.camera_permission -> cameraCallback.onStepCompleted() - } + checkForButtonState.invoke() return@registerForActivityResult } @@ -394,8 +491,6 @@ class SetupFragment : Fragment() { .show() } - private lateinit var userDirCallback: SetupCallback - private val openCitraDirectory = registerForActivityResult( ActivityResultContracts.OpenDocumentTree() ) { result: Uri? -> @@ -403,11 +498,9 @@ class SetupFragment : Fragment() { return@registerForActivityResult } - CitraDirectoryHelper(requireActivity()).showCitraDirectoryDialog(result, userDirCallback) + CitraDirectoryHelper(requireActivity()).showCitraDirectoryDialog(result, pageButtonCallback, checkForButtonState) } - private lateinit var gamesDirCallback: SetupCallback - private val getGamesDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> if (result == null) { @@ -427,7 +520,8 @@ class SetupFragment : Fragment() { homeViewModel.setGamesDir(requireActivity(), result.path!!) - gamesDirCallback.onStepCompleted() + checkForButtonState.invoke() + } private fun finishSetup() { diff --git a/src/android/app/src/main/res/drawable/ic_permission.xml b/src/android/app/src/main/res/drawable/ic_permission.xml new file mode 100644 index 000000000..b28cb1384 --- /dev/null +++ b/src/android/app/src/main/res/drawable/ic_permission.xml @@ -0,0 +1,9 @@ + + + \ 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 5495a2a23..9665719e5 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -83,6 +83,10 @@ Skip selecting applications folder? Software won\'t be displayed in the Applications list if a folder isn\'t selected. https://web.archive.org/web/20240304210021/https://citra-emu.org/wiki/dumping-game-cartridges/ + Permissions + Data Folders + Select data folders (User folder is required) + Grant optional permissions to use specific features of the emulator Help Skip Cancel From edce0c4ab09f82b58b8f9b2c01a850c70814c864 Mon Sep 17 00:00:00 2001 From: Kleidis <167202775+kleidis@users.noreply.github.com> Date: Wed, 12 Feb 2025 19:19:16 +0100 Subject: [PATCH 2/6] Add new `PageButton` data class --- .../org/citra/citra_emu/model/SetupPage.kt | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/model/SetupPage.kt b/src/android/app/src/main/java/org/citra/citra_emu/model/SetupPage.kt index c45f05cf8..55395a19c 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/model/SetupPage.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/model/SetupPage.kt @@ -11,21 +11,35 @@ data class SetupPage( val buttonIconId: Int, val leftAlignedIcon: Boolean, val buttonTextId: Int, + val pageButton: List? = null, + val pageSteps: () -> PageState = { PageState.PAGE_STEPS_UNDEFINED }, + ) + +data class PageButton( + val iconId: Int, + val titleId: Int, + val descriptionId: Int, val buttonAction: (callback: SetupCallback) -> Unit, + val buttonState: () -> ButtonState = { ButtonState.BUTTON_ACTION_UNDEFINED }, val isUnskippable: Boolean = false, val hasWarning: Boolean = false, - val stepCompleted: () -> StepState = { StepState.STEP_UNDEFINED }, val warningTitleId: Int = 0, val warningDescriptionId: Int = 0, val warningHelpLinkId: Int = 0 ) interface SetupCallback { - fun onStepCompleted() + fun onStepCompleted(pageButtonId : Int, pageFullyCompleted: Boolean) } -enum class StepState { - STEP_COMPLETE, - STEP_INCOMPLETE, - STEP_UNDEFINED +enum class PageState { + PAGE_STEPS_COMPLETE, + PAGE_STEPS_INCOMPLETE, + PAGE_STEPS_UNDEFINED +} + +enum class ButtonState { + BUTTON_ACTION_COMPLETE, + BUTTON_ACTION_INCOMPLETE, + BUTTON_ACTION_UNDEFINED } From 6f56108170976573a99b29a50a54606c7bac50cf Mon Sep 17 00:00:00 2001 From: Kleidis <167202775+kleidis@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:25:47 +0100 Subject: [PATCH 3/6] Programmatic button creation && button disabling in setUpAdapter --- .../citra/citra_emu/adapters/SetupAdapter.kt | 80 +++++++++++++------ .../app/src/main/res/layout/page_button.xml | 8 ++ .../app/src/main/res/layout/page_setup.xml | 36 ++++----- 3 files changed, 79 insertions(+), 45 deletions(-) create mode 100644 src/android/app/src/main/res/layout/page_button.xml diff --git a/src/android/app/src/main/java/org/citra/citra_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/citra/citra_emu/adapters/SetupAdapter.kt index b6917b072..94cac65ac 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/adapters/SetupAdapter.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/adapters/SetupAdapter.kt @@ -4,6 +4,7 @@ package org.citra.citra_emu.adapters +import android.content.res.ColorStateList import android.text.Html import android.text.method.LinkMovementMethod import android.view.LayoutInflater @@ -14,10 +15,12 @@ import androidx.core.content.res.ResourcesCompat import androidx.recyclerview.widget.RecyclerView import com.google.android.material.button.MaterialButton import org.citra.citra_emu.databinding.PageSetupBinding +import org.citra.citra_emu.model.PageState import org.citra.citra_emu.model.SetupCallback import org.citra.citra_emu.model.SetupPage -import org.citra.citra_emu.model.StepState import org.citra.citra_emu.utils.ViewUtils +import org.citra.citra_emu.R +import org.citra.citra_emu.model.ButtonState class SetupAdapter(val activity: AppCompatActivity, val pages: List) : RecyclerView.Adapter() { @@ -42,8 +45,40 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List) fun bind(page: SetupPage) { this.page = page - if (page.stepCompleted.invoke() == StepState.STEP_COMPLETE) { - onStepCompleted() + if (page.pageSteps.invoke() == PageState.PAGE_STEPS_COMPLETE) { + onStepCompleted(0, pageFullyCompleted = true) + } + + if (page.pageButton != null && page.pageSteps.invoke() != PageState.PAGE_STEPS_COMPLETE) { + for (pageButton in page.pageButton) { + val pageButtonView = LayoutInflater.from(activity) + .inflate( + R.layout.page_button, + binding.pegeButtonContainer, + false + ) as MaterialButton + + pageButtonView.apply { + id = pageButton.titleId + icon = ResourcesCompat.getDrawable( + activity.resources, + pageButton.iconId, + activity.theme + ) + text = activity.resources.getString(pageButton.titleId) + } + + pageButtonView.setOnClickListener { + pageButton.buttonAction.invoke(this@SetupPageViewHolder) + } + + binding.pegeButtonContainer.addView(pageButtonView) + + // Disable buton add if its already completed + if (pageButton.buttonState.invoke() == ButtonState.BUTTON_ACTION_COMPLETE) { + onStepCompleted(pageButton.titleId, pageFullyCompleted = false) + } + } } binding.icon.setImageDrawable( @@ -57,31 +92,26 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List) binding.textDescription.text = Html.fromHtml(activity.resources.getString(page.descriptionId), 0) binding.textDescription.movementMethod = LinkMovementMethod.getInstance() - - binding.buttonAction.apply { - text = activity.resources.getString(page.buttonTextId) - if (page.buttonIconId != 0) { - icon = ResourcesCompat.getDrawable( - activity.resources, - page.buttonIconId, - activity.theme - ) - } - iconGravity = - if (page.leftAlignedIcon) { - MaterialButton.ICON_GRAVITY_START - } else { - MaterialButton.ICON_GRAVITY_END - } - setOnClickListener { - page.buttonAction.invoke(this@SetupPageViewHolder) - } - } } - override fun onStepCompleted() { - ViewUtils.hideView(binding.buttonAction, 200) + override fun onStepCompleted(pageButtonId: Int, pageFullyCompleted: Boolean) { + val button = binding.pegeButtonContainer.findViewById(pageButtonId) + + if (pageFullyCompleted) { + ViewUtils.hideView(binding.pegeButtonContainer, 200) ViewUtils.showView(binding.textConfirmation, 200) } + + if (button != null) { + button.isEnabled = false + button.animate() + .alpha(0.38f) + .setDuration(200) + .start() + button.setTextColor(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled)) + button.iconTint = + ColorStateList.valueOf(button.context.getColor(com.google.android.material.R.color.material_on_surface_disabled)) + } + } } } diff --git a/src/android/app/src/main/res/layout/page_button.xml b/src/android/app/src/main/res/layout/page_button.xml new file mode 100644 index 000000000..36e176ab0 --- /dev/null +++ b/src/android/app/src/main/res/layout/page_button.xml @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/src/android/app/src/main/res/layout/page_setup.xml b/src/android/app/src/main/res/layout/page_setup.xml index 535abcf02..cd667bc27 100644 --- a/src/android/app/src/main/res/layout/page_setup.xml +++ b/src/android/app/src/main/res/layout/page_setup.xml @@ -11,7 +11,6 @@ android:layout_width="0dp" android:layout_height="0dp" android:layout_marginTop="64dp" - android:layout_marginBottom="32dp" app:layout_constraintBottom_toTopOf="@+id/text_title" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHeight_max="220dp" @@ -43,11 +42,11 @@ android:id="@+id/text_description" style="@style/TextAppearance.Material3.TitleLarge" android:layout_width="0dp" - android:layout_height="0dp" + android:layout_height="78dp" android:textAlignment="center" android:textSize="20sp" android:paddingHorizontal="16dp" - app:layout_constraintBottom_toTopOf="@+id/button_action" + app:layout_constraintBottom_toTopOf="@+id/text_confirmation" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/text_title" @@ -58,15 +57,15 @@ - + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> From 33e11bf19c7eb1dd24f2613c892ee54474146d58 Mon Sep 17 00:00:00 2001 From: Kleidis <167202775+kleidis@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:27:01 +0100 Subject: [PATCH 4/6] Refactor SetupWarningDialogFragment to support multiple titles, descriptions, and help links --- .../fragments/SetupWarningDialogFragment.kt | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/SetupWarningDialogFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/SetupWarningDialogFragment.kt index 20a1e0baf..28b005522 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/SetupWarningDialogFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/SetupWarningDialogFragment.kt @@ -14,18 +14,18 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.citra.citra_emu.R class SetupWarningDialogFragment : DialogFragment() { - private var titleId: Int = 0 - private var descriptionId: Int = 0 - private var helpLinkId: Int = 0 + private var titleIds: IntArray = intArrayOf() + private var descriptionIds: IntArray = intArrayOf() + private var helpLinkIds: IntArray = intArrayOf() private var page: Int = 0 private lateinit var setupFragment: SetupFragment override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - titleId = requireArguments().getInt(TITLE) - descriptionId = requireArguments().getInt(DESCRIPTION) - helpLinkId = requireArguments().getInt(HELP_LINK) + titleIds = requireArguments().getIntArray(TITLES) ?: intArrayOf() + descriptionIds = requireArguments().getIntArray(DESCRIPTIONS) ?: intArrayOf() + helpLinkIds = requireArguments().getIntArray(HELP_LINKS) ?: intArrayOf() page = requireArguments().getInt(PAGE) setupFragment = requireParentFragment() as SetupFragment @@ -39,16 +39,23 @@ class SetupWarningDialogFragment : DialogFragment() { } .setNegativeButton(R.string.warning_cancel, null) - if (titleId != 0) { - builder.setTitle(titleId) - } else { - builder.setTitle("") + // Message builder to build multiple strings into one + val messageBuilder = StringBuilder() + for (i in titleIds.indices) { + if (titleIds[i] != 0) { + messageBuilder.append(getString(titleIds[i])).append("\n\n") + } + if (descriptionIds[i] != 0) { + messageBuilder.append(getString(descriptionIds[i])).append("\n\n") + } } - if (descriptionId != 0) { - builder.setMessage(descriptionId) - } - if (helpLinkId != 0) { + + builder.setTitle("Warning") + builder.setMessage(messageBuilder.toString().trim()) + + if (helpLinkIds.any { it != 0 }) { builder.setNeutralButton(R.string.warning_help) { _: DialogInterface?, _: Int -> + val helpLinkId = helpLinkIds.first { it != 0 } val helpLink = resources.getString(helpLinkId) val intent = Intent(Intent.ACTION_VIEW, Uri.parse(helpLink)) startActivity(intent) @@ -61,23 +68,23 @@ class SetupWarningDialogFragment : DialogFragment() { companion object { const val TAG = "SetupWarningDialogFragment" - private const val TITLE = "Title" - private const val DESCRIPTION = "Description" - private const val HELP_LINK = "HelpLink" + private const val TITLES = "Titles" + private const val DESCRIPTIONS = "Descriptions" + private const val HELP_LINKS = "HelpLinks" private const val PAGE = "Page" fun newInstance( - titleId: Int, - descriptionId: Int, - helpLinkId: Int, + titleIds: IntArray, + descriptionIds: IntArray, + helpLinkIds: IntArray, page: Int ): SetupWarningDialogFragment { val dialog = SetupWarningDialogFragment() val bundle = Bundle() bundle.apply { - putInt(TITLE, titleId) - putInt(DESCRIPTION, descriptionId) - putInt(HELP_LINK, helpLinkId) + putIntArray(TITLES, titleIds) + putIntArray(DESCRIPTIONS, descriptionIds) + putIntArray(HELP_LINKS, helpLinkIds) putInt(PAGE, page) } dialog.arguments = bundle From 2e2b044678fabeeb9844e223976a948ebd194172 Mon Sep 17 00:00:00 2001 From: Kleidis <167202775+kleidis@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:28:31 +0100 Subject: [PATCH 5/6] Rework CitraDirectoryHelper to support button step state --- .../citra_emu/fragments/CopyDirProgressDialogFragment.kt | 2 +- .../src/main/java/org/citra/citra_emu/ui/main/MainActivity.kt | 2 +- .../java/org/citra/citra_emu/utils/CitraDirectoryHelper.kt | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/android/app/src/main/java/org/citra/citra_emu/fragments/CopyDirProgressDialogFragment.kt b/src/android/app/src/main/java/org/citra/citra_emu/fragments/CopyDirProgressDialogFragment.kt index 7fd92f979..f07bb1d36 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/fragments/CopyDirProgressDialogFragment.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/fragments/CopyDirProgressDialogFragment.kt @@ -141,7 +141,7 @@ class CopyDirProgressDialog : DialogFragment() { override fun onComplete() { CitraDirectoryHelper.initializeCitraDirectory(path) - callback?.onStepCompleted() + callback?.onStepCompleted(0, false) viewModel.setCopyComplete(true) } }) 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..9ced4309a 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 @@ -307,7 +307,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider { return@registerForActivityResult } - CitraDirectoryHelper(this@MainActivity).showCitraDirectoryDialog(result) + CitraDirectoryHelper(this@MainActivity).showCitraDirectoryDialog(result, buttonState = {}) } val ciaFileInstaller = registerForActivityResult( diff --git a/src/android/app/src/main/java/org/citra/citra_emu/utils/CitraDirectoryHelper.kt b/src/android/app/src/main/java/org/citra/citra_emu/utils/CitraDirectoryHelper.kt index 0b6c91a98..08aa81b68 100644 --- a/src/android/app/src/main/java/org/citra/citra_emu/utils/CitraDirectoryHelper.kt +++ b/src/android/app/src/main/java/org/citra/citra_emu/utils/CitraDirectoryHelper.kt @@ -17,7 +17,7 @@ import org.citra.citra_emu.viewmodel.HomeViewModel * Citra directory initialization ui flow controller. */ class CitraDirectoryHelper(private val fragmentActivity: FragmentActivity) { - fun showCitraDirectoryDialog(result: Uri, callback: SetupCallback? = null) { + fun showCitraDirectoryDialog(result: Uri, callback: SetupCallback? = null, buttonState: () -> Unit) { val citraDirectoryDialog = CitraDirectoryDialogFragment.newInstance( fragmentActivity, result.toString(), @@ -36,7 +36,7 @@ class CitraDirectoryHelper(private val fragmentActivity: FragmentActivity) { ) if (!moveData || previous.toString().isEmpty()) { initializeCitraDirectory(path) - callback?.onStepCompleted() + buttonState() val viewModel = ViewModelProvider(fragmentActivity)[HomeViewModel::class.java] viewModel.setUserDir(fragmentActivity, path.path!!) viewModel.setPickingUserDir(false) From 6405e74b27763f5f58ea0d8da34092ee71969183 Mon Sep 17 00:00:00 2001 From: Kleidis <167202775+kleidis@users.noreply.github.com> Date: Fri, 7 Mar 2025 18:37:36 +0100 Subject: [PATCH 6/6] Update warning message for user folder selection step --- src/android/app/src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 9665719e5..58d64870c 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -93,7 +93,7 @@ Select User Folder user data directory with the button below.]]> Select - You can\'t skip this step + You can\'t skip setting the user folder This step is required to allow Azahar to work. Please select a directory and then you can continue. https://web.archive.org/web/20240304193549/https://github.com/citra-emu/citra/wiki/Citra-Android-user-data-and-storage Theme Settings