From 6bfc3c530ce0c8ba3ac0a62609d1266aa8d67d35 Mon Sep 17 00:00:00 2001
From: t895 <clombardo169@gmail.com>
Date: Wed, 10 Jan 2024 13:09:06 -0500
Subject: [PATCH] android: Rework driver fragment

Applies settings upon selection and uses a new Driver model to represent the information in-view. Also switches from an async diff list to a plain one.
---
 .../fragments/DriverManagerFragment.kt        |  39 ++----
 .../java/org/yuzu/yuzu_emu/model/Driver.kt    |  27 ++++
 .../yuzu/yuzu_emu/model/DriverViewModel.kt    | 129 ++++++------------
 .../yuzu/yuzu_emu/utils/GpuDriverHelper.kt    |   3 -
 4 files changed, 86 insertions(+), 112 deletions(-)
 create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt

diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
index cc71254dc..82c966954 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
@@ -13,16 +13,16 @@ import androidx.core.view.WindowInsetsCompat
 import androidx.core.view.updatePadding
 import androidx.fragment.app.Fragment
 import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.lifecycleScope
 import androidx.navigation.findNavController
 import androidx.navigation.fragment.navArgs
 import androidx.recyclerview.widget.GridLayoutManager
 import com.google.android.material.transition.MaterialSharedAxis
-import kotlinx.coroutines.flow.collectLatest
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
 import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.adapters.DriverAdapter
 import org.yuzu.yuzu_emu.databinding.FragmentDriverManagerBinding
+import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
 import org.yuzu.yuzu_emu.model.DriverViewModel
 import org.yuzu.yuzu_emu.model.HomeViewModel
 import org.yuzu.yuzu_emu.utils.FileUtil
@@ -85,25 +85,6 @@ class DriverManagerFragment : Fragment() {
             adapter = DriverAdapter(driverViewModel)
         }
 
-        viewLifecycleOwner.lifecycleScope.apply {
-            launch {
-                driverViewModel.driverList.collectLatest {
-                    (binding.listDrivers.adapter as DriverAdapter).submitList(it)
-                }
-            }
-            launch {
-                driverViewModel.newDriverInstalled.collect {
-                    if (_binding != null && it) {
-                        (binding.listDrivers.adapter as DriverAdapter).apply {
-                            notifyItemChanged(driverViewModel.previouslySelectedDriver)
-                            notifyItemChanged(driverViewModel.selectedDriver)
-                            driverViewModel.setNewDriverInstalled(false)
-                        }
-                    }
-                }
-            }
-        }
-
         setInsets()
     }
 
@@ -177,12 +158,20 @@ class DriverManagerFragment : Fragment() {
 
                 val driverData = GpuDriverHelper.getMetadataFromZip(driverFile)
                 val driverInList =
-                    driverViewModel.driverList.value.firstOrNull { it.second == driverData }
+                    driverViewModel.driverData.firstOrNull { it.second == driverData }
                 if (driverInList != null) {
                     return@newInstance getString(R.string.driver_already_installed)
                 } else {
-                    driverViewModel.addDriver(Pair(driverPath, driverData))
-                    driverViewModel.setNewDriverInstalled(true)
+                    driverViewModel.onDriverAdded(Pair(driverPath, driverData))
+                    withContext(Dispatchers.Main) {
+                        if (_binding != null) {
+                            val adapter = binding.listDrivers.adapter as DriverAdapter
+                            adapter.addItem(driverData.toDriver())
+                            adapter.selectItem(adapter.currentList.indices.last)
+                            binding.listDrivers
+                                .smoothScrollToPosition(adapter.currentList.indices.last)
+                        }
+                    }
                 }
                 return@newInstance Any()
             }.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt
