summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt22
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt145
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt7
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt14
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt109
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt35
-rw-r--r--src/android/app/src/main/res/layout-w600dp/fragment_setup.xml60
-rw-r--r--src/android/app/src/main/res/layout-w600dp/page_setup.xml69
-rw-r--r--src/android/app/src/main/res/layout/fragment_setup.xml62
-rw-r--r--src/android/app/src/main/res/layout/page_setup.xml30
-rw-r--r--src/android/app/src/main/res/values/strings.xml1
11 files changed, 370 insertions, 184 deletions
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
index 481ddd5a5..6b46d359e 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/SetupAdapter.kt
@@ -5,13 +5,19 @@ package org.yuzu.yuzu_emu.adapters
5 5
6import android.text.Html 6import android.text.Html
7import android.view.LayoutInflater 7import android.view.LayoutInflater
8import android.view.View
8import android.view.ViewGroup 9import android.view.ViewGroup
9import androidx.appcompat.app.AppCompatActivity 10import androidx.appcompat.app.AppCompatActivity
10import androidx.core.content.res.ResourcesCompat 11import androidx.core.content.res.ResourcesCompat
12import androidx.lifecycle.ViewModelProvider
11import androidx.recyclerview.widget.RecyclerView 13import androidx.recyclerview.widget.RecyclerView
12import com.google.android.material.button.MaterialButton 14import com.google.android.material.button.MaterialButton
13import org.yuzu.yuzu_emu.databinding.PageSetupBinding 15import org.yuzu.yuzu_emu.databinding.PageSetupBinding
16import org.yuzu.yuzu_emu.model.HomeViewModel
17import org.yuzu.yuzu_emu.model.SetupCallback
14import org.yuzu.yuzu_emu.model.SetupPage 18import org.yuzu.yuzu_emu.model.SetupPage
19import org.yuzu.yuzu_emu.model.StepState
20import org.yuzu.yuzu_emu.utils.ViewUtils
15 21
16class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : 22class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
17 RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { 23 RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
@@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
26 holder.bind(pages[position]) 32 holder.bind(pages[position])
27 33
28 inner class SetupPageViewHolder(val binding: PageSetupBinding) : 34 inner class SetupPageViewHolder(val binding: PageSetupBinding) :
29 RecyclerView.ViewHolder(binding.root) { 35 RecyclerView.ViewHolder(binding.root), SetupCallback {
30 lateinit var page: SetupPage 36 lateinit var page: SetupPage
31 37
32 init { 38 init {
@@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
35 41
36 fun bind(page: SetupPage) { 42 fun bind(page: SetupPage) {
37 this.page = page 43 this.page = page
44
45 if (page.stepCompleted.invoke() == StepState.COMPLETE) {
46 binding.buttonAction.visibility = View.INVISIBLE
47 binding.textConfirmation.visibility = View.VISIBLE
48 }
49
38 binding.icon.setImageDrawable( 50 binding.icon.setImageDrawable(
39 ResourcesCompat.getDrawable( 51 ResourcesCompat.getDrawable(
40 activity.resources, 52 activity.resources,
@@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
62 MaterialButton.ICON_GRAVITY_END 74 MaterialButton.ICON_GRAVITY_END
63 } 75 }
64 setOnClickListener { 76 setOnClickListener {
65 page.buttonAction.invoke() 77 page.buttonAction.invoke(this@SetupPageViewHolder)
66 } 78 }
67 } 79 }
68 } 80 }
81
82 override fun onStepCompleted() {
83 ViewUtils.hideView(binding.buttonAction, 200)
84 ViewUtils.showView(binding.textConfirmation, 200)
85 ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true)
86 }
69 } 87 }
70} 88}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
index 6c4ddaf6b..d50c421a0 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/SetupFragment.kt
@@ -19,6 +19,7 @@ import androidx.core.content.ContextCompat
19import androidx.core.view.ViewCompat 19import androidx.core.view.ViewCompat
20import androidx.core.view.WindowInsetsCompat 20import androidx.core.view.WindowInsetsCompat
21import androidx.core.view.isVisible 21import androidx.core.view.isVisible
22import androidx.core.view.updatePadding
22import androidx.fragment.app.Fragment 23import androidx.fragment.app.Fragment
23import androidx.fragment.app.activityViewModels 24import androidx.fragment.app.activityViewModels
24import androidx.navigation.findNavController 25import androidx.navigation.findNavController
@@ -32,10 +33,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter
32import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding 33import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding
33import org.yuzu.yuzu_emu.features.settings.model.Settings 34import org.yuzu.yuzu_emu.features.settings.model.Settings
34import org.yuzu.yuzu_emu.model.HomeViewModel 35import org.yuzu.yuzu_emu.model.HomeViewModel
36import org.yuzu.yuzu_emu.model.SetupCallback
35import org.yuzu.yuzu_emu.model.SetupPage 37import org.yuzu.yuzu_emu.model.SetupPage
38import org.yuzu.yuzu_emu.model.StepState
36import org.yuzu.yuzu_emu.ui.main.MainActivity 39import org.yuzu.yuzu_emu.ui.main.MainActivity
37import org.yuzu.yuzu_emu.utils.DirectoryInitialization 40import org.yuzu.yuzu_emu.utils.DirectoryInitialization
38import org.yuzu.yuzu_emu.utils.GameHelper 41import org.yuzu.yuzu_emu.utils.GameHelper
42import org.yuzu.yuzu_emu.utils.ViewUtils
39 43
40class SetupFragment : Fragment() { 44class SetupFragment : Fragment() {
41 private var _binding: FragmentSetupBinding? = null 45 private var _binding: FragmentSetupBinding? = null
@@ -112,14 +116,22 @@ class SetupFragment : Fragment() {
112 0, 116 0,
113 false, 117 false,
114 R.string.give_permission, 118 R.string.give_permission,
115 { permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }, 119 {
120 notificationCallback = it
121 permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
122 },
116 true, 123 true,
117 R.string.notification_warning, 124 R.string.notification_warning,
118 R.string.notification_warning_description, 125 R.string.notification_warning_description,
119 0, 126 0,
120 { 127 {
121 NotificationManagerCompat.from(requireContext()) 128 if (NotificationManagerCompat.from(requireContext())
122 .areNotificationsEnabled() 129 .areNotificationsEnabled()
130 ) {
131 StepState.COMPLETE
132 } else {
133 StepState.INCOMPLETE
134 }
123 } 135 }
124 ) 136 )
125 ) 137 )
@@ -133,12 +145,22 @@ class SetupFragment : Fragment() {
133 R.drawable.ic_add, 145 R.drawable.ic_add,
134 true, 146 true,
135 R.string.select_keys, 147 R.string.select_keys,
136 { mainActivity.getProdKey.launch(arrayOf("*/*")) }, 148 {
149 keyCallback = it
150 getProdKey.launch(arrayOf("*/*"))
151 },
137 true, 152 true,
138 R.string.install_prod_keys_warning, 153 R.string.install_prod_keys_warning,
139 R.string.install_prod_keys_warning_description, 154 R.string.install_prod_keys_warning_description,
140 R.string.install_prod_keys_warning_help, 155 R.string.install_prod_keys_warning_help,
141 { File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() } 156 {
157 val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
158 if (file.exists()) {
159 StepState.COMPLETE
160 } else {
161 StepState.INCOMPLETE
162 }
163 }
142 ) 164 )
143 ) 165 )
144 add( 166 add(
@@ -150,9 +172,8 @@ class SetupFragment : Fragment() {
150 true, 172 true,
151 R.string.add_games, 173 R.string.add_games,
152 { 174 {
153 mainActivity.getGamesDirectory.launch( 175 gamesDirCallback = it
154 Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data 176 getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
155 )
156 }, 177 },
157 true, 178 true,
158 R.string.add_games_warning, 179 R.string.add_games_warning,
@@ -163,7 +184,11 @@ class SetupFragment : Fragment() {
163 PreferenceManager.getDefaultSharedPreferences( 184 PreferenceManager.getDefaultSharedPreferences(
164 YuzuApplication.appContext 185 YuzuApplication.appContext
165 ) 186 )
166 preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() 187 if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
188 StepState.COMPLETE
189 } else {
190 StepState.INCOMPLETE
191 }
167 } 192 }
168 ) 193 )
169 ) 194 )
@@ -181,6 +206,13 @@ class SetupFragment : Fragment() {
181 ) 206 )
182 } 207 }
183 208
209 homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
210 if (it) {
211 pageForward()
212 homeViewModel.setShouldPageForward(false)
213 }
214 }
215
184 binding.viewPager2.apply { 216 binding.viewPager2.apply {
185 adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) 217 adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
186 offscreenPageLimit = 2 218 offscreenPageLimit = 2
@@ -194,15 +226,15 @@ class SetupFragment : Fragment() {
194 super.onPageSelected(position) 226 super.onPageSelected(position)
195 227
196 if (position == 1 && previousPosition == 0) { 228 if (position == 1 && previousPosition == 0) {
197 showView(binding.buttonNext) 229 ViewUtils.showView(binding.buttonNext)
198 showView(binding.buttonBack) 230 ViewUtils.showView(binding.buttonBack)
199 } else if (position == 0 && previousPosition == 1) { 231 } else if (position == 0 && previousPosition == 1) {
200 hideView(binding.buttonBack) 232 ViewUtils.hideView(binding.buttonBack)
201 hideView(binding.buttonNext) 233 ViewUtils.hideView(binding.buttonNext)
202 } else if (position == pages.size - 1 && previousPosition == pages.size - 2) { 234 } else if (position == pages.size - 1 && previousPosition == pages.size - 2) {
203 hideView(binding.buttonNext) 235 ViewUtils.hideView(binding.buttonNext)
204 } else if (position == pages.size - 2 && previousPosition == pages.size - 1) { 236 } else if (position == pages.size - 2 && previousPosition == pages.size - 1) {
205 showView(binding.buttonNext) 237 ViewUtils.showView(binding.buttonNext)
206 } 238 }
207 239
208 previousPosition = position 240 previousPosition = position
@@ -215,7 +247,8 @@ class SetupFragment : Fragment() {
215 247
216 // Checks if the user has completed the task on the current page 248 // Checks if the user has completed the task on the current page
217 if (currentPage.hasWarning) { 249 if (currentPage.hasWarning) {
218 if (currentPage.taskCompleted.invoke()) { 250 val stepState = currentPage.stepCompleted.invoke()
251 if (stepState != StepState.INCOMPLETE) {
219 pageForward() 252 pageForward()
220 return@setOnClickListener 253 return@setOnClickListener
221 } 254 }
@@ -264,9 +297,15 @@ class SetupFragment : Fragment() {
264 _binding = null 297 _binding = null
265 } 298 }
266 299
300 private lateinit var notificationCallback: SetupCallback
301
267 @RequiresApi(Build.VERSION_CODES.TIRAMISU) 302 @RequiresApi(Build.VERSION_CODES.TIRAMISU)
268 private val permissionLauncher = 303 private val permissionLauncher =
269 registerForActivityResult(ActivityResultContracts.RequestPermission()) { 304 registerForActivityResult(ActivityResultContracts.RequestPermission()) {
305 if (it) {
306 notificationCallback.onStepCompleted()
307 }
308
270 if (!it && 309 if (!it &&
271 !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) 310 !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
272 ) { 311 ) {
@@ -277,6 +316,27 @@ class SetupFragment : Fragment() {
277 } 316 }
278 } 317 }
279 318
319 private lateinit var keyCallback: SetupCallback
320
321 val getProdKey =
322 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
323 if (result != null) {
324 if (mainActivity.processKey(result)) {
325 keyCallback.onStepCompleted()
326 }
327 }
328 }
329
330 private lateinit var gamesDirCallback: SetupCallback
331
332 val getGamesDirectory =
333 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
334 if (result != null) {
335 mainActivity.processGamesDir(result)
336 gamesDirCallback.onStepCompleted()
337 }
338 }
339
280 private fun finishSetup() { 340 private fun finishSetup() {
281 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 341 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
282 .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) 342 .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
@@ -284,33 +344,6 @@ class SetupFragment : Fragment() {
284 mainActivity.finishSetup(binding.root.findNavController()) 344 mainActivity.finishSetup(binding.root.findNavController())
285 } 345 }
286 346
287 private fun showView(view: View) {
288 view.apply {
289 alpha = 0f
290 visibility = View.VISIBLE
291 isClickable = true
292 }.animate().apply {
293 duration = 300
294 alpha(1f)
295 }.start()
296 }
297
298 private fun hideView(view: View) {
299 if (view.visibility == View.INVISIBLE) {
300 return
301 }
302
303 view.apply {
304 alpha = 1f
305 isClickable = false
306 }.animate().apply {
307 duration = 300
308 alpha(0f)
309 }.withEndAction {
310 view.visibility = View.INVISIBLE
311 }
312 }
313
314 fun pageForward() { 347 fun pageForward() {
315 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 348 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
316 } 349 }
@@ -326,15 +359,29 @@ class SetupFragment : Fragment() {
326 private fun setInsets() = 359 private fun setInsets() =
327 ViewCompat.setOnApplyWindowInsetsListener( 360 ViewCompat.setOnApplyWindowInsetsListener(
328 binding.root 361 binding.root
329 ) { view: View, windowInsets: WindowInsetsCompat -> 362 ) { _: View, windowInsets: WindowInsetsCompat ->
330 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) 363 val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
331 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) 364 val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
332 view.setPadding( 365
333 barInsets.left + cutoutInsets.left, 366 val leftPadding = barInsets.left + cutoutInsets.left
334 barInsets.top + cutoutInsets.top, 367 val topPadding = barInsets.top + cutoutInsets.top
335 barInsets.right + cutoutInsets.right, 368 val rightPadding = barInsets.right + cutoutInsets.right
336 barInsets.bottom + cutoutInsets.bottom 369 val bottomPadding = barInsets.bottom + cutoutInsets.bottom
337 ) 370
371 if (resources.getBoolean(R.bool.small_layout)) {
372 binding.viewPager2
373 .updatePadding(left = leftPadding, top = topPadding, right = rightPadding)
374 binding.constraintButtons
375 .updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding)
376 } else {
377 binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding)
378 binding.constraintButtons
379 .updatePadding(
380 left = leftPadding,
381 right = rightPadding,
382 bottom = bottomPadding
383 )
384 }
338 windowInsets 385 windowInsets
339 } 386 }
340} 387}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
index 263ee7144..e13d84c9c 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/HomeViewModel.kt
@@ -14,6 +14,9 @@ class HomeViewModel : ViewModel() {
14 private val _statusBarShadeVisible = MutableLiveData(true) 14 private val _statusBarShadeVisible = MutableLiveData(true)
15 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible 15 val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
16 16
17 private val _shouldPageForward = MutableLiveData(false)
18 val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
19
17 var navigatedToSetup = false 20 var navigatedToSetup = false
18 21
19 init { 22 init {
@@ -33,4 +36,8 @@ class HomeViewModel : ViewModel() {
33 } 36 }
34 _statusBarShadeVisible.value = visible 37 _statusBarShadeVisible.value = visible
35 } 38 }
39
40 fun setShouldPageForward(pageForward: Boolean) {
41 _shouldPageForward.value = pageForward
42 }
36} 43}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
index a0c878e1c..09a128ae6 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/SetupPage.kt
@@ -10,10 +10,20 @@ data class SetupPage(
10 val buttonIconId: Int, 10 val buttonIconId: Int,
11 val leftAlignedIcon: Boolean, 11 val leftAlignedIcon: Boolean,
12 val buttonTextId: Int, 12 val buttonTextId: Int,
13 val buttonAction: () -> Unit, 13 val buttonAction: (callback: SetupCallback) -> Unit,
14 val hasWarning: Boolean, 14 val hasWarning: Boolean,
15 val warningTitleId: Int = 0, 15 val warningTitleId: Int = 0,
16 val warningDescriptionId: Int = 0, 16 val warningDescriptionId: Int = 0,
17 val warningHelpLinkId: Int = 0, 17 val warningHelpLinkId: Int = 0,
18 val taskCompleted: () -> Boolean = { true } 18 val stepCompleted: () -> StepState = { StepState.UNDEFINED }
19) 19)
20
21interface SetupCallback {
22 fun onStepCompleted()
23}
24
25enum class StepState {
26 COMPLETE,
27 INCOMPLETE,
28 UNDEFINED
29}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
index f7d7aed1e..f77d06262 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
@@ -266,73 +266,80 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
266 266
267 val getGamesDirectory = 267 val getGamesDirectory =
268 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> 268 registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
269 if (result == null) { 269 if (result != null) {
270 return@registerForActivityResult 270 processGamesDir(result)
271 } 271 }
272 }
272 273
273 contentResolver.takePersistableUriPermission( 274 fun processGamesDir(result: Uri) {
274 result, 275 contentResolver.takePersistableUriPermission(
275 Intent.FLAG_GRANT_READ_URI_PERMISSION 276 result,
276 ) 277 Intent.FLAG_GRANT_READ_URI_PERMISSION
278 )
277 279
278 // When a new directory is picked, we currently will reset the existing games 280 // When a new directory is picked, we currently will reset the existing games
279 // database. This effectively means that only one game directory is supported. 281 // database. This effectively means that only one game directory is supported.
280 PreferenceManager.getDefaultSharedPreferences(applicationContext).edit() 282 PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
281 .putString(GameHelper.KEY_GAME_PATH, result.toString()) 283 .putString(GameHelper.KEY_GAME_PATH, result.toString())
282 .apply() 284 .apply()
283 285
284 Toast.makeText( 286 Toast.makeText(
285 applicationContext, 287 applicationContext,
286 R.string.games_dir_selected, 288 R.string.games_dir_selected,
287 Toast.LENGTH_LONG 289 Toast.LENGTH_LONG
288 ).show() 290 ).show()
289 291
290 gamesViewModel.reloadGames(true) 292 gamesViewModel.reloadGames(true)
291 } 293 }
292 294
293 val getProdKey = 295 val getProdKey =
294 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 296 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
295 if (result == null) { 297 if (result != null) {
296 return@registerForActivityResult 298 processKey(result)
297 } 299 }
300 }
298 301
299 if (FileUtil.getExtension(result) != "keys") { 302 fun processKey(result: Uri): Boolean {
300 MessageDialogFragment.newInstance( 303 if (FileUtil.getExtension(result) != "keys") {
301 R.string.reading_keys_failure, 304 MessageDialogFragment.newInstance(
302 R.string.install_prod_keys_failure_extension_description 305 R.string.reading_keys_failure,
303 ).show(supportFragmentManager, MessageDialogFragment.TAG) 306 R.string.install_prod_keys_failure_extension_description
304 return@registerForActivityResult 307 ).show(supportFragmentManager, MessageDialogFragment.TAG)
305 } 308 return false
309 }
306 310
307 contentResolver.takePersistableUriPermission( 311 contentResolver.takePersistableUriPermission(
312 result,
313 Intent.FLAG_GRANT_READ_URI_PERMISSION
314 )
315
316 val dstPath = DirectoryInitialization.userDirectory + "/keys/"
317 if (FileUtil.copyUriToInternalStorage(
318 applicationContext,
308 result, 319 result,
309 Intent.FLAG_GRANT_READ_URI_PERMISSION 320 dstPath,
321 "prod.keys"
310 ) 322 )
311 323 ) {
312 val dstPath = DirectoryInitialization.userDirectory + "/keys/" 324 if (NativeLibrary.reloadKeys()) {
313 if (FileUtil.copyUriToInternalStorage( 325 Toast.makeText(
314 applicationContext, 326 applicationContext,
315 result, 327 R.string.install_keys_success,
316 dstPath, 328 Toast.LENGTH_SHORT
317 "prod.keys" 329 ).show()
318 ) 330 gamesViewModel.reloadGames(true)
319 ) { 331 return true
320 if (NativeLibrary.reloadKeys()) { 332 } else {
321 Toast.makeText( 333 MessageDialogFragment.newInstance(
322 applicationContext, 334 R.string.invalid_keys_error,
323 R.string.install_keys_success, 335 R.string.install_keys_failure_description,
324 Toast.LENGTH_SHORT 336 R.string.dumping_keys_quickstart_link
325 ).show() 337 ).show(supportFragmentManager, MessageDialogFragment.TAG)
326 gamesViewModel.reloadGames(true) 338 return false
327 } else {
328 MessageDialogFragment.newInstance(
329 R.string.invalid_keys_error,
330 R.string.install_keys_failure_description,
331 R.string.dumping_keys_quickstart_link
332 ).show(supportFragmentManager, MessageDialogFragment.TAG)
333 }
334 } 339 }
335 } 340 }
341 return false
342 }
336 343
337 val getFirmware = 344 val getFirmware =
338 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> 345 registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
new file mode 100644
index 000000000..f9a3e4126
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ViewUtils.kt
@@ -0,0 +1,35 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4package org.yuzu.yuzu_emu.utils
5
6import android.view.View
7
8object ViewUtils {
9 fun showView(view: View, length: Long = 300) {
10 view.apply {
11 alpha = 0f
12 visibility = View.VISIBLE
13 isClickable = true
14 }.animate().apply {
15 duration = length
16 alpha(1f)
17 }.start()
18 }
19
20 fun hideView(view: View, length: Long = 300) {
21 if (view.visibility == View.INVISIBLE) {
22 return
23 }
24
25 view.apply {
26 alpha = 1f
27 isClickable = false
28 }.animate().apply {
29 duration = length
30 alpha(0f)
31 }.withEndAction {
32 view.visibility = View.INVISIBLE
33 }.start()
34 }
35}
diff --git a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
index cbe631d88..406df9eab 100644
--- a/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
+++ b/src/android/app/src/main/res/layout-w600dp/fragment_setup.xml
@@ -1,5 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout 2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/setup_root" 5 android:id="@+id/setup_root"
@@ -8,33 +8,39 @@
8 8
9 <androidx.viewpager2.widget.ViewPager2 9 <androidx.viewpager2.widget.ViewPager2
10 android:id="@+id/viewPager2" 10 android:id="@+id/viewPager2"
11 android:layout_width="0dp" 11 android:layout_width="match_parent"
12 android:layout_height="0dp" 12 android:layout_height="match_parent"
13 app:layout_constraintBottom_toBottomOf="parent" 13 android:layout_alignParentTop="true"
14 app:layout_constraintEnd_toEndOf="parent" 14 android:layout_alignParentBottom="true"
15 app:layout_constraintStart_toStartOf="parent" 15 android:clipToPadding="false" />
16 app:layout_constraintTop_toTopOf="parent" />
17 16
18 <com.google.android.material.button.MaterialButton 17 <androidx.constraintlayout.widget.ConstraintLayout
19 style="@style/Widget.Material3.Button.TextButton" 18 android:id="@+id/constraint_buttons"
20 android:id="@+id/button_next" 19 android:layout_width="match_parent"
21 android:layout_width="wrap_content"
22 android:layout_height="wrap_content" 20 android:layout_height="wrap_content"
23 android:layout_margin="16dp" 21 android:layout_alignParentBottom="true"
24 android:text="@string/next" 22 android:layout_margin="8dp">
25 android:visibility="invisible"
26 app:layout_constraintBottom_toBottomOf="parent"
27 app:layout_constraintEnd_toEndOf="parent" />
28 23
29 <com.google.android.material.button.MaterialButton 24 <com.google.android.material.button.MaterialButton
30 android:id="@+id/button_back" 25 android:id="@+id/button_next"
31 style="@style/Widget.Material3.Button.TextButton" 26 style="@style/Widget.Material3.Button.TextButton"
32 android:layout_width="wrap_content" 27 android:layout_width="wrap_content"
33 android:layout_height="wrap_content" 28 android:layout_height="wrap_content"
34 android:layout_margin="16dp" 29 android:text="@string/next"
35 android:text="@string/back" 30 android:visibility="invisible"
36 android:visibility="invisible" 31 app:layout_constraintBottom_toBottomOf="parent"
37 app:layout_constraintBottom_toBottomOf="parent" 32 app:layout_constraintEnd_toEndOf="parent" />
38 app:layout_constraintStart_toStartOf="parent" /> 33
34 <com.google.android.material.button.MaterialButton
35 android:id="@+id/button_back"
36 style="@style/Widget.Material3.Button.TextButton"
37 android:layout_width="wrap_content"
38 android:layout_height="wrap_content"
39 android:text="@string/back"
40 android:visibility="invisible"
41 app:layout_constraintBottom_toBottomOf="parent"
42 app:layout_constraintStart_toStartOf="parent" />
43
44 </androidx.constraintlayout.widget.ConstraintLayout>
39 45
40</androidx.constraintlayout.widget.ConstraintLayout> 46</RelativeLayout>
diff --git a/src/android/app/src/main/res/layout-w600dp/page_setup.xml b/src/android/app/src/main/res/layout-w600dp/page_setup.xml
index e1c26b2f8..9e0ab8ecb 100644
--- a/src/android/app/src/main/res/layout-w600dp/page_setup.xml
+++ b/src/android/app/src/main/res/layout-w600dp/page_setup.xml
@@ -21,45 +21,76 @@
21 21
22 </LinearLayout> 22 </LinearLayout>
23 23
24 <LinearLayout 24 <androidx.constraintlayout.widget.ConstraintLayout
25 android:layout_width="match_parent" 25 android:layout_width="match_parent"
26 android:layout_height="match_parent" 26 android:layout_height="match_parent"
27 android:layout_weight="1" 27 android:layout_weight="1">
28 android:orientation="vertical"
29 android:gravity="center">
30 28
31 <com.google.android.material.textview.MaterialTextView 29 <com.google.android.material.textview.MaterialTextView
32 style="@style/TextAppearance.Material3.DisplaySmall"
33 android:id="@+id/text_title" 30 android:id="@+id/text_title"
34 android:layout_width="match_parent" 31 style="@style/TextAppearance.Material3.DisplaySmall"
35 android:layout_height="wrap_content" 32 android:layout_width="0dp"
36 android:textAlignment="center" 33 android:layout_height="0dp"
34 android:gravity="center"
37 android:textColor="?attr/colorOnSurface" 35 android:textColor="?attr/colorOnSurface"
38 android:textStyle="bold" 36 android:textStyle="bold"
37 app:layout_constraintBottom_toTopOf="@+id/text_description"
38 app:layout_constraintEnd_toEndOf="parent"
39 app:layout_constraintStart_toStartOf="parent"
40 app:layout_constraintTop_toTopOf="parent"
41 app:layout_constraintVertical_weight="2"
39 tools:text="@string/welcome" /> 42 tools:text="@string/welcome" />
40 43
41 <com.google.android.material.textview.MaterialTextView 44 <com.google.android.material.textview.MaterialTextView
42 style="@style/TextAppearance.Material3.TitleLarge"
43 android:id="@+id/text_description" 45 android:id="@+id/text_description"
44 android:layout_width="match_parent" 46 style="@style/TextAppearance.Material3.TitleLarge"
45 android:layout_height="wrap_content" 47 android:layout_width="0dp"
46 android:layout_marginTop="16dp" 48 android:layout_height="0dp"
47 android:paddingHorizontal="32dp" 49 android:gravity="center"
48 android:textAlignment="center" 50 android:textSize="20sp"
49 android:textSize="26sp" 51 android:paddingHorizontal="16dp"
50 app:lineHeight="40sp" 52 app:layout_constraintBottom_toTopOf="@+id/button_action"
53 app:layout_constraintEnd_toEndOf="parent"
54 app:layout_constraintStart_toStartOf="parent"
55 app:layout_constraintTop_toBottomOf="@+id/text_title"
56 app:layout_constraintVertical_weight="2"
57 app:lineHeight="30sp"
51 tools:text="@string/welcome_description" /> 58 tools:text="@string/welcome_description" />
52 59
60 <com.google.android.material.textview.MaterialTextView
61 android:id="@+id/text_confirmation"
62 style="@style/TextAppearance.Material3.TitleLarge"
63 android:layout_width="0dp"
64 android:layout_height="0dp"
65 android:paddingHorizontal="16dp"
66 android:paddingBottom="20dp"
67 android:gravity="center"
68 android:textSize="30sp"
69 android:visibility="invisible"
70 android:text="@string/step_complete"
71 android:textStyle="bold"
72 app:layout_constraintBottom_toBottomOf="parent"
73 app:layout_constraintEnd_toEndOf="parent"
74 app:layout_constraintStart_toStartOf="parent"
75 app:layout_constraintTop_toBottomOf="@+id/text_description"
76 app:layout_constraintVertical_weight="1"
77 app:lineHeight="30sp" />
78
53 <com.google.android.material.button.MaterialButton 79 <com.google.android.material.button.MaterialButton
54 android:id="@+id/button_action" 80 android:id="@+id/button_action"
55 android:layout_width="wrap_content" 81 android:layout_width="wrap_content"
56 android:layout_height="56dp" 82 android:layout_height="56dp"
57 android:layout_marginTop="32dp" 83 android:layout_marginTop="16dp"
84 android:layout_marginBottom="48dp"
58 android:textSize="20sp" 85 android:textSize="20sp"
59 app:iconSize="24sp"
60 app:iconGravity="end" 86 app:iconGravity="end"
87 app:iconSize="24sp"
88 app:layout_constraintBottom_toBottomOf="parent"
89 app:layout_constraintEnd_toEndOf="parent"
90 app:layout_constraintStart_toStartOf="parent"
91 app:layout_constraintTop_toBottomOf="@+id/text_description"
61 tools:text="Get started" /> 92 tools:text="Get started" />
62 93
63 </LinearLayout> 94 </androidx.constraintlayout.widget.ConstraintLayout>
64 95
65</LinearLayout> 96</LinearLayout>
diff --git a/src/android/app/src/main/res/layout/fragment_setup.xml b/src/android/app/src/main/res/layout/fragment_setup.xml
index d7bafaea2..9499f6463 100644
--- a/src/android/app/src/main/res/layout/fragment_setup.xml
+++ b/src/android/app/src/main/res/layout/fragment_setup.xml
@@ -1,5 +1,5 @@
1<?xml version="1.0" encoding="utf-8"?> 1<?xml version="1.0" encoding="utf-8"?>
2<androidx.constraintlayout.widget.ConstraintLayout 2<RelativeLayout
3 xmlns:android="http://schemas.android.com/apk/res/android" 3 xmlns:android="http://schemas.android.com/apk/res/android"
4 xmlns:app="http://schemas.android.com/apk/res-auto" 4 xmlns:app="http://schemas.android.com/apk/res-auto"
5 android:id="@+id/setup_root" 5 android:id="@+id/setup_root"
@@ -8,35 +8,39 @@
8 8
9 <androidx.viewpager2.widget.ViewPager2 9 <androidx.viewpager2.widget.ViewPager2
10 android:id="@+id/viewPager2" 10 android:id="@+id/viewPager2"
11 android:layout_width="0dp" 11 android:layout_width="match_parent"
12 android:layout_height="0dp"
13 android:clipToPadding="false"
14 android:layout_marginBottom="16dp"
15 app:layout_constraintBottom_toTopOf="@+id/button_next"
16 app:layout_constraintEnd_toEndOf="parent"
17 app:layout_constraintStart_toStartOf="parent"
18 app:layout_constraintTop_toTopOf="parent" />
19
20 <com.google.android.material.button.MaterialButton
21 style="@style/Widget.Material3.Button.TextButton"
22 android:id="@+id/button_next"
23 android:layout_width="wrap_content"
24 android:layout_height="wrap_content" 12 android:layout_height="wrap_content"
25 android:layout_margin="12dp" 13 android:layout_above="@+id/constraint_buttons"
26 android:text="@string/next" 14 android:layout_alignParentTop="true"
27 android:visibility="invisible" 15 android:clipToPadding="false" />
28 app:layout_constraintBottom_toBottomOf="parent"
29 app:layout_constraintEnd_toEndOf="parent" />
30 16
31 <com.google.android.material.button.MaterialButton 17 <androidx.constraintlayout.widget.ConstraintLayout
32 style="@style/Widget.Material3.Button.TextButton" 18 android:id="@+id/constraint_buttons"
33 android:id="@+id/button_back" 19 android:layout_width="match_parent"
34 android:layout_width="wrap_content"
35 android:layout_height="wrap_content" 20 android:layout_height="wrap_content"
36 android:layout_margin="12dp" 21 android:layout_margin="8dp"
37 android:text="@string/back" 22 android:layout_alignParentBottom="true">
38 android:visibility="invisible" 23
39 app:layout_constraintBottom_toBottomOf="parent" 24 <com.google.android.material.button.MaterialButton
40 app:layout_constraintStart_toStartOf="parent" /> 25 android:id="@+id/button_next"
26 style="@style/Widget.Material3.Button.TextButton"
27 android:layout_width="wrap_content"
28 android:layout_height="wrap_content"
29 android:text="@string/next"
30 android:visibility="invisible"
31 app:layout_constraintBottom_toBottomOf="parent"
32 app:layout_constraintEnd_toEndOf="parent" />
33
34 <com.google.android.material.button.MaterialButton
35 android:id="@+id/button_back"
36 style="@style/Widget.Material3.Button.TextButton"
37 android:layout_width="wrap_content"
38 android:layout_height="wrap_content"
39 android:text="@string/back"
40 android:visibility="invisible"
41 app:layout_constraintBottom_toBottomOf="parent"
42 app:layout_constraintStart_toStartOf="parent" />
43
44 </androidx.constraintlayout.widget.ConstraintLayout>
41 45
42</androidx.constraintlayout.widget.ConstraintLayout> 46</RelativeLayout>
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 1436ef308..535abcf02 100644
--- a/src/android/app/src/main/res/layout/page_setup.xml
+++ b/src/android/app/src/main/res/layout/page_setup.xml
@@ -21,11 +21,12 @@
21 app:layout_constraintVertical_chainStyle="spread" 21 app:layout_constraintVertical_chainStyle="spread"
22 app:layout_constraintWidth_max="220dp" 22 app:layout_constraintWidth_max="220dp"
23 app:layout_constraintWidth_min="110dp" 23 app:layout_constraintWidth_min="110dp"
24 app:layout_constraintVertical_weight="3" /> 24 app:layout_constraintVertical_weight="3"
25 tools:src="@drawable/ic_notification" />
25 26
26 <com.google.android.material.textview.MaterialTextView 27 <com.google.android.material.textview.MaterialTextView
27 android:id="@+id/text_title" 28 android:id="@+id/text_title"
28 style="@style/TextAppearance.Material3.DisplayMedium" 29 style="@style/TextAppearance.Material3.DisplaySmall"
29 android:layout_width="0dp" 30 android:layout_width="0dp"
30 android:layout_height="0dp" 31 android:layout_height="0dp"
31 android:textAlignment="center" 32 android:textAlignment="center"
@@ -44,23 +45,42 @@
44 android:layout_width="0dp" 45 android:layout_width="0dp"
45 android:layout_height="0dp" 46 android:layout_height="0dp"
46 android:textAlignment="center" 47 android:textAlignment="center"
47 android:textSize="26sp" 48 android:textSize="20sp"
48 android:paddingHorizontal="16dp" 49 android:paddingHorizontal="16dp"
49 app:layout_constraintBottom_toTopOf="@+id/button_action" 50 app:layout_constraintBottom_toTopOf="@+id/button_action"
50 app:layout_constraintEnd_toEndOf="parent" 51 app:layout_constraintEnd_toEndOf="parent"
51 app:layout_constraintStart_toStartOf="parent" 52 app:layout_constraintStart_toStartOf="parent"
52 app:layout_constraintTop_toBottomOf="@+id/text_title" 53 app:layout_constraintTop_toBottomOf="@+id/text_title"
53 app:layout_constraintVertical_weight="2" 54 app:layout_constraintVertical_weight="2"
54 app:lineHeight="40sp" 55 app:lineHeight="30sp"
55 tools:text="@string/welcome_description" /> 56 tools:text="@string/welcome_description" />
56 57
58 <com.google.android.material.textview.MaterialTextView
59 android:id="@+id/text_confirmation"
60 style="@style/TextAppearance.Material3.TitleLarge"
61 android:layout_width="wrap_content"
62 android:layout_height="0dp"
63 android:paddingHorizontal="16dp"
64 android:paddingTop="24dp"
65 android:textAlignment="center"
66 android:textSize="30sp"
67 android:visibility="invisible"
68 android:text="@string/step_complete"
69 android:textStyle="bold"
70 app:layout_constraintBottom_toBottomOf="parent"
71 app:layout_constraintEnd_toEndOf="parent"
72 app:layout_constraintStart_toStartOf="parent"
73 app:layout_constraintTop_toBottomOf="@+id/text_description"
74 app:layout_constraintVertical_weight="1"
75 app:lineHeight="30sp" />
76
57 <com.google.android.material.button.MaterialButton 77 <com.google.android.material.button.MaterialButton
58 android:id="@+id/button_action" 78 android:id="@+id/button_action"
59 android:layout_width="wrap_content" 79 android:layout_width="wrap_content"
60 android:layout_height="56dp" 80 android:layout_height="56dp"
61 android:textSize="20sp"
62 android:layout_marginTop="16dp" 81 android:layout_marginTop="16dp"
63 android:layout_marginBottom="48dp" 82 android:layout_marginBottom="48dp"
83 android:textSize="20sp"
64 app:iconGravity="end" 84 app:iconGravity="end"
65 app:iconSize="24sp" 85 app:iconSize="24sp"
66 app:layout_constraintBottom_toBottomOf="parent" 86 app:layout_constraintBottom_toBottomOf="parent"
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index 82359f358..de1b2909b 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -29,6 +29,7 @@
29 <string name="back">Back</string> 29 <string name="back">Back</string>
30 <string name="add_games">Add Games</string> 30 <string name="add_games">Add Games</string>
31 <string name="add_games_description">Select your games folder</string> 31 <string name="add_games_description">Select your games folder</string>
32 <string name="step_complete">Complete!</string>
32 33
33 <!-- Home strings --> 34 <!-- Home strings -->
34 <string name="home_games">Games</string> 35 <string name="home_games">Games</string>