forked from ShuriZma/suyu
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.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.res.ResourcesCompat
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
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.StepState
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
@ -26,7 +32,7 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
holder.bind(pages[position])
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root), SetupCallback {
lateinit var page: SetupPage
init {
@ -35,6 +41,12 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
fun bind(page: SetupPage) {
this.page = page
if (page.stepCompleted.invoke() == StepState.COMPLETE) {
binding.buttonAction.visibility = View.INVISIBLE
binding.textConfirmation.visibility = View.VISIBLE
}
binding.icon.setImageDrawable(
ResourcesCompat.getDrawable(
activity.resources,
@ -62,9 +74,15 @@ class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>)
MaterialButton.ICON_GRAVITY_END
}
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.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updatePadding
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
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.features.settings.model.Settings
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.StepState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.GameHelper
import org.yuzu.yuzu_emu.utils.ViewUtils
class SetupFragment : Fragment() {
private var _binding: FragmentSetupBinding? = null
@ -112,14 +116,22 @@ class SetupFragment : Fragment() {
0,
false,
R.string.give_permission,
{ permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) },
{
notificationCallback = it
permissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
},
true,
R.string.notification_warning,
R.string.notification_warning_description,
0,
{
NotificationManagerCompat.from(requireContext())
if (NotificationManagerCompat.from(requireContext())
.areNotificationsEnabled()
) {
StepState.COMPLETE
} else {
StepState.INCOMPLETE
}
}
)
)
@ -133,12 +145,22 @@ class SetupFragment : Fragment() {
R.drawable.ic_add,
true,
R.string.select_keys,
{ mainActivity.getProdKey.launch(arrayOf("*/*")) },
{
keyCallback = it
getProdKey.launch(arrayOf("*/*"))
},
true,
R.string.install_prod_keys_warning,
R.string.install_prod_keys_warning_description,
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(
@ -150,9 +172,8 @@ class SetupFragment : Fragment() {
true,
R.string.add_games,
{
mainActivity.getGamesDirectory.launch(
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data
)
gamesDirCallback = it
getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
},
true,
R.string.add_games_warning,
@ -163,7 +184,11 @@ class SetupFragment : Fragment() {
PreferenceManager.getDefaultSharedPreferences(
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 {
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
offscreenPageLimit = 2
@ -194,15 +226,15 @@ class SetupFragment : Fragment() {
super.onPageSelected(position)
if (position == 1 && previousPosition == 0) {
showView(binding.buttonNext)
showView(binding.buttonBack)
ViewUtils.showView(binding.buttonNext)
ViewUtils.showView(binding.buttonBack)
} else if (position == 0 && previousPosition == 1) {
hideView(binding.buttonBack)
hideView(binding.buttonNext)
ViewUtils.hideView(binding.buttonBack)
ViewUtils.hideView(binding.buttonNext)
} 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) {
showView(binding.buttonNext)
ViewUtils.showView(binding.buttonNext)
}
previousPosition = position
@ -215,7 +247,8 @@ class SetupFragment : Fragment() {
// Checks if the user has completed the task on the current page
if (currentPage.hasWarning) {
if (currentPage.taskCompleted.invoke()) {
val stepState = currentPage.stepCompleted.invoke()
if (stepState != StepState.INCOMPLETE) {
pageForward()
return@setOnClickListener
}
@ -264,9 +297,15 @@ class SetupFragment : Fragment() {
_binding = null
}
private lateinit var notificationCallback: SetupCallback
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
private val permissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) {
if (it) {
notificationCallback.onStepCompleted()
}
if (!it &&
!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() {
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
.putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
@ -284,33 +344,6 @@ class SetupFragment : Fragment() {
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() {
binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
}
@ -326,15 +359,29 @@ class SetupFragment : Fragment() {
private fun setInsets() =
ViewCompat.setOnApplyWindowInsetsListener(
binding.root
) { view: View, windowInsets: WindowInsetsCompat ->
) { _: View, windowInsets: WindowInsetsCompat ->
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())
view.setPadding(
barInsets.left + cutoutInsets.left,
barInsets.top + cutoutInsets.top,
barInsets.right + cutoutInsets.right,
barInsets.bottom + cutoutInsets.bottom
val leftPadding = barInsets.left + cutoutInsets.left
val topPadding = barInsets.top + cutoutInsets.top
val rightPadding = barInsets.right + cutoutInsets.right
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
}
}

View File

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

View File

@ -10,10 +10,20 @@ data class SetupPage(
val buttonIconId: Int,
val leftAlignedIcon: Boolean,
val buttonTextId: Int,
val buttonAction: () -> Unit,
val buttonAction: (callback: SetupCallback) -> Unit,
val hasWarning: Boolean,
val warningTitleId: Int = 0,
val warningDescriptionId: 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,10 +266,12 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getGamesDirectory =
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
if (result == null) {
return@registerForActivityResult
if (result != null) {
processGamesDir(result)
}
}
fun processGamesDir(result: Uri) {
contentResolver.takePersistableUriPermission(
result,
Intent.FLAG_GRANT_READ_URI_PERMISSION
@ -292,16 +294,18 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
val getProdKey =
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
if (result == null) {
return@registerForActivityResult
if (result != null) {
processKey(result)
}
}
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@registerForActivityResult
return false
}
contentResolver.takePersistableUriPermission(
@ -324,14 +328,17 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
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 =

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"?>
<androidx.constraintlayout.widget.ConstraintLayout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_root"
@ -8,19 +8,24 @@
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentBottom="true"
android:clipToPadding="false" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_margin="8dp">
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton"
android:id="@+id/button_next"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:text="@string/next"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
@ -31,10 +36,11 @@
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
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
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:gravity="center">
android:layout_weight="1">
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.DisplaySmall"
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
style="@style/TextAppearance.Material3.DisplaySmall"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:textColor="?attr/colorOnSurface"
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" />
<com.google.android.material.textview.MaterialTextView
style="@style/TextAppearance.Material3.TitleLarge"
android:id="@+id/text_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:paddingHorizontal="32dp"
android:textAlignment="center"
android:textSize="26sp"
app:lineHeight="40sp"
style="@style/TextAppearance.Material3.TitleLarge"
android:layout_width="0dp"
android:layout_height="0dp"
android:gravity="center"
android:textSize="20sp"
android:paddingHorizontal="16dp"
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" />
<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
android:id="@+id/button_action"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:layout_marginTop="32dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="48dp"
android:textSize="20sp"
app:iconSize="24sp"
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" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/setup_root"
@ -8,35 +8,39 @@
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager2"
android:layout_width="0dp"
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" />
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="@+id/constraint_buttons"
android:layout_alignParentTop="true"
android:clipToPadding="false" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/constraint_buttons"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:layout_alignParentBottom="true">
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton"
android:id="@+id/button_next"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="@string/next"
android:visibility="invisible"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<com.google.android.material.button.MaterialButton
style="@style/Widget.Material3.Button.TextButton"
android:id="@+id/button_back"
style="@style/Widget.Material3.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="12dp"
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_constraintWidth_max="220dp"
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
android:id="@+id/text_title"
style="@style/TextAppearance.Material3.DisplayMedium"
style="@style/TextAppearance.Material3.DisplaySmall"
android:layout_width="0dp"
android:layout_height="0dp"
android:textAlignment="center"
@ -44,23 +45,42 @@
android:layout_width="0dp"
android:layout_height="0dp"
android:textAlignment="center"
android:textSize="26sp"
android:textSize="20sp"
android:paddingHorizontal="16dp"
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="40sp"
app:lineHeight="30sp"
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
android:id="@+id/button_action"
android:layout_width="wrap_content"
android:layout_height="56dp"
android:textSize="20sp"
android:layout_marginTop="16dp"
android:layout_marginBottom="48dp"
android:textSize="20sp"
app:iconGravity="end"
app:iconSize="24sp"
app:layout_constraintBottom_toBottomOf="parent"

View File

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