new file mode 100644
index 000000000..de342212a
--- /dev/null
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/Driver.kt
@@ -0,0 +1,27 @@
+// SPDX-FileCopyrightText: 2024 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+package org.yuzu.yuzu_emu.model
+
+import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
+
+data class Driver(
+    override var selected: Boolean,
+    val title: String,
+    val version: String = "",
+    val description: String = ""
+) : SelectableItem {
+    override fun onSelectionStateChanged(selected: Boolean) {
+        this.selected = selected
+    }
+
+    companion object {
+        fun GpuDriverMetadata.toDriver(selected: Boolean = false): Driver =
+            Driver(
+                selected,
+                this.name ?: "",
+                this.version ?: "",
+                this.description ?: ""
+            )
+    }
+}
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
index 76accf8f3..a1fee48cc 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
@@ -17,11 +17,10 @@ import org.yuzu.yuzu_emu.R
 import org.yuzu.yuzu_emu.YuzuApplication
 import org.yuzu.yuzu_emu.features.settings.model.StringSetting
 import org.yuzu.yuzu_emu.features.settings.utils.SettingsFile
-import org.yuzu.yuzu_emu.utils.FileUtil
+import org.yuzu.yuzu_emu.model.Driver.Companion.toDriver
 import org.yuzu.yuzu_emu.utils.GpuDriverHelper
 import org.yuzu.yuzu_emu.utils.GpuDriverMetadata
 import org.yuzu.yuzu_emu.utils.NativeConfig
-import java.io.BufferedOutputStream
 import java.io.File
 
 class DriverViewModel : ViewModel() {
@@ -38,97 +37,74 @@ class DriverViewModel : ViewModel() {
             !loading && ready && !deleting
         }.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), initialValue = false)
 
-    private val _driverList = MutableStateFlow(GpuDriverHelper.getDrivers())
-    val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList
+    var driverData = GpuDriverHelper.getDrivers()
 
-    var previouslySelectedDriver = 0
-    var selectedDriver = -1
+    private val _driverList = MutableStateFlow(emptyList<Driver>())
+    val driverList: StateFlow<List<Driver>> get() = _driverList
 
     // Used for showing which driver is currently installed within the driver manager card
     private val _selectedDriverTitle = MutableStateFlow("")
     val selectedDriverTitle: StateFlow<String> get() = _selectedDriverTitle
 
-    private val _newDriverInstalled = MutableStateFlow(false)
-    val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled
-
-    val driversToDelete = mutableListOf<String>()
+    private val driversToDelete = mutableListOf<String>()
 
     init {
-        val currentDriverMetadata = GpuDriverHelper.installedCustomDriverData
-        findSelectedDriver(currentDriverMetadata)
-
-        // If a user had installed a driver before the manager was implemented, this zips
-        // the installed driver to UserData/gpu_drivers/CustomDriver.zip so that it can
-        // be indexed and exported as expected.
-        if (selectedDriver == -1) {
-            val driverToSave =
-                File(GpuDriverHelper.driverStoragePath, "CustomDriver.zip")
-            driverToSave.createNewFile()
-            FileUtil.zipFromInternalStorage(
-                File(GpuDriverHelper.driverInstallationPath!!),
-                GpuDriverHelper.driverInstallationPath!!,
-                BufferedOutputStream(driverToSave.outputStream())
-            )
-            _driverList.value.add(Pair(driverToSave.path, currentDriverMetadata))
-            setSelectedDriverIndex(_driverList.value.size - 1)
-        }
-
-        // If a user had installed a driver before the config was reworked to be multiplatform,
-        // we have save the path of the previously selected driver to the new setting.
-        if (StringSetting.DRIVER_PATH.getString(true).isEmpty() && selectedDriver > 0 &&
-            StringSetting.DRIVER_PATH.global
-        ) {
-            StringSetting.DRIVER_PATH.setString(_driverList.value[selectedDriver].first)
-            NativeConfig.saveGlobalConfig()
-        } else {
-            findSelectedDriver(GpuDriverHelper.customDriverSettingData)
-        }
+        updateDriverList()
         updateDriverNameForGame(null)
     }
 
