yuzu-emu
/
yuzu-android
Archived
1
0
Fork 0

Merge pull request #11273 from t895/setup-completion

android: Setup additions
This commit is contained in:
bunnei 2023-08-14 15:41:35 -07:00 committed by GitHub
commit bbc6b08fc7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 379 additions and 193 deletions

View File

@ -5,13 +5,19 @@ package org.yuzu.yuzu_emu.adapters
import android.text.Html import android.text.Html
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton import com.google.android.material.button.MaterialButton
import org.yuzu.yuzu_emu.databinding.PageSetupBinding import org.yuzu.yuzu_emu.databinding.PageSetupBinding
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) : class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() { RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
holder.bind(pages[position]) holder.bind(pages[position])
inner class SetupPageViewHolder(val binding: PageSetupBinding) : inner class SetupPageViewHolder(val binding: PageSetupBinding) :
RecyclerView.ViewHolder(binding.root) { RecyclerView.ViewHolder(binding.root), SetupCallback {
lateinit var page: SetupPage lateinit var page: SetupPage
init { init {
@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
fun bind(page: SetupPage) { fun bind(page: SetupPage) {
this.page = page this.page = page
if (page.stepCompleted.invoke() == StepState.COMPLETE) {
binding.buttonAction.visibility = View.INVISIBLE
binding.textConfirmation.visibility = View.VISIBLE
}
binding.icon.setImageDrawable( binding.icon.setImageDrawable(
ResourcesCompat.getDrawable( ResourcesCompat.getDrawable(
activity.resources, activity.resources,
@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
MaterialButton.ICON_GRAVITY_END MaterialButton.ICON_GRAVITY_END
} }
setOnClickListener { setOnClickListener {
page.buttonAction.invoke() page.buttonAction.invoke(this@SetupPageViewHolder)
} }
} }
} }
override fun onStepCompleted() {
ViewUtils.hideView(binding.buttonAction, 200)
ViewUtils.showView(binding.textConfirmation, 200)
ViewModelProvider(activity)[HomeViewModel::class.java].setShouldPageForward(true)
}
} }
} }

View File

@ -19,6 +19,7 @@ import androidx.core.content.ContextCompat
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.findNavController import androidx.navigation.findNavController
@ -32,10 +33,13 @@ import org.yuzu.yuzu_emu.adapters.SetupAdapter
import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding import org.yuzu.yuzu_emu.databinding.FragmentSetupBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.model.HomeViewModel import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.SetupCallback
import org.yuzu.yuzu_emu.model.SetupPage import org.yuzu.yuzu_emu.model.SetupPage
import org.yuzu.yuzu_emu.model.StepState
import org.yuzu.yuzu_emu.ui.main.MainActivity import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.GameHelper import org.yuzu.yuzu_emu.utils.GameHelper
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupFragment : Fragment() { class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null private var _binding: FragmentSetupBinding? = null
@ -112,14 +116,22 @@ class SetupFragment : Fragment() {
0, 0,
false, false,
R.string.give_permission, R.string.give_permission,
{ permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) }, {
notificationCallback = it
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
},
true, true,
R.string.notification_warning, R.string.notification_warning,
R.string.notification_warning_description, R.string.notification_warning_description,
0, 0,
{ {
NotificationManagerCompat.from(requireContext()) if (NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled() .areNotificationsEnabled()
) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
}
} }
) )
) )
@ -133,12 +145,22 @@ class SetupFragment : Fragment() {
R.drawable.ic_add, R.drawable.ic_add,
true, true,
R.string.select_keys, R.string.select_keys,
{ mainActivity.getProdKey.launch(arrayOf("*/*")) }, {
keyCallback = it
getProdKey.launch(arrayOf("*/*"))
},
true, true,
R.string.install_prod_keys_warning, R.string.install_prod_keys_warning,
R.string.install_prod_keys_warning_description, R.string.install_prod_keys_warning_description,
R.string.install_prod_keys_warning_help, R.string.install_prod_keys_warning_help,
{ File(DirectoryInitialization.userDirectory + "/keys/prod.keys").exists() } {
val file = File(DirectoryInitialization.userDirectory + "/keys/prod.keys")
if (file.exists()) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
}
}
) )
) )
add( add(
@ -150,9 +172,8 @@ class SetupFragment : Fragment() {
true, true,
R.string.add_games, R.string.add_games,
{ {
mainActivity.getGamesDirectory.launch( gamesDirCallback = it
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
)
}, },
true, true,
R.string.add_games_warning, R.string.add_games_warning,
@ -163,7 +184,11 @@ class SetupFragment : Fragment() {
PreferenceManager.getDefaultSharedPreferences( PreferenceManager.getDefaultSharedPreferences(
YuzuApplication.appContext YuzuApplication.appContext
) )
preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty() if (preferences.getString(GameHelper.KEY_GAME_PATH, "")!!.isNotEmpty()) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
}
} }
) )
) )
@ -181,6 +206,13 @@ class SetupFragment : Fragment() {
) )
} }
homeViewModel.shouldPageForward.observe(viewLifecycleOwner) {
if (it) {
pageForward()
homeViewModel.setShouldPageForward(false)
}
}
binding.viewPager2.apply { binding.viewPager2.apply {
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages) adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
offscreenPageLimit = 2 offscreenPageLimit = 2
@ -194,15 +226,15 @@ class SetupFragment : Fragment() {
super.onPageSelected(position) super.onPageSelected(position)
if (position == 1 && previousPosition == 0) { if (position == 1 && previousPosition == 0) {
showView(binding.buttonNext) ViewUtils.showView(binding.buttonNext)
showView(binding.buttonBack) ViewUtils.showView(binding.buttonBack)
} else if (position == 0 && previousPosition == 1) { } else if (position == 0 && previousPosition == 1) {
hideView(binding.buttonBack) ViewUtils.hideView(binding.buttonBack)
hideView(binding.buttonNext) ViewUtils.hideView(binding.buttonNext)
} else if (position == pages.size - 1 && previousPosition == pages.size - 2) { } else if (position == pages.size - 1 && previousPosition == pages.size - 2) {
hideView(binding.buttonNext) ViewUtils.hideView(binding.buttonNext)
} else if (position == pages.size - 2 && previousPosition == pages.size - 1) { } else if (position == pages.size - 2 && previousPosition == pages.size - 1) {
showView(binding.buttonNext) ViewUtils.showView(binding.buttonNext)
} }
previousPosition = position previousPosition = position
@ -215,7 +247,8 @@ class SetupFragment : Fragment() {
// Checks if the user has completed the task on the current page // Checks if the user has completed the task on the current page
if (currentPage.hasWarning) { if (currentPage.hasWarning) {
if (currentPage.taskCompleted.invoke()) { val stepState = currentPage.stepCompleted.invoke()
if (stepState != StepState.INCOMPLETE) {
pageForward() pageForward()
return@setOnClickListener return@setOnClickListener
} }
@ -264,9 +297,15 @@ class SetupFragment : Fragment() {
_binding = null _binding = null
} }
private lateinit var notificationCallback: SetupCallback
@RequiresApi(Build.VERSION_CODES.TIRAMISU) @RequiresApi(Build.VERSION_CODES.TIRAMISU)
private val permissionLauncher = private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
notificationCallback.onStepCompleted()
}
if (!it && if (!it &&
!shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) !shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS)
) { ) {
@ -277,6 +316,27 @@ class SetupFragment : Fragment() {
} }
} }
private lateinit var keyCallback: SetupCallback
val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result != null) {
if (mainActivity.processKey(result)) {
keyCallback.onStepCompleted()
}
}
}
private lateinit var gamesDirCallback: SetupCallback
val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result != null) {
mainActivity.processGamesDir(result)
gamesDirCallback.onStepCompleted()
}
}
private fun finishSetup() { private fun finishSetup() {
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
.putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false) .putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
@ -284,33 +344,6 @@ class SetupFragment : Fragment() {
mainActivity.finishSetup(binding.root.findNavController()) mainActivity.finishSetup(binding.root.findNavController())
} }
private fun showView(view: View) {
view.apply {
alpha = 0f
visibility = View.VISIBLE
isClickable = true
}.animate().apply {
duration = 300
alpha(1f)
}.start()
}
private fun hideView(view: View) {
if (view.visibility == View.INVISIBLE) {
return
}
view.apply {
alpha = 1f
isClickable = false
}.animate().apply {
duration = 300
alpha(0f)
}.withEndAction {
view.visibility = View.INVISIBLE
}
}
fun pageForward() { fun pageForward() {
binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1 binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
} }
@ -326,15 +359,29 @@ class SetupFragment : Fragment() {
private fun setInsets() = private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener( ViewCompat.setOnApplyWindowInsetsListener(
binding.root binding.root
) { view: View, windowInsets: WindowInsetsCompat -> ) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.setPadding(
barInsets.left + cutoutInsets.left, val leftPadding = barInsets.left + cutoutInsets.left
barInsets.top + cutoutInsets.top, val topPadding = barInsets.top + cutoutInsets.top
barInsets.right + cutoutInsets.right, val rightPadding = barInsets.right + cutoutInsets.right
barInsets.bottom + cutoutInsets.bottom val bottomPadding = barInsets.bottom + cutoutInsets.bottom
)
if (resources.getBoolean(R.bool.small_layout)) {
binding.viewPager2
.updatePadding(left = leftPadding, top = topPadding, right = rightPadding)
binding.constraintButtons
.updatePadding(left = leftPadding, right = rightPadding, bottom = bottomPadding)
} else {
binding.viewPager2.updatePadding(top = topPadding, bottom = bottomPadding)
binding.constraintButtons
.updatePadding(
left = leftPadding,
right = rightPadding,
bottom = bottomPadding
)
}
windowInsets windowInsets
} }
} }

