Quellcode durchsuchen

Merge pull request #10726 from t895/emulation-nav-component

android: Adapt EmulationActivity to navigation component
bunnei vor 3 Jahren
Ursprung
Commit
a10a091928

+ 1 - 0
src/android/app/build.gradle.kts

@@ -9,6 +9,7 @@ plugins {
     id("org.jetbrains.kotlin.android")
     id("kotlin-parcelize")
     kotlin("plugin.serialization") version "1.8.21"
+    id("androidx.navigation.safeargs.kotlin")
 }
 
 /**

+ 0 - 1
src/android/app/src/main/AndroidManifest.xml

@@ -53,7 +53,6 @@ SPDX-License-Identifier: GPL-3.0-or-later
         <activity
             android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
             android:theme="@style/Theme.Yuzu.Main"
-            android:launchMode="singleTop"
             android:screenOrientation="userLandscape"
             android:exported="true">
 

+ 16 - 48
src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt

@@ -23,30 +23,25 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.core.view.WindowCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.WindowInsetsControllerCompat
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
-import androidx.window.layout.WindowInfoTracker
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
+import androidx.navigation.fragment.NavHostFragment
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
+import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
 import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
-import org.yuzu.yuzu_emu.fragments.EmulationFragment
 import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
 import org.yuzu.yuzu_emu.utils.ForegroundService
 import org.yuzu.yuzu_emu.utils.InputHandler
 import org.yuzu.yuzu_emu.utils.NfcReader
-import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
 import org.yuzu.yuzu_emu.utils.ThemeHelper
 import kotlin.math.roundToInt
 
 class EmulationActivity : AppCompatActivity(), SensorEventListener {
+    private lateinit var binding: ActivityEmulationBinding
+
     private var controllerMappingHelper: ControllerMappingHelper? = null
 
     var isActivityRecreated = false
-    private var emulationFragment: EmulationFragment? = null
     private lateinit var nfcReader: NfcReader
     private lateinit var inputHandler: InputHandler
 
@@ -55,8 +50,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
     private var motionTimestamp: Long = 0
     private var flipMotionOrientation: Boolean = false
 
-    private lateinit var game: Game
-
     private val settingsViewModel: SettingsViewModel by viewModels()
 
     override fun onDestroy() {
@@ -70,47 +63,31 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
         settingsViewModel.settings.loadSettings()
 
         super.onCreate(savedInstanceState)
-        if (savedInstanceState == null) {
-            // Get params we were passed
-            game = intent.parcelable(EXTRA_SELECTED_GAME)!!
-            isActivityRecreated = false
-        } else {
-            isActivityRecreated = true
-            restoreState(savedInstanceState)
-        }
+
+        binding = ActivityEmulationBinding.inflate(layoutInflater)
+        setContentView(binding.root)
+
+        val navHostFragment =
+            supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment
+        val navController = navHostFragment.navController
+        navController
+            .setGraph(R.navigation.emulation_navigation, intent.extras)
+
+        isActivityRecreated = savedInstanceState != null
+
         controllerMappingHelper = ControllerMappingHelper()
 
         // Set these options now so that the SurfaceView the game renders into is the right size.
         enableFullscreenImmersive()
 
-        setContentView(R.layout.activity_emulation)
         window.decorView.setBackgroundColor(getColor(android.R.color.black))
 
-        // Find or create the EmulationFragment
-        emulationFragment =
-            supportFragmentManager.findFragmentById(R.id.frame_emulation_fragment) as EmulationFragment?
-        if (emulationFragment == null) {
-            emulationFragment = EmulationFragment.newInstance(game)
-            supportFragmentManager.beginTransaction()
-                .add(R.id.frame_emulation_fragment, emulationFragment!!)
-                .commit()
-        }
-        title = game.title
-
         nfcReader = NfcReader(this)
         nfcReader.initialize()
 
         inputHandler = InputHandler()
         inputHandler.initialize()
 
-        lifecycleScope.launch(Dispatchers.Main) {
-            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
-                WindowInfoTracker.getOrCreate(this@EmulationActivity)
-                    .windowLayoutInfo(this@EmulationActivity)
-                    .collect { emulationFragment?.updateCurrentLayout(this@EmulationActivity, it) }
-            }
-        }
-
         // Start a foreground service to prevent the app from getting killed in the background
         val startIntent = Intent(this, ForegroundService::class.java)
         startForegroundService(startIntent)
@@ -157,11 +134,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
         nfcReader.onNewIntent(intent)
     }
 
-    override fun onSaveInstanceState(outState: Bundle) {
-        outState.putParcelable(EXTRA_SELECTED_GAME, game)
-        super.onSaveInstanceState(outState)
-    }
-
     override fun dispatchKeyEvent(event: KeyEvent): Boolean {
         if (event.source and InputDevice.SOURCE_JOYSTICK != InputDevice.SOURCE_JOYSTICK &&
             event.source and InputDevice.SOURCE_GAMEPAD != InputDevice.SOURCE_GAMEPAD
@@ -248,10 +220,6 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
 
     override fun onAccuracyChanged(sensor: Sensor, i: Int) {}
 
-    private fun restoreState(savedInstanceState: Bundle) {
-        game = savedInstanceState.parcelable(EXTRA_SELECTED_GAME)!!
-    }
-
     private fun enableFullscreenImmersive() {
         WindowCompat.setDecorFitsSystemWindows(window, false)
 

+ 4 - 1
src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/GameAdapter.kt

@@ -16,6 +16,7 @@ import androidx.appcompat.app.AppCompatActivity
 import androidx.documentfile.provider.DocumentFile
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.lifecycleScope
+import androidx.navigation.findNavController
 import androidx.preference.PreferenceManager
 import androidx.recyclerview.widget.AsyncDifferConfig
 import androidx.recyclerview.widget.DiffUtil
@@ -23,6 +24,7 @@ import androidx.recyclerview.widget.ListAdapter
 import androidx.recyclerview.widget.RecyclerView
 import coil.load
 import kotlinx.coroutines.launch
+import org.yuzu.yuzu_emu.HomeNavigationDirections
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
@@ -78,7 +80,8 @@ class GameAdapter(private val activity: AppCompatActivity) :
             )
             .apply()
 
-        EmulationActivity.launch(activity, holder.game)
+        val action = HomeNavigationDirections.actionGlobalEmulationActivity(holder.game)
+        view.findNavController().navigate(action)
     }
 
     inner class GameViewHolder(val binding: CardGameBinding) :

+ 18 - 14
src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt

@@ -26,11 +26,18 @@ import androidx.core.view.ViewCompat
 import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import androidx.navigation.fragment.navArgs
 import androidx.preference.PreferenceManager
 import androidx.window.layout.FoldingFeature
+import androidx.window.layout.WindowInfoTracker
 import androidx.window.layout.WindowLayoutInfo
 import com.google.android.material.dialog.MaterialAlertDialogBuilder
 import com.google.android.material.slider.Slider
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 import org.yuzu.yuzu_emu.NativeLibrary
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
@@ -41,9 +48,7 @@ import org.yuzu.yuzu_emu.features.settings.model.IntSetting
 import org.yuzu.yuzu_emu.features.settings.model.Settings
 import org.yuzu.yuzu_emu.features.settings.ui.SettingsActivity
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.model.Game
 import org.yuzu.yuzu_emu.utils.*
-import org.yuzu.yuzu_emu.utils.SerializableHelper.parcelable
 
 class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     private lateinit var preferences: SharedPreferences
@@ -54,7 +59,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
     private var _binding: FragmentEmulationBinding? = null
     private val binding get() = _binding!!
 
-    private lateinit var game: Game
+    val args by navArgs<EmulationFragmentArgs>()
 
     override fun onAttach(context: Context) {
         super.onAttach(context)
@@ -75,8 +80,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         // So this fragment doesn't restart on configuration changes; i.e. rotation.
         retainInstance = true
         preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
-        game = requireArguments().parcelable(EmulationActivity.EXTRA_SELECTED_GAME)!!
-        emulationState = EmulationState(game.path)
+        emulationState = EmulationState(args.game.path)
     }
 
     /**
@@ -100,7 +104,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
         updateShowFpsOverlay()
 
         binding.inGameMenu.getHeaderView(0).findViewById<TextView>(R.id.text_game_title).text =
-            game.title
+            args.game.title
         binding.inGameMenu.setNavigationItemSelectedListener {
             when (it.itemId) {
                 R.id.menu_pause_emulation -> {
@@ -153,6 +157,14 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
                     if (binding.drawerLayout.isOpen) binding.drawerLayout.close() else binding.drawerLayout.open()
                 }
             })
+
+        viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
+            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                WindowInfoTracker.getOrCreate(requireContext())
+                    .windowLayoutInfo(requireActivity())
+                    .collect { updateCurrentLayout(requireActivity() as EmulationActivity, it) }
+            }
+        }
     }
 
     override fun onResume() {
@@ -601,13 +613,5 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
 
     companion object {
         private val perfStatsUpdateHandler = Handler(Looper.myLooper()!!)
-
-        fun newInstance(game: Game): EmulationFragment {
-            val args = Bundle()
-            args.putParcelable(EmulationActivity.EXTRA_SELECTED_GAME, game)
-            val fragment = EmulationFragment()
-            fragment.arguments = args
-            return fragment
-        }
     }
 }

+ 6 - 10
src/android/app/src/main/res/layout/activity_emulation.xml

@@ -1,13 +1,9 @@
-<FrameLayout
+<androidx.fragment.app.FragmentContainerView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/frame_content"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/fragment_container"
+    android:name="androidx.navigation.fragment.NavHostFragment"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:keepScreenOn="true">
-
-    <FrameLayout
-        android:id="@+id/frame_emulation_fragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent" />
-
-</FrameLayout>
+    android:keepScreenOn="true"
+    app:defaultNavHost="true" />

+ 18 - 0
src/android/app/src/main/res/navigation/emulation_navigation.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<navigation 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:id="@+id/emulation_navigation"
+    app:startDestination="@id/emulationFragment">
+
+    <fragment
+        android:id="@+id/emulationFragment"
+        android:name="org.yuzu.yuzu_emu.fragments.EmulationFragment"
+        android:label="fragment_emulation"
+        tools:layout="@layout/fragment_emulation" >
+        <argument
+            android:name="game"
+            app:argType="org.yuzu.yuzu_emu.model.Game" />
+    </fragment>
+
+</navigation>

+ 14 - 0
src/android/app/src/main/res/navigation/home_navigation.xml

@@ -56,4 +56,18 @@
         android:name="org.yuzu.yuzu_emu.fragments.LicensesFragment"
         android:label="LicensesFragment" />
 
+    <activity
+        android:id="@+id/emulationActivity"
+        android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
+        android:label="EmulationActivity">
+        <argument
+            android:name="game"
+            app:argType="org.yuzu.yuzu_emu.model.Game" />
+    </activity>
+
+    <action
+        android:id="@+id/action_global_emulationActivity"
+        app:destination="@id/emulationActivity"
+        app:launchSingleTop="true" />
+
 </navigation>

+ 9 - 0
src/android/build.gradle.kts

@@ -11,3 +11,12 @@ plugins {
 tasks.register("clean").configure {
     delete(rootProject.buildDir)
 }
+
+buildscript {
+    repositories {
+        google()
+    }
+    dependencies {
+        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.6.0")
+    }
+}