-    fun setSelectedDriverIndex(value: Int) {
-        if (selectedDriver != -1) {
-            previouslySelectedDriver = selectedDriver
+    fun reloadDriverData() {
+        _areDriversLoading.value = true
+        driverData = GpuDriverHelper.getDrivers()
+        updateDriverList()
+        _areDriversLoading.value = false
+    }
+
+    private fun updateDriverList() {
+        val selectedDriver = GpuDriverHelper.customDriverSettingData
+        val newDriverList = mutableListOf(
+            Driver(
+                selectedDriver == GpuDriverMetadata(),
+                YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+            )
+        )
+        driverData.forEach {
+            newDriverList.add(it.second.toDriver(it.second == selectedDriver))
         }
-        selectedDriver = value
-    }
-
-    fun setNewDriverInstalled(value: Boolean) {
-        _newDriverInstalled.value = value
-    }
-
-    fun addDriver(driverData: Pair<String, GpuDriverMetadata>) {
-        val driverIndex = _driverList.value.indexOfFirst { it == driverData }
-        if (driverIndex == -1) {
-            _driverList.value.add(driverData)
-            setSelectedDriverIndex(_driverList.value.size - 1)
-            _selectedDriverTitle.value = driverData.second.name
-                ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
-        } else {
-            setSelectedDriverIndex(driverIndex)
-        }
-    }
-
-    fun removeDriver(driverData: Pair<String, GpuDriverMetadata>) {
-        _driverList.value.remove(driverData)
+        _driverList.value = newDriverList
     }
 
     fun onOpenDriverManager(game: Game?) {
         if (game != null) {
             SettingsFile.loadCustomConfig(game)
         }
+        updateDriverList()
+    }
 
-        val driverPath = StringSetting.DRIVER_PATH.getString()
-        if (driverPath.isEmpty()) {
-            setSelectedDriverIndex(0)
+    fun onDriverSelected(position: Int) {
+        if (position == 0) {
+            StringSetting.DRIVER_PATH.setString("")
         } else {
-            findSelectedDriver(GpuDriverHelper.getMetadataFromZip(File(driverPath)))
+            StringSetting.DRIVER_PATH.setString(driverData[position - 1].first)
         }
     }
 
+    fun onDriverRemoved(removedPosition: Int, selectedPosition: Int) {
+        driversToDelete.add(driverData[removedPosition - 1].first)
+        driverData.removeAt(removedPosition - 1)
+        onDriverSelected(selectedPosition)
+    }
+
+    fun onDriverAdded(driver: Pair<String, GpuDriverMetadata>) {
+        if (driversToDelete.contains(driver.first)) {
+            driversToDelete.remove(driver.first)
+        }
+        driverData.add(driver)
+        onDriverSelected(driverData.size)
+    }
+
     fun onCloseDriverManager(game: Game?) {
         _isDeletingDrivers.value = true
-        StringSetting.DRIVER_PATH.setString(driverList.value[selectedDriver].first)
         updateDriverNameForGame(game)
         if (game == null) {
             NativeConfig.saveGlobalConfig()
@@ -181,20 +157,6 @@ class DriverViewModel : ViewModel() {
         }
     }
 
-    private fun findSelectedDriver(currentDriverMetadata: GpuDriverMetadata) {
-        if (driverList.value.size == 1) {
-            setSelectedDriverIndex(0)
-            return
-        }
-
-        driverList.value.forEachIndexed { i: Int, driver: Pair<String, GpuDriverMetadata> ->
-            if (driver.second == currentDriverMetadata) {
-                setSelectedDriverIndex(i)
-                return
-            }
-        }
-    }
-
     fun updateDriverNameForGame(game: Game?) {
         if (!GpuDriverHelper.supportsCustomDriverLoading()) {
             return
@@ -217,7 +179,6 @@ class DriverViewModel : ViewModel() {
 
     private fun setDriverReady() {
         _isDriverReady.value = true
-        _selectedDriverTitle.value = GpuDriverHelper.customDriverSettingData.name
-            ?: YuzuApplication.appContext.getString(R.string.system_gpu_driver)
+        updateName()
     }
 }
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
index 685272288..a8f9dcc34 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
@@ -62,9 +62,6 @@ object GpuDriverHelper {
                 ?.sortedByDescending { it: Pair<String, GpuDriverMetadata> -> it.second.name }
                 ?.distinct()
                 ?.toMutableList() ?: mutableListOf()
-
-        // TODO: Get system driver information
-        drivers.add(0, Pair("", GpuDriverMetadata()))
         return drivers
     }