View File

@ -14,6 +14,9 @@ class HomeViewModel : ViewModel() {
private val _statusBarShadeVisible = MutableLiveData(true) private val _statusBarShadeVisible = MutableLiveData(true)
val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible val statusBarShadeVisible: LiveData<Boolean> get() = _statusBarShadeVisible
private val _shouldPageForward = MutableLiveData(false)
val shouldPageForward: LiveData<Boolean> get() = _shouldPageForward
var navigatedToSetup = false var navigatedToSetup = false
init { init {
@ -33,4 +36,8 @@ class HomeViewModel : ViewModel() {
} }
_statusBarShadeVisible.value = visible _statusBarShadeVisible.value = visible
} }
fun setShouldPageForward(pageForward: Boolean) {
_shouldPageForward.value = pageForward
}
} }

View File

@ -10,10 +10,20 @@ data class SetupPage(
val buttonIconId: Int, val buttonIconId: Int,
val leftAlignedIcon: Boolean, val leftAlignedIcon: Boolean,
val buttonTextId: Int, val buttonTextId: Int,
val buttonAction: () -> Unit, val buttonAction: (callback: SetupCallback) -> Unit,
val hasWarning: Boolean, val hasWarning: Boolean,
val warningTitleId: Int = 0, val warningTitleId: Int = 0,
val warningDescriptionId: Int = 0, val warningDescriptionId: Int = 0,
val warningHelpLinkId: Int = 0, val warningHelpLinkId: Int = 0,
val taskCompleted: () -> Boolean = { true } val stepCompleted: () -> StepState = { StepState.UNDEFINED }
) )
interface SetupCallback {
fun onStepCompleted()
}
enum class StepState {
COMPLETE,
INCOMPLETE,
UNDEFINED
}

