|
@@ -3,42 +3,31 @@
|
|
|
|
|
|
|
|
package org.yuzu.yuzu_emu.ui.main
|
|
package org.yuzu.yuzu_emu.ui.main
|
|
|
|
|
|
|
|
-import android.content.DialogInterface
|
|
|
|
|
-import android.content.Intent
|
|
|
|
|
import android.os.Bundle
|
|
import android.os.Bundle
|
|
|
-import android.view.Menu
|
|
|
|
|
-import android.view.MenuItem
|
|
|
|
|
import android.view.View
|
|
import android.view.View
|
|
|
-import android.widget.Toast
|
|
|
|
|
-import androidx.activity.result.contract.ActivityResultContracts
|
|
|
|
|
|
|
+import android.view.ViewGroup.MarginLayoutParams
|
|
|
|
|
+import android.view.animation.PathInterpolator
|
|
|
|
|
+import androidx.activity.viewModels
|
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
|
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.core.view.updatePadding
|
|
|
|
|
-import androidx.lifecycle.lifecycleScope
|
|
|
|
|
-import androidx.preference.PreferenceManager
|
|
|
|
|
-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 androidx.navigation.fragment.NavHostFragment
|
|
|
|
|
+import androidx.navigation.ui.setupWithNavController
|
|
|
|
|
+import com.google.android.material.color.MaterialColors
|
|
|
|
|
+import com.google.android.material.elevation.ElevationOverlayProvider
|
|
|
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.ui.SettingsActivity
|
|
|
|
|
-import org.yuzu.yuzu_emu.ui.platform.PlatformGamesFragment
|
|
|
|
|
|
|
+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(), MainView {
|
|
|
|
|
- private var platformGamesFragment: PlatformGamesFragment? = null
|
|
|
|
|
- private val presenter = MainPresenter(this)
|
|
|
|
|
|
|
|
|
|
|
|
+class MainActivity : AppCompatActivity() {
|
|
|
private lateinit var binding: ActivityMainBinding
|
|
private lateinit var binding: ActivityMainBinding
|
|
|
|
|
|
|
|
|
|
+ private val homeViewModel: HomeViewModel by viewModels()
|
|
|
|
|
+
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
|
val splashScreen = installSplashScreen()
|
|
val splashScreen = installSplashScreen()
|
|
|
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
|
|
splashScreen.setKeepOnScreenCondition { !DirectoryInitialization.areDirectoriesReady }
|
|
@@ -52,19 +41,36 @@ class MainActivity : AppCompatActivity(), MainView {
|
|
|
|
|
|
|
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
|
|
|
|
|
|
- setSupportActionBar(binding.toolbarMain)
|
|
|
|
|
- presenter.onCreate()
|
|
|
|
|
- if (savedInstanceState == null) {
|
|
|
|
|
- StartupHandler.handleInit(this)
|
|
|
|
|
- platformGamesFragment = PlatformGamesFragment()
|
|
|
|
|
- supportFragmentManager.beginTransaction()
|
|
|
|
|
- .add(R.id.games_platform_frame, platformGamesFragment!!)
|
|
|
|
|
- .commit()
|
|
|
|
|
- } else {
|
|
|
|
|
- platformGamesFragment = supportFragmentManager.getFragment(
|
|
|
|
|
- savedInstanceState,
|
|
|
|
|
- PlatformGamesFragment.TAG
|
|
|
|
|
- ) as PlatformGamesFragment?
|
|
|
|
|
|
|
+ ThemeHelper.setNavigationBarColor(
|
|
|
|
|
+ this,
|
|
|
|
|
+ ElevationOverlayProvider(binding.navigationBar.context).compositeOverlay(
|
|
|
|
|
+ MaterialColors.getColor(binding.navigationBar, R.attr.colorSurface),
|
|
|
|
|
+ binding.navigationBar.elevation
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // Set up a central host fragment that is controlled via bottom navigation with xml navigation
|
|
|
|
|
+ val navHostFragment =
|
|
|
|
|
+ supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
|
|
|
|
|
+ binding.navigationBar.setupWithNavController(navHostFragment.navController)
|
|
|
|
|
+
|
|
|
|
|
+ binding.statusBarShade.setBackgroundColor(
|
|
|
|
|
+ ThemeHelper.getColorWithOpacity(
|
|
|
|
|
+ MaterialColors.getColor(
|
|
|
|
|
+ binding.root,
|
|
|
|
|
+ R.attr.colorSurface
|
|
|
|
|
+ ), ThemeHelper.SYSTEM_BAR_ALPHA
|
|
|
|
|
+ )
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
|
|
+ // Prevents navigation from being drawn for a short time on recreation if set to hidden
|
|
|
|
|
+ if (homeViewModel.navigationVisible.value == false) {
|
|
|
|
|
+ binding.navigationBar.visibility = View.INVISIBLE
|
|
|
|
|
+ binding.statusBarShade.visibility = View.INVISIBLE
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ homeViewModel.navigationVisible.observe(this) { visible ->
|
|
|
|
|
+ showNavigation(visible)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
// Dismiss previous notifications (should not happen unless a crash occurred)
|
|
// Dismiss previous notifications (should not happen unless a crash occurred)
|
|
@@ -73,78 +79,24 @@ class MainActivity : AppCompatActivity(), MainView {
|
|
|
setInsets()
|
|
setInsets()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- override fun onSaveInstanceState(outState: Bundle) {
|
|
|
|
|
- super.onSaveInstanceState(outState)
|
|
|
|
|
- supportFragmentManager.putFragment(
|
|
|
|
|
- outState,
|
|
|
|
|
- PlatformGamesFragment.TAG,
|
|
|
|
|
- platformGamesFragment!!
|
|
|
|
|
- )
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
|
|
|
|
- menuInflater.inflate(R.menu.menu_game_grid, menu)
|
|
|
|
|
- return true
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * MainView
|
|
|
|
|
- */
|
|
|
|
|
- override fun setVersionString(version: String) {
|
|
|
|
|
- binding.toolbarMain.subtitle = version
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun launchSettingsActivity(menuTag: String) {
|
|
|
|
|
- SettingsActivity.launch(this, menuTag, "")
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- override fun launchFileListActivity(request: Int) {
|
|
|
|
|
- when (request) {
|
|
|
|
|
- MainPresenter.REQUEST_ADD_DIRECTORY -> getGamesDirectory.launch(Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).data)
|
|
|
|
|
- MainPresenter.REQUEST_INSTALL_KEYS -> getProdKey.launch(arrayOf("*/*"))
|
|
|
|
|
- MainPresenter.REQUEST_INSTALL_AMIIBO_KEYS -> getAmiiboKey.launch(arrayOf("*/*"))
|
|
|
|
|
- MainPresenter.REQUEST_SELECT_GPU_DRIVER -> {
|
|
|
|
|
- // Get the driver name for the dialog message.
|
|
|
|
|
- var driverName = GpuDriverHelper.customDriverName
|
|
|
|
|
- if (driverName == null) {
|
|
|
|
|
- driverName = getString(R.string.system_gpu_driver)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- MaterialAlertDialogBuilder(this)
|
|
|
|
|
- .setTitle(getString(R.string.select_gpu_driver_title))
|
|
|
|
|
- .setMessage(driverName)
|
|
|
|
|
- .setNegativeButton(android.R.string.cancel, null)
|
|
|
|
|
- .setPositiveButton(R.string.select_gpu_driver_default) { _: DialogInterface?, _: Int ->
|
|
|
|
|
- GpuDriverHelper.installDefaultDriver(this)
|
|
|
|
|
- Toast.makeText(
|
|
|
|
|
- this,
|
|
|
|
|
- R.string.select_gpu_driver_use_default,
|
|
|
|
|
- Toast.LENGTH_SHORT
|
|
|
|
|
- ).show()
|
|
|
|
|
- }
|
|
|
|
|
- .setNeutralButton(R.string.select_gpu_driver_install) { _: DialogInterface?, _: Int ->
|
|
|
|
|
- getDriver.launch(arrayOf("application/zip"))
|
|
|
|
|
- }
|
|
|
|
|
- .show()
|
|
|
|
|
|
|
+ private fun showNavigation(visible: Boolean) {
|
|
|
|
|
+ binding.navigationBar.animate().apply {
|
|
|
|
|
+ if (visible) {
|
|
|
|
|
+ binding.navigationBar.visibility = View.VISIBLE
|
|
|
|
|
+ binding.navigationBar.translationY = binding.navigationBar.height.toFloat() * 2
|
|
|
|
|
+ duration = 300
|
|
|
|
|
+ translationY(0f)
|
|
|
|
|
+ interpolator = PathInterpolator(0.05f, 0.7f, 0.1f, 1f)
|
|
|
|
|
+ } else {
|
|
|
|
|
+ duration = 300
|
|
|
|
|
+ translationY(binding.navigationBar.height.toFloat() * 2)
|
|
|
|
|
+ interpolator = PathInterpolator(0.3f, 0f, 0.8f, 0.15f)
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- /**
|
|
|
|
|
- * Called by the framework whenever any actionbar/toolbar icon is clicked.
|
|
|
|
|
- *
|
|
|
|
|
- * @param item The icon that was clicked on.
|
|
|
|
|
- * @return True if the event was handled, false to bubble it up to the OS.
|
|
|
|
|
- */
|
|
|
|
|
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
|
|
|
- return presenter.handleOptionSelection(item.itemId)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- private fun refreshFragment() {
|
|
|
|
|
- if (platformGamesFragment != null) {
|
|
|
|
|
- NativeLibrary.resetRomMetadata()
|
|
|
|
|
- platformGamesFragment!!.refresh()
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ }.withEndAction {
|
|
|
|
|
+ if (!visible) {
|
|
|
|
|
+ binding.navigationBar.visibility = View.INVISIBLE
|
|
|
|
|
+ }
|
|
|
|
|
+ }.start()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
override fun onDestroy() {
|
|
override fun onDestroy() {
|
|
@@ -152,145 +104,12 @@ class MainActivity : AppCompatActivity(), MainView {
|
|
|
super.onDestroy()
|
|
super.onDestroy()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private fun setInsets() {
|
|
|
|
|
- ViewCompat.setOnApplyWindowInsetsListener(binding.gamesPlatformFrame) { view: View, windowInsets: WindowInsetsCompat ->
|
|
|
|
|
|
|
+ private fun setInsets() =
|
|
|
|
|
+ ViewCompat.setOnApplyWindowInsetsListener(binding.statusBarShade) { view: View, windowInsets: WindowInsetsCompat ->
|
|
|
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
|
- view.updatePadding(left = insets.left, right = insets.right)
|
|
|
|
|
- InsetsHelper.insetAppBar(insets, binding.appbarMain)
|
|
|
|
|
|
|
+ val mlpShade = view.layoutParams as MarginLayoutParams
|
|
|
|
|
+ mlpShade.height = insets.top
|
|
|
|
|
+ binding.statusBarShade.layoutParams = mlpShade
|
|
|
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
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
- 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
|
|
|
|
|
- contentResolver.takePersistableUriPermission(
|
|
|
|
|
- result,
|
|
|
|
|
- takeFlags
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
|
|
|
|
- if (FileUtil.copyUriToInternalStorage(this, result, dstPath, "prod.keys")) {
|
|
|
|
|
- if (NativeLibrary.reloadKeys()) {
|
|
|
|
|
- Toast.makeText(
|
|
|
|
|
- this,
|
|
|
|
|
- R.string.install_keys_success,
|
|
|
|
|
- Toast.LENGTH_SHORT
|
|
|
|
|
- ).show()
|
|
|
|
|
- refreshFragment()
|
|
|
|
|
- } else {
|
|
|
|
|
- Toast.makeText(
|
|
|
|
|
- this,
|
|
|
|
|
- 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
|
|
|
|
|
- contentResolver.takePersistableUriPermission(
|
|
|
|
|
- result,
|
|
|
|
|
- takeFlags
|
|
|
|
|
- )
|
|
|
|
|
-
|
|
|
|
|
- val dstPath = DirectoryInitialization.userDirectory + "/keys/"
|
|
|
|
|
- if (FileUtil.copyUriToInternalStorage(this, result, dstPath, "key_retail.bin")) {
|
|
|
|
|
- if (NativeLibrary.reloadKeys()) {
|
|
|
|
|
- Toast.makeText(
|
|
|
|
|
- this,
|
|
|
|
|
- R.string.install_keys_success,
|
|
|
|
|
- Toast.LENGTH_SHORT
|
|
|
|
|
- ).show()
|
|
|
|
|
- refreshFragment()
|
|
|
|
|
- } else {
|
|
|
|
|
- Toast.makeText(
|
|
|
|
|
- this,
|
|
|
|
|
- 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
|
|
|
|
|
- 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()
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|