mirror of
https://git.suyu.dev/suyu/suyu.git
synced 2024-11-29 06:46:22 -05:00
android: First time setup screen
This commit is contained in:
parent
766655fa41
commit
59525ddbeb
19 changed files with 769 additions and 163 deletions
|
@ -0,0 +1,70 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.adapters
|
||||||
|
|
||||||
|
import android.text.Html
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
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.SetupPage
|
||||||
|
|
||||||
|
class SetupAdapter(val activity: AppCompatActivity, val pages: List<SetupPage>) :
|
||||||
|
RecyclerView.Adapter<SetupAdapter.SetupPageViewHolder>() {
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SetupPageViewHolder {
|
||||||
|
val binding = PageSetupBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
return SetupPageViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int = pages.size
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: SetupPageViewHolder, position: Int) =
|
||||||
|
holder.bind(pages[position])
|
||||||
|
|
||||||
|
inner class SetupPageViewHolder(val binding: PageSetupBinding) :
|
||||||
|
RecyclerView.ViewHolder(binding.root) {
|
||||||
|
lateinit var page: SetupPage
|
||||||
|
|
||||||
|
init {
|
||||||
|
itemView.tag = this
|
||||||
|
}
|
||||||
|
|
||||||
|
fun bind(page: SetupPage) {
|
||||||
|
this.page = page
|
||||||
|
binding.icon.setImageDrawable(
|
||||||
|
ResourcesCompat.getDrawable(
|
||||||
|
activity.resources,
|
||||||
|
page.iconId,
|
||||||
|
activity.theme
|
||||||
|
)
|
||||||
|
)
|
||||||
|
binding.textTitle.text = activity.resources.getString(page.titleId)
|
||||||
|
binding.textDescription.text =
|
||||||
|
Html.fromHtml(activity.resources.getString(page.descriptionId), 0)
|
||||||
|
|
||||||
|
binding.buttonAction.apply {
|
||||||
|
text = activity.resources.getString(page.buttonTextId)
|
||||||
|
if (page.buttonIconId != 0) {
|
||||||
|
icon = ResourcesCompat.getDrawable(
|
||||||
|
activity.resources,
|
||||||
|
page.buttonIconId,
|
||||||
|
activity.theme
|
||||||
|
)
|
||||||
|
}
|
||||||
|
iconGravity =
|
||||||
|
if (page.leftAlignedIcon) {
|
||||||
|
MaterialButton.ICON_GRAVITY_START
|
||||||
|
} else {
|
||||||
|
MaterialButton.ICON_GRAVITY_END
|
||||||
|
}
|
||||||
|
setOnClickListener {
|
||||||
|
page.buttonAction.invoke()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,39 +10,26 @@ import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.activityViewModels
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.withContext
|
|
||||||
import org.yuzu.yuzu_emu.NativeLibrary
|
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.HomeOptionAdapter
|
import org.yuzu.yuzu_emu.adapters.HomeOptionAdapter
|
||||||
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
|
||||||
import org.yuzu.yuzu_emu.databinding.FragmentOptionsBinding
|
import org.yuzu.yuzu_emu.databinding.FragmentOptionsBinding
|
||||||
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
|
||||||
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
|
||||||
import org.yuzu.yuzu_emu.model.GamesViewModel
|
|
||||||
import org.yuzu.yuzu_emu.model.HomeOption
|
import org.yuzu.yuzu_emu.model.HomeOption
|
||||||
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
|
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||||
import org.yuzu.yuzu_emu.utils.FileUtil
|
|
||||||
import org.yuzu.yuzu_emu.utils.GameHelper
|
|
||||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class OptionsFragment : Fragment() {
|
class OptionsFragment : Fragment() {
|
||||||
private var _binding: FragmentOptionsBinding? = null
|
private var _binding: FragmentOptionsBinding? = null
|
||||||
private val binding get() = _binding!!
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
private lateinit var mainActivity: MainActivity
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
|
@ -54,22 +41,24 @@ class OptionsFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
mainActivity = requireActivity() as MainActivity
|
||||||
|
|
||||||
val optionsList: List<HomeOption> = listOf(
|
val optionsList: List<HomeOption> = listOf(
|
||||||
HomeOption(
|
HomeOption(
|
||||||
R.string.add_games,
|
R.string.add_games,
|
||||||
R.string.add_games_description,
|
R.string.add_games_description,
|
||||||
R.drawable.ic_add
|
R.drawable.ic_add
|
||||||
) { getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||||
HomeOption(
|
HomeOption(
|
||||||
R.string.install_prod_keys,
|
R.string.install_prod_keys,
|
||||||
R.string.install_prod_keys_description,
|
R.string.install_prod_keys_description,
|
||||||
R.drawable.ic_unlock
|
R.drawable.ic_unlock
|
||||||
) { getProdKey.launch(arrayOf("*/*")) },
|
) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
|
||||||
HomeOption(
|
HomeOption(
|
||||||
R.string.install_amiibo_keys,
|
R.string.install_amiibo_keys,
|
||||||
R.string.install_amiibo_keys_description,
|
R.string.install_amiibo_keys_description,
|
||||||
R.drawable.ic_nfc
|
R.drawable.ic_nfc
|
||||||
) { getAmiiboKey.launch(arrayOf("*/*")) },
|
) { mainActivity.getAmiiboKey.launch(arrayOf("*/*")) },
|
||||||
HomeOption(
|
HomeOption(
|
||||||
R.string.install_gpu_driver,
|
R.string.install_gpu_driver,
|
||||||
R.string.install_gpu_driver_description,
|
R.string.install_gpu_driver_description,
|
||||||
|
@ -115,7 +104,7 @@ class OptionsFragment : Fragment() {
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
.setNeutralButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
|
.setNeutralButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
|
||||||
getDriver.launch(arrayOf("application/zip"))
|
mainActivity.getDriver.launch(arrayOf("application/zip"))
|
||||||
}
|
}
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -131,144 +120,4 @@ class OptionsFragment : Fragment() {
|
||||||
)
|
)
|
||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
|
|
||||||
private val getGamesDirectory =
|
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
|
||||||
if (result == null)
|
|
||||||
return@registerForActivityResult
|
|
||||||
|
|
||||||
val takeFlags =
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
requireActivity().contentResolver.takePersistableUriPermission(
|
|
||||||
result,
|
|
||||||
takeFlags
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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(requireContext()).edit()
|
|
||||||
.putString(GameHelper.KEY_GAME_PATH, result.toString())
|
|
||||||
.apply()
|
|
||||||
|
|
||||||
gamesViewModel.reloadGames(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val getProdKey =
|
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
|
||||||
if (result == null)
|
|
||||||
return@registerForActivityResult
|
|
||||||
|
|
||||||
val takeFlags =
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
requireActivity().contentResolver.takePersistableUriPermission(
|
|
||||||
result,
|
|
||||||
takeFlags
|
|
||||||
)
|
|
||||||
|
|
||||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
|
||||||
if (FileUtil.copyUriToInternalStorage(requireContext(), result, dstPath, "prod.keys")) {
|
|
||||||
if (NativeLibrary.reloadKeys()) {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.install_keys_success,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
gamesViewModel.reloadGames(true)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.install_keys_failure,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val getAmiiboKey =
|
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
|
||||||
if (result == null)
|
|
||||||
return@registerForActivityResult
|
|
||||||
|
|
||||||
val takeFlags =
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
requireActivity().contentResolver.takePersistableUriPermission(
|
|
||||||
result,
|
|
||||||
takeFlags
|
|
||||||
)
|
|
||||||
|
|
||||||
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
|
||||||
if (FileUtil.copyUriToInternalStorage(
|
|
||||||
requireContext(),
|
|
||||||
result,
|
|
||||||
dstPath,
|
|
||||||
"key_retail.bin"
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
if (NativeLibrary.reloadKeys()) {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.install_keys_success,
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.install_amiibo_keys_failure,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val getDriver =
|
|
||||||
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
|
||||||
if (result == null)
|
|
||||||
return@registerForActivityResult
|
|
||||||
|
|
||||||
val takeFlags =
|
|
||||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
||||||
requireActivity().contentResolver.takePersistableUriPermission(
|
|
||||||
result,
|
|
||||||
takeFlags
|
|
||||||
)
|
|
||||||
|
|
||||||
val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
|
|
||||||
progressBinding.progressBar.isIndeterminate = true
|
|
||||||
val installationDialog = MaterialAlertDialogBuilder(requireContext())
|
|
||||||
.setTitle(R.string.installing_driver)
|
|
||||||
.setView(progressBinding.root)
|
|
||||||
.show()
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
|
||||||
withContext(Dispatchers.IO) {
|
|
||||||
// Ignore file exceptions when a user selects an invalid zip
|
|
||||||
try {
|
|
||||||
GpuDriverHelper.installCustomDriver(requireContext(), result)
|
|
||||||
} catch (_: IOException) {
|
|
||||||
}
|
|
||||||
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
installationDialog.dismiss()
|
|
||||||
|
|
||||||
val driverName = GpuDriverHelper.customDriverName
|
|
||||||
if (driverName != null) {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
getString(
|
|
||||||
R.string.select_gpu_driver_install_success,
|
|
||||||
driverName
|
|
||||||
),
|
|
||||||
Toast.LENGTH_SHORT
|
|
||||||
).show()
|
|
||||||
} else {
|
|
||||||
Toast.makeText(
|
|
||||||
requireContext(),
|
|
||||||
R.string.select_gpu_driver_error,
|
|
||||||
Toast.LENGTH_LONG
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,206 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.fragments
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.activity.OnBackPressedCallback
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.ViewCompat
|
||||||
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
|
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
|
||||||
|
import com.google.android.material.transition.MaterialFadeThrough
|
||||||
|
import org.yuzu.yuzu_emu.R
|
||||||
|
import org.yuzu.yuzu_emu.YuzuApplication
|
||||||
|
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.SetupPage
|
||||||
|
import org.yuzu.yuzu_emu.ui.main.MainActivity
|
||||||
|
|
||||||
|
class SetupFragment : Fragment() {
|
||||||
|
private var _binding: FragmentSetupBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private lateinit var mainActivity: MainActivity
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
exitTransition = MaterialFadeThrough()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = FragmentSetupBinding.inflate(layoutInflater)
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
mainActivity = requireActivity() as MainActivity
|
||||||
|
|
||||||
|
homeViewModel.setNavigationVisibility(false)
|
||||||
|
|
||||||
|
requireActivity().onBackPressedDispatcher.addCallback(
|
||||||
|
viewLifecycleOwner,
|
||||||
|
object : OnBackPressedCallback(true) {
|
||||||
|
override fun handleOnBackPressed() {
|
||||||
|
if (binding.viewPager2.currentItem > 0) {
|
||||||
|
pageBackward()
|
||||||
|
} else {
|
||||||
|
requireActivity().finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
requireActivity().window.navigationBarColor =
|
||||||
|
ContextCompat.getColor(requireContext(), android.R.color.transparent)
|
||||||
|
|
||||||
|
val pages = listOf(
|
||||||
|
SetupPage(
|
||||||
|
R.drawable.ic_yuzu_title,
|
||||||
|
R.string.welcome,
|
||||||
|
R.string.welcome_description,
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
R.string.get_started
|
||||||
|
) { pageForward() },
|
||||||
|
SetupPage(
|
||||||
|
R.drawable.ic_key,
|
||||||
|
R.string.keys,
|
||||||
|
R.string.keys_description,
|
||||||
|
R.drawable.ic_add,
|
||||||
|
true,
|
||||||
|
R.string.select_keys
|
||||||
|
) { mainActivity.getProdKey.launch(arrayOf("*/*")) },
|
||||||
|
SetupPage(
|
||||||
|
R.drawable.ic_controller,
|
||||||
|
R.string.games,
|
||||||
|
R.string.games_description,
|
||||||
|
R.drawable.ic_add,
|
||||||
|
true,
|
||||||
|
R.string.add_games
|
||||||
|
) { mainActivity.getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data) },
|
||||||
|
SetupPage(
|
||||||
|
R.drawable.ic_check,
|
||||||
|
R.string.done,
|
||||||
|
R.string.done_description,
|
||||||
|
R.drawable.ic_arrow_forward,
|
||||||
|
false,
|
||||||
|
R.string.text_continue
|
||||||
|
) { finishSetup() }
|
||||||
|
)
|
||||||
|
binding.viewPager2.apply {
|
||||||
|
adapter = SetupAdapter(requireActivity() as AppCompatActivity, pages)
|
||||||
|
offscreenPageLimit = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.viewPager2.registerOnPageChangeCallback(object : OnPageChangeCallback() {
|
||||||
|
override fun onPageScrolled(
|
||||||
|
position: Int,
|
||||||
|
positionOffset: Float,
|
||||||
|
positionOffsetPixels: Int
|
||||||
|
) {
|
||||||
|
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
|
||||||
|
if (position == 0) {
|
||||||
|
hideView(binding.buttonBack)
|
||||||
|
} else {
|
||||||
|
showView(binding.buttonBack)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position == pages.size - 1 || position == 0) {
|
||||||
|
hideView(binding.buttonNext)
|
||||||
|
} else {
|
||||||
|
showView(binding.buttonNext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
binding.buttonNext.setOnClickListener { pageForward() }
|
||||||
|
binding.buttonBack.setOnClickListener { pageBackward() }
|
||||||
|
|
||||||
|
if (binding.viewPager2.currentItem == 0) {
|
||||||
|
binding.buttonNext.visibility = View.INVISIBLE
|
||||||
|
binding.buttonBack.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
setInsets()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun finishSetup() {
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
|
||||||
|
.putBoolean(Settings.PREF_FIRST_APP_LAUNCH, false)
|
||||||
|
.apply()
|
||||||
|
mainActivity.finishSetup(binding.root.findNavController())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showView(view: View) {
|
||||||
|
if (view.visibility == View.VISIBLE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
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.GONE) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
view.apply {
|
||||||
|
alpha = 1f
|
||||||
|
isClickable = false
|
||||||
|
}.animate().apply {
|
||||||
|
duration = 300
|
||||||
|
alpha(0f)
|
||||||
|
}.withEndAction {
|
||||||
|
view.visibility = View.INVISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pageForward() {
|
||||||
|
binding.viewPager2.currentItem = binding.viewPager2.currentItem + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pageBackward() {
|
||||||
|
binding.viewPager2.currentItem = binding.viewPager2.currentItem - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setInsets() =
|
||||||
|
ViewCompat.setOnApplyWindowInsetsListener(binding.setupRoot) { view: View, windowInsets: WindowInsetsCompat ->
|
||||||
|
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
||||||
|
view.setPadding(
|
||||||
|
insets.left,
|
||||||
|
insets.top,
|
||||||
|
insets.right,
|
||||||
|
insets.bottom
|
||||||
|
)
|
||||||
|
windowInsets
|
||||||
|
}
|
||||||
|
}
|
|
@ -11,6 +11,8 @@ 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
|
||||||
|
|
||||||
|
var navigatedToSetup = false
|
||||||
|
|
||||||
fun setNavigationVisibility(visible: Boolean) {
|
fun setNavigationVisibility(visible: Boolean) {
|
||||||
if (_navigationVisible.value == visible) {
|
if (_navigationVisible.value == visible) {
|
||||||
return
|
return
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
package org.yuzu.yuzu_emu.model
|
||||||
|
|
||||||
|
data class SetupPage(
|
||||||
|
val iconId: Int,
|
||||||
|
val titleId: Int,
|
||||||
|
val descriptionId: Int,
|
||||||
|
val buttonIconId: Int,
|
||||||
|
val leftAlignedIcon: Boolean,
|
||||||
|
val buttonTextId: Int,
|
||||||
|
val buttonAction: () -> Unit
|
||||||
|
)
|
|
@ -18,6 +18,7 @@ import androidx.fragment.app.activityViewModels
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.search.SearchView
|
import com.google.android.material.search.SearchView
|
||||||
import com.google.android.material.search.SearchView.TransitionState
|
import com.google.android.material.search.SearchView.TransitionState
|
||||||
|
import com.google.android.material.transition.MaterialFadeThrough
|
||||||
import info.debatty.java.stringsimilarity.Jaccard
|
import info.debatty.java.stringsimilarity.Jaccard
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||||
|
@ -35,6 +36,11 @@ class GamesFragment : Fragment() {
|
||||||
private val gamesViewModel: GamesViewModel by activityViewModels()
|
private val gamesViewModel: GamesViewModel by activityViewModels()
|
||||||
private val homeViewModel: HomeViewModel by activityViewModels()
|
private val homeViewModel: HomeViewModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
enterTransition = MaterialFadeThrough()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater,
|
inflater: LayoutInflater,
|
||||||
container: ViewGroup?,
|
container: ViewGroup?,
|
||||||
|
|
|
@ -3,10 +3,13 @@
|
||||||
|
|
||||||
package org.yuzu.yuzu_emu.ui.main
|
package org.yuzu.yuzu_emu.ui.main
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup.MarginLayoutParams
|
import android.view.ViewGroup.MarginLayoutParams
|
||||||
import android.view.animation.PathInterpolator
|
import android.view.animation.PathInterpolator
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
@ -14,20 +17,33 @@ import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.navigation.NavController
|
||||||
import androidx.navigation.fragment.NavHostFragment
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
|
import androidx.preference.PreferenceManager
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.elevation.ElevationOverlayProvider
|
import com.google.android.material.elevation.ElevationOverlayProvider
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import org.yuzu.yuzu_emu.NativeLibrary
|
||||||
import org.yuzu.yuzu_emu.R
|
import org.yuzu.yuzu_emu.R
|
||||||
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
import org.yuzu.yuzu_emu.activities.EmulationActivity
|
||||||
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
|
||||||
|
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
|
||||||
|
import org.yuzu.yuzu_emu.features.settings.model.Settings
|
||||||
|
import org.yuzu.yuzu_emu.model.GamesViewModel
|
||||||
import org.yuzu.yuzu_emu.model.HomeViewModel
|
import org.yuzu.yuzu_emu.model.HomeViewModel
|
||||||
import org.yuzu.yuzu_emu.utils.*
|
import org.yuzu.yuzu_emu.utils.*
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var binding: ActivityMainBinding
|
private lateinit var binding: ActivityMainBinding
|
||||||
|
|
||||||
private val homeViewModel: HomeViewModel by viewModels()
|
private val homeViewModel: HomeViewModel by viewModels()
|
||||||
|
private val gamesViewModel: GamesViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
val splashScreen = installSplashScreen()
|
val splashScreen = installSplashScreen()
|
||||||
|
@ -52,10 +68,9 @@ class MainActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set up a central host fragment that is controlled via bottom navigation with xml navigation
|
|
||||||
val navHostFragment =
|
val navHostFragment =
|
||||||
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
||||||
binding.navigationBar.setupWithNavController(navHostFragment.navController)
|
setUpNavigation(navHostFragment.navController)
|
||||||
|
|
||||||
binding.statusBarShade.setBackgroundColor(
|
binding.statusBarShade.setBackgroundColor(
|
||||||
ThemeHelper.getColorWithOpacity(
|
ThemeHelper.getColorWithOpacity(
|
||||||
|
@ -85,6 +100,32 @@ class MainActivity : AppCompatActivity() {
|
||||||
setInsets()
|
setInsets()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun finishSetup(navController: NavController) {
|
||||||
|
navController.navigate(R.id.action_firstTimeSetupFragment_to_gamesFragment)
|
||||||
|
binding.navigationBar.setupWithNavController(navController)
|
||||||
|
showNavigation(true)
|
||||||
|
|
||||||
|
ThemeHelper.setNavigationBarColor(
|
||||||
|
this,
|
||||||
|
ElevationOverlayProvider(binding.navigationBar.context).compositeOverlay(
|
||||||
|
MaterialColors.getColor(binding.navigationBar, R.attr.colorSurface),
|
||||||
|
binding.navigationBar.elevation
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUpNavigation(navController: NavController) {
|
||||||
|
val firstTimeSetup = PreferenceManager.getDefaultSharedPreferences(applicationContext)
|
||||||
|
.getBoolean(Settings.PREF_FIRST_APP_LAUNCH, true)
|
||||||
|
|
||||||
|
if (firstTimeSetup && !homeViewModel.navigatedToSetup) {
|
||||||
|
navController.navigate(R.id.firstTimeSetupFragment)
|
||||||
|
homeViewModel.navigatedToSetup = true
|
||||||
|
} else {
|
||||||
|
binding.navigationBar.setupWithNavController(navController)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun showNavigation(visible: Boolean) {
|
private fun showNavigation(visible: Boolean) {
|
||||||
binding.navigationBar.animate().apply {
|
binding.navigationBar.animate().apply {
|
||||||
if (visible) {
|
if (visible) {
|
||||||
|
@ -138,4 +179,150 @@ class MainActivity : AppCompatActivity() {
|
||||||
binding.statusBarShade.layoutParams = mlpShade
|
binding.statusBarShade.layoutParams = mlpShade
|
||||||
windowInsets
|
windowInsets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val getGamesDirectory =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { result ->
|
||||||
|
if (result == null)
|
||||||
|
return@registerForActivityResult
|
||||||
|
|
||||||
|
val takeFlags =
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
contentResolver.takePersistableUriPermission(
|
||||||
|
result,
|
||||||
|
takeFlags
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
|
if (result == null)
|
||||||
|
return@registerForActivityResult
|
||||||
|
|
||||||
|
val takeFlags =
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
contentResolver.takePersistableUriPermission(
|
||||||
|
result,
|
||||||
|
takeFlags
|
||||||
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
R.string.install_keys_failure,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val getAmiiboKey =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
|
if (result == null)
|
||||||
|
return@registerForActivityResult
|
||||||
|
|
||||||
|
val takeFlags =
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
contentResolver.takePersistableUriPermission(
|
||||||
|
result,
|
||||||
|
takeFlags
|
||||||
|
)
|
||||||
|
|
||||||
|
val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
||||||
|
if (FileUtil.copyUriToInternalStorage(
|
||||||
|
applicationContext,
|
||||||
|
result,
|
||||||
|
dstPath,
|
||||||
|
"key_retail.bin"
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
if (NativeLibrary.reloadKeys()) {
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
R.string.install_keys_success,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
R.string.install_amiibo_keys_failure,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val getDriver =
|
||||||
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
|
||||||
|
if (result == null)
|
||||||
|
return@registerForActivityResult
|
||||||
|
|
||||||
|
val takeFlags =
|
||||||
|
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
contentResolver.takePersistableUriPermission(
|
||||||
|
result,
|
||||||
|
takeFlags
|
||||||
|
)
|
||||||
|
|
||||||
|
val progressBinding = DialogProgressBarBinding.inflate(layoutInflater)
|
||||||
|
progressBinding.progressBar.isIndeterminate = true
|
||||||
|
val installationDialog = MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(R.string.installing_driver)
|
||||||
|
.setView(progressBinding.root)
|
||||||
|
.show()
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
// Ignore file exceptions when a user selects an invalid zip
|
||||||
|
try {
|
||||||
|
GpuDriverHelper.installCustomDriver(applicationContext, result)
|
||||||
|
} catch (_: IOException) {
|
||||||
|
}
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
installationDialog.dismiss()
|
||||||
|
|
||||||
|
val driverName = GpuDriverHelper.customDriverName
|
||||||
|
if (driverName != null) {
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
getString(
|
||||||
|
R.string.select_gpu_driver_install_success,
|
||||||
|
driverName
|
||||||
|
),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
R.string.select_gpu_driver_error,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
10
src/android/app/src/main/res/drawable/ic_arrow_forward.xml
Normal file
10
src/android/app/src/main/res/drawable/ic_arrow_forward.xml
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:autoMirrored="true"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorControlNormal"
|
||||||
|
android:pathData="M12,4l-1.41,1.41L16.17,11H4v2h12.17l-5.58,5.59L12,20l8,-8z" />
|
||||||
|
</vector>
|
9
src/android/app/src/main/res/drawable/ic_check.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_check.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorOnSurface"
|
||||||
|
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
|
||||||
|
</vector>
|
|
@ -4,6 +4,6 @@
|
||||||
android:viewportHeight="24"
|
android:viewportHeight="24"
|
||||||
android:viewportWidth="24">
|
android:viewportWidth="24">
|
||||||
<path
|
<path
|
||||||
android:fillColor="?attr/colorControlNormal"
|
android:fillColor="?attr/colorOnSurface"
|
||||||
android:pathData="M21,6L3,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.1 -0.9,-2 -2,-2zM11,13L8,13v3L6,16v-3L3,13v-2h3L6,8h2v3h3v2zM15.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S18.67,9 19.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" />
|
android:pathData="M21,6L3,6c-1.1,0 -2,0.9 -2,2v8c0,1.1 0.9,2 2,2h18c1.1,0 2,-0.9 2,-2L23,8c0,-1.1 -0.9,-2 -2,-2zM11,13L8,13v3L6,16v-3L3,13v-2h3L6,8h2v3h3v2zM15.5,15c-0.83,0 -1.5,-0.67 -1.5,-1.5s0.67,-1.5 1.5,-1.5 1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5zM19.5,12c-0.83,0 -1.5,-0.67 -1.5,-1.5S18.67,9 19.5,9s1.5,0.67 1.5,1.5 -0.67,1.5 -1.5,1.5z" />
|
||||||
</vector>
|
</vector>
|
||||||
|
|
9
src/android/app/src/main/res/drawable/ic_key.xml
Normal file
9
src/android/app/src/main/res/drawable/ic_key.xml
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorOnSurface"
|
||||||
|
android:pathData="M21,10h-8.35C11.83,7.67 9.61,6 7,6c-3.31,0 -6,2.69 -6,6s2.69,6 6,6c2.61,0 4.83,-1.67 5.65,-4H13l2,2l2,-2l2,2l4,-4.04L21,10zM7,15c-1.65,0 -3,-1.35 -3,-3c0,-1.65 1.35,-3 3,-3s3,1.35 3,3C10,13.65 8.65,15 7,15z" />
|
||||||
|
</vector>
|
24
src/android/app/src/main/res/drawable/ic_yuzu_title.xml
Normal file
24
src/android/app/src/main/res/drawable/ic_yuzu_title.xml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="340.97dp"
|
||||||
|
android:height="389.85dp"
|
||||||
|
android:viewportWidth="340.97"
|
||||||
|
android:viewportHeight="389.85">
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorOnSurface"
|
||||||
|
android:pathData="M341,268.68v73c0,14.5 -2.24,25.24 -6.83,32.82 -5.92,10.15 -16.21,15.32 -30.54,15.32S279,384.61 273,374.27c-4.56,-7.64 -6.8,-18.42 -6.8,-32.92V268.68a4.52,4.52 0,0 1,4.51 -4.51H273a4.5,4.5 0,0 1,4.5 4.51v72.5c0,33.53 14.88,37.4 26.07,37.4 12.14,0 26.08,-4.17 26.08,-36.71V268.68a4.52,4.52 0,0 1,4.52 -4.51h2.27A4.5,4.5 0,0 1,341 268.68Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorOnSurface"
|
||||||
|
android:pathData="M246.49,389.85H178.6c-2.35,0 -4.72,-1.88 -4.72,-6.08a8.28,8.28 0,0 1,1.33 -4.48l60.33,-104.47H186a4.51,4.51 0,0 1,-4.51 -4.51v-1.58a4.51,4.51 0,0 1,4.48 -4.51h0.8c58.69,-0.11 59.12,0 59.67,0.07a5.19,5.19 0,0 1,4 5.8,8.69 8.69,0 0,1 -1.33,3.76l-60.6,104.77h58a4.51,4.51 0,0 1,4.51 4.51v2.21A4.51,4.51 0,0 1,246.49 389.85Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorOnSurface"
|
||||||
|
android:pathData="M73.6,268.68v82.06c0,26 -11.8,38.44 -37.12,39.09h-0.12a4.51,4.51 0,0 1,-4.51 -4.51V383a4.51,4.51 0,0 1,4.48 -4.5c18.49,-0.15 26,-8.23 26,-27.9v-2.37A32.34,32.34 0,0 1,59 351.46c-6.39,5.5 -14.5,8.29 -24.07,8.29C12.09,359.75 0,347.34 0,323.86V268.68a4.52,4.52 0,0 1,4.51 -4.51H6.73a4.52,4.52 0,0 1,4.5 4.51v55c0,7.6 1.82,14.22 5,18.18 3.57,4.56 9.17,6.49 18.75,6.49 10.13,0 17.32,-3.76 22,-11.5 3.61,-5.92 5.43,-13.66 5.43,-23V268.68a4.52,4.52 0,0 1,4.51 -4.51h2.22A4.52,4.52 0,0 1,73.6 268.68Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="?attr/colorOnSurface"
|
||||||
|
android:pathData="M163.27,268.68v73c0,14.5 -2.24,25.24 -6.84,32.82 -5.92,10.15 -16.2,15.32 -30.53,15.32s-24.62,-5.23 -30.58,-15.57c-4.56,-7.64 -6.79,-18.42 -6.79,-32.92V268.68A4.51,4.51 0,0 1,93 264.17h2.28a4.51,4.51 0,0 1,4.51 4.51v72.5c0,33.53 14.88,37.4 26.07,37.4 12.14,0 26.08,-4.17 26.08,-36.71V268.68a4.51,4.51 0,0 1,4.51 -4.51h2.27A4.51,4.51 0,0 1,163.27 268.68Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#ff3c28"
|
||||||
|
android:pathData="M181.2,42.83V214.17a85.67,85.67 0,0 0,0 -171.34M197.93,61.6a69,69 0,0 1,0 133.8V61.6" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#0ab9e6"
|
||||||
|
android:pathData="M159.78,0a85.67,85.67 0,1 0,0 171.33ZM143.05,18.77v133.8A69,69 0,0 1,111 36.92a68.47,68.47 0,0 1,32 -18.15" />
|
||||||
|
</vector>
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/setup_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<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_margin="16dp"
|
||||||
|
android:text="@string/next"
|
||||||
|
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:layout_margin="16dp"
|
||||||
|
android:text="@string/back"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
65
src/android/app/src/main/res/layout-w600dp/page_setup.xml
Normal file
65
src/android/app/src/main/res/layout-w600dp/page_setup.xml
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="260dp"
|
||||||
|
android:layout_height="260dp"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<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"
|
||||||
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:textStyle="bold"
|
||||||
|
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"
|
||||||
|
tools:text="@string/welcome_description" />
|
||||||
|
|
||||||
|
<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:textSize="20sp"
|
||||||
|
app:iconSize="24sp"
|
||||||
|
app:iconGravity="end"
|
||||||
|
tools:text="Get started" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
|
@ -24,10 +24,12 @@
|
||||||
android:id="@+id/navigation_bar"
|
android:id="@+id/navigation_bar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:visibility="invisible"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintLeft_toLeftOf="parent"
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
app:layout_constraintRight_toRightOf="parent"
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
app:menu="@menu/menu_navigation" />
|
app:menu="@menu/menu_navigation"
|
||||||
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/status_bar_shade"
|
android:id="@+id/status_bar_shade"
|
||||||
|
|
38
src/android/app/src/main/res/layout/fragment_setup.xml
Normal file
38
src/android/app/src/main/res/layout/fragment_setup.xml
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/setup_root"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<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" />
|
||||||
|
|
||||||
|
<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_margin="16dp"
|
||||||
|
android:text="@string/next"
|
||||||
|
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"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:text="@string/back"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
52
src/android/app/src/main/res/layout/page_setup.xml
Normal file
52
src/android/app/src/main/res/layout/page_setup.xml
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.appcompat.widget.LinearLayoutCompat
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingBottom="64dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="220dp"
|
||||||
|
android:layout_height="220dp"
|
||||||
|
android:layout_marginTop="64dp"
|
||||||
|
android:layout_gravity="center" />
|
||||||
|
|
||||||
|
<com.google.android.material.textview.MaterialTextView
|
||||||
|
style="@style/TextAppearance.Material3.DisplayMedium"
|
||||||
|
android:id="@+id/text_title"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="64dp"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textColor="?attr/colorOnSurface"
|
||||||
|
android:textStyle="bold"
|
||||||
|
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="24dp"
|
||||||
|
android:paddingHorizontal="32dp"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="26sp"
|
||||||
|
app:lineHeight="40sp"
|
||||||
|
tools:text="@string/welcome_description" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/button_action"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="56dp"
|
||||||
|
android:layout_marginTop="96dp"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:textSize="20sp"
|
||||||
|
app:iconSize="24sp"
|
||||||
|
app:iconGravity="end"
|
||||||
|
tools:text="Get started" />
|
||||||
|
|
||||||
|
</androidx.appcompat.widget.LinearLayoutCompat>
|
|
@ -14,4 +14,13 @@
|
||||||
android:name="org.yuzu.yuzu_emu.fragments.OptionsFragment"
|
android:name="org.yuzu.yuzu_emu.fragments.OptionsFragment"
|
||||||
android:label="OptionsFragment" />
|
android:label="OptionsFragment" />
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/firstTimeSetupFragment"
|
||||||
|
android:name="org.yuzu.yuzu_emu.fragments.SetupFragment"
|
||||||
|
android:label="FirstTimeSetupFragment" >
|
||||||
|
<action
|
||||||
|
android:id="@+id/action_firstTimeSetupFragment_to_gamesFragment"
|
||||||
|
app:destination="@id/gamesFragment" />
|
||||||
|
</fragment>
|
||||||
|
|
||||||
</navigation>
|
</navigation>
|
||||||
|
|
|
@ -9,12 +9,28 @@
|
||||||
<string name="app_notification_channel_description">yuzu Switch emulator notifications</string>
|
<string name="app_notification_channel_description">yuzu Switch emulator notifications</string>
|
||||||
<string name="app_notification_running">yuzu is running</string>
|
<string name="app_notification_running">yuzu is running</string>
|
||||||
|
|
||||||
|
<!-- Setup strings -->
|
||||||
|
<string name="welcome">Welcome!</string>
|
||||||
|
<string name="welcome_description">Learn how to setup <b>yuzu</b> and jump into emulation.</string>
|
||||||
|
<string name="get_started">Get started</string>
|
||||||
|
<string name="keys">Keys</string>
|
||||||
|
<string name="keys_description">Select your <b>prod.keys</b> file with the button below.</string>
|
||||||
|
<string name="select_keys">Select Keys</string>
|
||||||
|
<string name="games">Games</string>
|
||||||
|
<string name="games_description">Select your <b>Games</b> folder with the button below.</string>
|
||||||
|
<string name="done">Done</string>
|
||||||
|
<string name="done_description">You\'re all set.\nEnjoy your games!</string>
|
||||||
|
<string name="text_continue">Continue</string>
|
||||||
|
<string name="next">Next</string>
|
||||||
|
<string name="back">Back</string>
|
||||||
|
|
||||||
<!-- Home strings -->
|
<!-- Home strings -->
|
||||||
<string name="home_games">Games</string>
|
<string name="home_games">Games</string>
|
||||||
<string name="home_options">Options</string>
|
<string name="home_options">Options</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="home_search_games">Search Games</string>
|
<string name="home_search_games">Search Games</string>
|
||||||
|
<string name="games_dir_selected">Games directory selected</string>
|
||||||
<string name="install_prod_keys">Install Prod.keys</string>
|
<string name="install_prod_keys">Install Prod.keys</string>
|
||||||
<string name="install_prod_keys_description">Required to decrypt retail games</string>
|
<string name="install_prod_keys_description">Required to decrypt retail games</string>
|
||||||
<string name="install_amiibo_keys">Install Amiibo Keys</string>
|
<string name="install_amiibo_keys">Install Amiibo Keys</string>
|
||||||
|
|
Loading…
Reference in a new issue