View File

@ -266,74 +266,81 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getGamesDirectory = val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result == null) { if (result != null) {
return@registerForActivityResult processGamesDir(result)
} }
contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
// When a new directory is picked, we currently will reset the existing games
// database. This effectively means that only one game directory is supported.
PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
.putString(GameHelper.KEY_GAME_PATH, result.toString())
.apply()
Toast.makeText(
applicationContext,
R.string.games_dir_selected,
Toast.LENGTH_LONG
).show()
gamesViewModel.reloadGames(true)
} }
fun processGamesDir(result: Uri) {
contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
// When a new directory is picked, we currently will reset the existing games
// database. This effectively means that only one game directory is supported.
PreferenceManager.getDefaultSharedPreferences(applicationContext).edit()
.putString(GameHelper.KEY_GAME_PATH, result.toString())
.apply()
Toast.makeText(
applicationContext,
R.string.games_dir_selected,
Toast.LENGTH_LONG
).show()
gamesViewModel.reloadGames(true)
}
val getProdKey = val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) { if (result != null) {
return@registerForActivityResult processKey(result)
}
if (FileUtil.getExtension(result) != "keys") {
MessageDialogFragment.newInstance(
R.string.reading_keys_failure,
R.string.install_prod_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return@registerForActivityResult
}
contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
if (FileUtil.copyUriToInternalStorage(
applicationContext,
result,
dstPath,
"prod.keys"
)
) {
if (NativeLibrary.reloadKeys()) {
Toast.makeText(
applicationContext,
R.string.install_keys_success,
Toast.LENGTH_SHORT
).show()
gamesViewModel.reloadGames(true)
} else {
MessageDialogFragment.newInstance(
R.string.invalid_keys_error,
R.string.install_keys_failure_description,
R.string.dumping_keys_quickstart_link
).show(supportFragmentManager, MessageDialogFragment.TAG)
}
} }
} }
fun processKey(result: Uri): Boolean {
if (FileUtil.getExtension(result) != "keys") {
MessageDialogFragment.newInstance(
R.string.reading_keys_failure,
R.string.install_prod_keys_failure_extension_description
).show(supportFragmentManager, MessageDialogFragment.TAG)
return false
}
contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
)
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
if (FileUtil.copyUriToInternalStorage(
applicationContext,
result,
dstPath,
"prod.keys"
)
) {
if (NativeLibrary.reloadKeys()) {
Toast.makeText(
applicationContext,
R.string.install_keys_success,
Toast.LENGTH_SHORT
).show()
gamesViewModel.reloadGames(true)
return true
} else {
MessageDialogFragment.newInstance(
R.string.invalid_keys_error,
R.string.install_keys_failure_description,
R.string.dumping_keys_quickstart_link
).show(supportFragmentManager, MessageDialogFragment.TAG)
return false
}
}
return false
}
val getFirmware = val getFirmware =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) { if (result == null) {

View File

@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
package org.yuzu.yuzu_emu.utils
import android.view.View
object ViewUtils {
fun showView(view: View, length: Long = 300) {
view.apply {
alpha = 0f
visibility = View.VISIBLE
isClickable = true
}.animate().apply {
duration = length
alpha(1f)
}.start()
}
fun hideView(view: View, length: Long = 300) {
if (view.visibility == View.INVISIBLE) {
return
}
view.apply {
alpha = 1f
isClickable = false
}.animate().apply {
duration = length
alpha(0f)
}.withEndAction {
view.visibility = View.INVISIBLE
}.start()
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_root" android:id="@+id/setup_root"
@ -8,33 +8,39 @@
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2" android:id="@+id/viewPager2"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp" android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent" android:layout_alignParentTop="true"
app:layout_constraintEnd_toEndOf="parent" android:layout_alignParentBottom="true"
app:layout_constraintStart_toStartOf="parent" android:clipToPadding="false" />
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton <androidx.constraintlayout.widget.ConstraintLayout
style="@style/Widget.Material3.Button.TextButton" android:id="@+id/constraint_buttons"
android:id="@+id/button_next" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_alignParentBottom="true"
android:text="@string/next" android:layout_margin="8dp">
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_back" android:id="@+id/button_next"
style="@style/Widget.Material3.Button.TextButton" style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:text="@string/next"
android:text="@string/back" android:visibility="invisible"
android:visibility="invisible" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" />
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> <com.google.android.material.button.MaterialButton
android:id="@+id/button_back"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/back"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>

View File

@ -21,45 +21,76 @@
</LinearLayout> </LinearLayout>
<LinearLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_weight="1" android:layout_weight="1">
android:orientation="vertical"
android:gravity="center">
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.DisplaySmall"
android:id="@+id/text_title" android:id="@+id/text_title"
android:layout_width="match_parent" style="@style/TextAppearance.Material3.DisplaySmall"
android:layout_height="wrap_content" android:layout_width="0dp"
android:textAlignment="center" android:layout_height="0dp"
android:gravity="center"
android:textColor="?attr/colorOnSurface" android:textColor="?attr/colorOnSurface"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/text_description"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_weight="2"
tools:text="@string/welcome" /> tools:text="@string/welcome" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleLarge"
android:id="@+id/text_description" android:id="@+id/text_description"
android:layout_width="match_parent" style="@style/TextAppearance.Material3.TitleLarge"
android:layout_height="wrap_content" android:layout_width="0dp"
android:layout_marginTop="16dp" android:layout_height="0dp"
android:paddingHorizontal="32dp" android:gravity="center"
android:textAlignment="center" android:textSize="20sp"
android:textSize="26sp" android:paddingHorizontal="16dp"
app:lineHeight="40sp" app:layout_constraintBottom_toTopOf="@+id/button_action"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title"
app:layout_constraintVertical_weight="2"
app:lineHeight="30sp"
tools:text="@string/welcome_description" /> tools:text="@string/welcome_description" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_confirmation"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="0dp"
android:layout_height="0dp"
android:paddingHorizontal="16dp"
android:paddingBottom="20dp"
android:gravity="center"
android:textSize="30sp"
android:visibility="invisible"
android:text="@string/step_complete"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_description"
app:layout_constraintVertical_weight="1"
app:lineHeight="30sp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_action" android:id="@+id/button_action"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="56dp" android:layout_height="56dp"
android:layout_marginTop="32dp" android:layout_marginTop="16dp"
android:layout_marginBottom="48dp"
android:textSize="20sp" android:textSize="20sp"
app:iconSize="24sp"
app:iconGravity="end" app:iconGravity="end"
app:iconSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_description"
tools:text="Get started" /> tools:text="Get started" />
</LinearLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout> </LinearLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout <RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_root" android:id="@+id/setup_root"
@ -8,35 +8,39 @@
<androidx.viewpager2.widget.ViewPager2 <androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2" android:id="@+id/viewPager2"
android:layout_width="0dp" android:layout_width="match_parent"
android:layout_height="0dp"
android:clipToPadding="false"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toTopOf="@+id/button_next"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton"
android:id="@+id/button_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_above="@+id/constraint_buttons"
android:text="@string/next" android:layout_alignParentTop="true"
android:visibility="invisible" android:clipToPadding="false" />
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton <androidx.constraintlayout.widget.ConstraintLayout
style="@style/Widget.Material3.Button.TextButton" android:id="@+id/constraint_buttons"
android:id="@+id/button_back" android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="12dp" android:layout_margin="8dp"
android:text="@string/back" android:layout_alignParentBottom="true">
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> <com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/next"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_back"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/back"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</RelativeLayout>

View File

@ -21,11 +21,12 @@
app:layout_constraintVertical_chainStyle="spread" app:layout_constraintVertical_chainStyle="spread"
app:layout_constraintWidth_max="220dp" app:layout_constraintWidth_max="220dp"
app:layout_constraintWidth_min="110dp" app:layout_constraintWidth_min="110dp"
app:layout_constraintVertical_weight="3" /> app:layout_constraintVertical_weight="3"
tools:src="@drawable/ic_notification" />
<com.google.android.material.textview.MaterialTextView <com.google.android.material.textview.MaterialTextView
android:id="@+id/text_title" android:id="@+id/text_title"
style="@style/TextAppearance.Material3.DisplayMedium" style="@style/TextAppearance.Material3.DisplaySmall"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:textAlignment="center" android:textAlignment="center"
@ -44,23 +45,42 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
android:textAlignment="center" android:textAlignment="center"
android:textSize="26sp" android:textSize="20sp"
android:paddingHorizontal="16dp" android:paddingHorizontal="16dp"
app:layout_constraintBottom_toTopOf="@+id/button_action" app:layout_constraintBottom_toTopOf="@+id/button_action"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_title" app:layout_constraintTop_toBottomOf="@+id/text_title"
app:layout_constraintVertical_weight="2" app:layout_constraintVertical_weight="2"
app:lineHeight="40sp" app:lineHeight="30sp"
tools:text="@string/welcome_description" /> tools:text="@string/welcome_description" />
<com.google.android.material.textview.MaterialTextView
android:id="@+id/text_confirmation"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:paddingHorizontal="16dp"
android:paddingTop="24dp"
android:textAlignment="center"
android:textSize="30sp"
android:visibility="invisible"
android:text="@string/step_complete"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/text_description"
app:layout_constraintVertical_weight="1"
app:lineHeight="30sp" />
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
android:id="@+id/button_action" android:id="@+id/button_action"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="56dp" android:layout_height="56dp"
android:textSize="20sp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginBottom="48dp" android:layout_marginBottom="48dp"
android:textSize="20sp"
app:iconGravity="end" app:iconGravity="end"
app:iconSize="24sp" app:iconSize="24sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"

View File

@ -29,6 +29,7 @@
<string name="back">Back</string> <string name="back">Back</string>
<string name="add_games">Add Games</string> <string name="add_games">Add Games</string>
<string name="add_games_description">Select your games folder</string> <string name="add_games_description">Select your games folder</string>
<string name="step_complete">Complete!</string>
<!-- Home strings --> <!-- Home strings -->
<string name="home_games">Games</string> <string name="home_games">Games</string>