committed by
GitHub
36 changed files with 1039 additions and 299 deletions
-
14src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/YuzuApplication.kt
-
117src/android/app/src/main/java/org/yuzu/yuzu_emu/adapters/DriverAdapter.kt
-
185src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriverManagerFragment.kt
-
75src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/DriversLoadingDialogFragment.kt
-
29src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
-
41src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/HomeSettingsFragment.kt
-
8src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/IndeterminateProgressDialogFragment.kt
-
158src/android/app/src/main/java/org/yuzu/yuzu_emu/model/DriverViewModel.kt
-
62src/android/app/src/main/java/org/yuzu/yuzu_emu/ui/main/MainActivity.kt
-
7src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/DocumentsTree.kt
-
97src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/FileUtil.kt
-
4src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GameHelper.kt
-
228src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverHelper.kt
-
112src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/GpuDriverMetadata.kt
-
9src/android/app/src/main/res/drawable/ic_build.xml
-
9src/android/app/src/main/res/drawable/ic_delete.xml
-
89src/android/app/src/main/res/layout/card_driver_option.xml
-
48src/android/app/src/main/res/layout/fragment_driver_manager.xml
-
7src/android/app/src/main/res/navigation/home_navigation.xml
-
2src/android/app/src/main/res/values-de/strings.xml
-
2src/android/app/src/main/res/values-es/strings.xml
-
2src/android/app/src/main/res/values-fr/strings.xml
-
2src/android/app/src/main/res/values-it/strings.xml
-
2src/android/app/src/main/res/values-ja/strings.xml
-
2src/android/app/src/main/res/values-ko/strings.xml
-
2src/android/app/src/main/res/values-nb/strings.xml
-
2src/android/app/src/main/res/values-pl/strings.xml
-
2src/android/app/src/main/res/values-pt-rBR/strings.xml
-
2src/android/app/src/main/res/values-pt-rPT/strings.xml
-
2src/android/app/src/main/res/values-ru/strings.xml
-
2src/android/app/src/main/res/values-uk/strings.xml
-
2src/android/app/src/main/res/values-zh-rCN/strings.xml
-
2src/android/app/src/main/res/values-zh-rTW/strings.xml
-
2src/android/app/src/main/res/values/dimens.xml
-
7src/android/app/src/main/res/values/strings.xml
@ -0,0 +1,117 @@ |
|||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.adapters |
||||
|
|
||||
|
import android.text.TextUtils |
||||
|
import android.view.LayoutInflater |
||||
|
import android.view.View |
||||
|
import android.view.ViewGroup |
||||
|
import androidx.recyclerview.widget.AsyncDifferConfig |
||||
|
import androidx.recyclerview.widget.DiffUtil |
||||
|
import androidx.recyclerview.widget.ListAdapter |
||||
|
import androidx.recyclerview.widget.RecyclerView |
||||
|
import org.yuzu.yuzu_emu.R |
||||
|
import org.yuzu.yuzu_emu.databinding.CardDriverOptionBinding |
||||
|
import org.yuzu.yuzu_emu.model.DriverViewModel |
||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata |
||||
|
|
||||
|
class DriverAdapter(private val driverViewModel: DriverViewModel) : |
||||
|
ListAdapter<Pair<String, GpuDriverMetadata>, DriverAdapter.DriverViewHolder>( |
||||
|
AsyncDifferConfig.Builder(DiffCallback()).build() |
||||
|
) { |
||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DriverViewHolder { |
||||
|
val binding = |
||||
|
CardDriverOptionBinding.inflate(LayoutInflater.from(parent.context), parent, false) |
||||
|
return DriverViewHolder(binding) |
||||
|
} |
||||
|
|
||||
|
override fun getItemCount(): Int = currentList.size |
||||
|
|
||||
|
override fun onBindViewHolder(holder: DriverViewHolder, position: Int) = |
||||
|
holder.bind(currentList[position]) |
||||
|
|
||||
|
private fun onSelectDriver(position: Int) { |
||||
|
driverViewModel.setSelectedDriverIndex(position) |
||||
|
notifyItemChanged(driverViewModel.previouslySelectedDriver) |
||||
|
notifyItemChanged(driverViewModel.selectedDriver) |
||||
|
} |
||||
|
|
||||
|
private fun onDeleteDriver(driverData: Pair<String, GpuDriverMetadata>, position: Int) { |
||||
|
if (driverViewModel.selectedDriver > position) { |
||||
|
driverViewModel.setSelectedDriverIndex(driverViewModel.selectedDriver - 1) |
||||
|
} |
||||
|
if (GpuDriverHelper.customDriverData == driverData.second) { |
||||
|
driverViewModel.setSelectedDriverIndex(0) |
||||
|
} |
||||
|
driverViewModel.driversToDelete.add(driverData.first) |
||||
|
driverViewModel.removeDriver(driverData) |
||||
|
notifyItemRemoved(position) |
||||
|
notifyItemChanged(driverViewModel.selectedDriver) |
||||
|
} |
||||
|
|
||||
|
inner class DriverViewHolder(val binding: CardDriverOptionBinding) : |
||||
|
RecyclerView.ViewHolder(binding.root) { |
||||
|
private lateinit var driverData: Pair<String, GpuDriverMetadata> |
||||
|
|
||||
|
fun bind(driverData: Pair<String, GpuDriverMetadata>) { |
||||
|
this.driverData = driverData |
||||
|
val driver = driverData.second |
||||
|
|
||||
|
binding.apply { |
||||
|
radioButton.isChecked = driverViewModel.selectedDriver == bindingAdapterPosition |
||||
|
root.setOnClickListener { |
||||
|
onSelectDriver(bindingAdapterPosition) |
||||
|
} |
||||
|
buttonDelete.setOnClickListener { |
||||
|
onDeleteDriver(driverData, bindingAdapterPosition) |
||||
|
} |
||||
|
|
||||
|
// Delay marquee by 3s |
||||
|
title.postDelayed( |
||||
|
{ |
||||
|
title.isSelected = true |
||||
|
title.ellipsize = TextUtils.TruncateAt.MARQUEE |
||||
|
version.isSelected = true |
||||
|
version.ellipsize = TextUtils.TruncateAt.MARQUEE |
||||
|
description.isSelected = true |
||||
|
description.ellipsize = TextUtils.TruncateAt.MARQUEE |
||||
|
}, |
||||
|
3000 |
||||
|
) |
||||
|
if (driver.name == null) { |
||||
|
title.setText(R.string.system_gpu_driver) |
||||
|
description.text = "" |
||||
|
version.text = "" |
||||
|
version.visibility = View.GONE |
||||
|
description.visibility = View.GONE |
||||
|
buttonDelete.visibility = View.GONE |
||||
|
} else { |
||||
|
title.text = driver.name |
||||
|
version.text = driver.version |
||||
|
description.text = driver.description |
||||
|
version.visibility = View.VISIBLE |
||||
|
description.visibility = View.VISIBLE |
||||
|
buttonDelete.visibility = View.VISIBLE |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private class DiffCallback : DiffUtil.ItemCallback<Pair<String, GpuDriverMetadata>>() { |
||||
|
override fun areItemsTheSame( |
||||
|
oldItem: Pair<String, GpuDriverMetadata>, |
||||
|
newItem: Pair<String, GpuDriverMetadata> |
||||
|
): Boolean { |
||||
|
return oldItem.first == newItem.first |
||||
|
} |
||||
|
|
||||
|
override fun areContentsTheSame( |
||||
|
oldItem: Pair<String, GpuDriverMetadata>, |
||||
|
newItem: Pair<String, GpuDriverMetadata> |
||||
|
): Boolean { |
||||
|
return oldItem.second == newItem.second |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,185 @@ |
|||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.fragments |
||||
|
|
||||
|
import android.os.Bundle |
||||
|
import android.view.LayoutInflater |
||||
|
import android.view.View |
||||
|
import android.view.ViewGroup |
||||
|
import androidx.activity.result.contract.ActivityResultContracts |
||||
|
import androidx.core.view.ViewCompat |
||||
|
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.recyclerview.widget.GridLayoutManager |
||||
|
import com.google.android.material.transition.MaterialSharedAxis |
||||
|
import kotlinx.coroutines.flow.collectLatest |
||||
|
import kotlinx.coroutines.launch |
||||
|
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.DriverViewModel |
||||
|
import org.yuzu.yuzu_emu.model.HomeViewModel |
||||
|
import org.yuzu.yuzu_emu.utils.FileUtil |
||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
||||
|
import java.io.IOException |
||||
|
|
||||
|
class DriverManagerFragment : Fragment() { |
||||
|
private var _binding: FragmentDriverManagerBinding? = null |
||||
|
private val binding get() = _binding!! |
||||
|
|
||||
|
private val homeViewModel: HomeViewModel by activityViewModels() |
||||
|
private val driverViewModel: DriverViewModel by activityViewModels() |
||||
|
|
||||
|
override fun onCreate(savedInstanceState: Bundle?) { |
||||
|
super.onCreate(savedInstanceState) |
||||
|
enterTransition = MaterialSharedAxis(MaterialSharedAxis.X, true) |
||||
|
returnTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
||||
|
reenterTransition = MaterialSharedAxis(MaterialSharedAxis.X, false) |
||||
|
} |
||||
|
|
||||
|
override fun onCreateView( |
||||
|
inflater: LayoutInflater, |
||||
|
container: ViewGroup?, |
||||
|
savedInstanceState: Bundle? |
||||
|
): View { |
||||
|
_binding = FragmentDriverManagerBinding.inflate(inflater) |
||||
|
return binding.root |
||||
|
} |
||||
|
|
||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
|
super.onViewCreated(view, savedInstanceState) |
||||
|
homeViewModel.setNavigationVisibility(visible = false, animated = true) |
||||
|
homeViewModel.setStatusBarShadeVisibility(visible = false) |
||||
|
|
||||
|
if (!driverViewModel.isInteractionAllowed) { |
||||
|
DriversLoadingDialogFragment().show( |
||||
|
childFragmentManager, |
||||
|
DriversLoadingDialogFragment.TAG |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
binding.toolbarDrivers.setNavigationOnClickListener { |
||||
|
binding.root.findNavController().popBackStack() |
||||
|
} |
||||
|
|
||||
|
binding.buttonInstall.setOnClickListener { |
||||
|
getDriver.launch(arrayOf("application/zip")) |
||||
|
} |
||||
|
|
||||
|
binding.listDrivers.apply { |
||||
|
layoutManager = GridLayoutManager( |
||||
|
requireContext(), |
||||
|
resources.getInteger(R.integer.grid_columns) |
||||
|
) |
||||
|
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() |
||||
|
} |
||||
|
|
||||
|
// Start installing requested driver |
||||
|
override fun onStop() { |
||||
|
super.onStop() |
||||
|
driverViewModel.onCloseDriverManager() |
||||
|
} |
||||
|
|
||||
|
private fun setInsets() = |
||||
|
ViewCompat.setOnApplyWindowInsetsListener( |
||||
|
binding.root |
||||
|
) { _: View, windowInsets: WindowInsetsCompat -> |
||||
|
val barInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()) |
||||
|
val cutoutInsets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout()) |
||||
|
|
||||
|
val leftInsets = barInsets.left + cutoutInsets.left |
||||
|
val rightInsets = barInsets.right + cutoutInsets.right |
||||
|
|
||||
|
val mlpAppBar = binding.toolbarDrivers.layoutParams as ViewGroup.MarginLayoutParams |
||||
|
mlpAppBar.leftMargin = leftInsets |
||||
|
mlpAppBar.rightMargin = rightInsets |
||||
|
binding.toolbarDrivers.layoutParams = mlpAppBar |
||||
|
|
||||
|
val mlplistDrivers = binding.listDrivers.layoutParams as ViewGroup.MarginLayoutParams |
||||
|
mlplistDrivers.leftMargin = leftInsets |
||||
|
mlplistDrivers.rightMargin = rightInsets |
||||
|
binding.listDrivers.layoutParams = mlplistDrivers |
||||
|
|
||||
|
val fabSpacing = resources.getDimensionPixelSize(R.dimen.spacing_fab) |
||||
|
val mlpFab = |
||||
|
binding.buttonInstall.layoutParams as ViewGroup.MarginLayoutParams |
||||
|
mlpFab.leftMargin = leftInsets + fabSpacing |
||||
|
mlpFab.rightMargin = rightInsets + fabSpacing |
||||
|
mlpFab.bottomMargin = barInsets.bottom + fabSpacing |
||||
|
binding.buttonInstall.layoutParams = mlpFab |
||||
|
|
||||
|
binding.listDrivers.updatePadding( |
||||
|
bottom = barInsets.bottom + |
||||
|
resources.getDimensionPixelSize(R.dimen.spacing_bottom_list_fab) |
||||
|
) |
||||
|
|
||||
|
windowInsets |
||||
|
} |
||||
|
|
||||
|
private val getDriver = |
||||
|
registerForActivityResult(ActivityResultContracts.OpenDocument()) { result -> |
||||
|
if (result == null) { |
||||
|
return@registerForActivityResult |
||||
|
} |
||||
|
|
||||
|
IndeterminateProgressDialogFragment.newInstance( |
||||
|
requireActivity(), |
||||
|
R.string.installing_driver, |
||||
|
false |
||||
|
) { |
||||
|
// Ignore file exceptions when a user selects an invalid zip |
||||
|
try { |
||||
|
GpuDriverHelper.copyDriverToInternalStorage(result) |
||||
|
} catch (_: IOException) { |
||||
|
return@newInstance getString(R.string.select_gpu_driver_error) |
||||
|
} |
||||
|
|
||||
|
val driverData = GpuDriverHelper.customDriverData |
||||
|
if (driverData.name == null) { |
||||
|
return@newInstance getString(R.string.select_gpu_driver_error) |
||||
|
} |
||||
|
|
||||
|
val driverInList = |
||||
|
driverViewModel.driverList.value.firstOrNull { it.second == driverData } |
||||
|
if (driverInList != null) { |
||||
|
return@newInstance getString(R.string.driver_already_installed) |
||||
|
} else { |
||||
|
driverViewModel.addDriver( |
||||
|
Pair( |
||||
|
"${GpuDriverHelper.driverStoragePath}/${FileUtil.getFilename(result)}", |
||||
|
driverData |
||||
|
) |
||||
|
) |
||||
|
driverViewModel.setNewDriverInstalled(true) |
||||
|
} |
||||
|
return@newInstance Any() |
||||
|
}.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG) |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.fragments |
||||
|
|
||||
|
import android.app.Dialog |
||||
|
import android.os.Bundle |
||||
|
import android.view.LayoutInflater |
||||
|
import android.view.View |
||||
|
import android.view.ViewGroup |
||||
|
import androidx.fragment.app.DialogFragment |
||||
|
import androidx.fragment.app.activityViewModels |
||||
|
import androidx.lifecycle.Lifecycle |
||||
|
import androidx.lifecycle.lifecycleScope |
||||
|
import androidx.lifecycle.repeatOnLifecycle |
||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder |
||||
|
import kotlinx.coroutines.launch |
||||
|
import org.yuzu.yuzu_emu.R |
||||
|
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding |
||||
|
import org.yuzu.yuzu_emu.model.DriverViewModel |
||||
|
|
||||
|
class DriversLoadingDialogFragment : DialogFragment() { |
||||
|
private val driverViewModel: DriverViewModel by activityViewModels() |
||||
|
|
||||
|
private lateinit var binding: DialogProgressBarBinding |
||||
|
|
||||
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { |
||||
|
binding = DialogProgressBarBinding.inflate(layoutInflater) |
||||
|
binding.progressBar.isIndeterminate = true |
||||
|
|
||||
|
isCancelable = false |
||||
|
|
||||
|
return MaterialAlertDialogBuilder(requireContext()) |
||||
|
.setTitle(R.string.loading) |
||||
|
.setView(binding.root) |
||||
|
.create() |
||||
|
} |
||||
|
|
||||
|
override fun onCreateView( |
||||
|
inflater: LayoutInflater, |
||||
|
container: ViewGroup?, |
||||
|
savedInstanceState: Bundle? |
||||
|
): View = binding.root |
||||
|
|
||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { |
||||
|
super.onViewCreated(view, savedInstanceState) |
||||
|
viewLifecycleOwner.lifecycleScope.apply { |
||||
|
launch { |
||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) { |
||||
|
driverViewModel.areDriversLoading.collect { checkForDismiss() } |
||||
|
} |
||||
|
} |
||||
|
launch { |
||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) { |
||||
|
driverViewModel.isDriverReady.collect { checkForDismiss() } |
||||
|
} |
||||
|
} |
||||
|
launch { |
||||
|
repeatOnLifecycle(Lifecycle.State.RESUMED) { |
||||
|
driverViewModel.isDeletingDrivers.collect { checkForDismiss() } |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun checkForDismiss() { |
||||
|
if (driverViewModel.isInteractionAllowed) { |
||||
|
dismiss() |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
companion object { |
||||
|
const val TAG = "DriversLoadingDialogFragment" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,158 @@ |
|||||
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project |
||||
|
// SPDX-License-Identifier: GPL-2.0-or-later |
||||
|
|
||||
|
package org.yuzu.yuzu_emu.model |
||||
|
|
||||
|
import androidx.lifecycle.ViewModel |
||||
|
import androidx.lifecycle.viewModelScope |
||||
|
import kotlinx.coroutines.Dispatchers |
||||
|
import kotlinx.coroutines.flow.MutableStateFlow |
||||
|
import kotlinx.coroutines.flow.StateFlow |
||||
|
import kotlinx.coroutines.launch |
||||
|
import kotlinx.coroutines.withContext |
||||
|
import org.yuzu.yuzu_emu.R |
||||
|
import org.yuzu.yuzu_emu.YuzuApplication |
||||
|
import org.yuzu.yuzu_emu.utils.FileUtil |
||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper |
||||
|
import org.yuzu.yuzu_emu.utils.GpuDriverMetadata |
||||
|
import java.io.BufferedOutputStream |
||||
|
import java.io.File |
||||
|
|
||||
|
class DriverViewModel : ViewModel() { |
||||
|
private val _areDriversLoading = MutableStateFlow(false) |
||||
|
val areDriversLoading: StateFlow<Boolean> get() = _areDriversLoading |
||||
|
|
||||
|
private val _isDriverReady = MutableStateFlow(true) |
||||
|
val isDriverReady: StateFlow<Boolean> get() = _isDriverReady |
||||
|
|
||||
|
private val _isDeletingDrivers = MutableStateFlow(false) |
||||
|
val isDeletingDrivers: StateFlow<Boolean> get() = _isDeletingDrivers |
||||
|
|
||||
|
private val _driverList = MutableStateFlow(mutableListOf<Pair<String, GpuDriverMetadata>>()) |
||||
|
val driverList: StateFlow<MutableList<Pair<String, GpuDriverMetadata>>> get() = _driverList |
||||
|
|
||||
|
var previouslySelectedDriver = 0 |
||||
|
var selectedDriver = -1 |
||||
|
|
||||
|
private val _selectedDriverMetadata = |
||||
|
MutableStateFlow( |
||||
|
GpuDriverHelper.customDriverData.name |
||||
|
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) |
||||
|
) |
||||
|
val selectedDriverMetadata: StateFlow<String> get() = _selectedDriverMetadata |
||||
|
|
||||
|
private val _newDriverInstalled = MutableStateFlow(false) |
||||
|
val newDriverInstalled: StateFlow<Boolean> get() = _newDriverInstalled |
||||
|
|
||||
|
val driversToDelete = mutableListOf<String>() |
||||
|
|
||||
|
val isInteractionAllowed |
||||
|
get() = !areDriversLoading.value && isDriverReady.value && !isDeletingDrivers.value |
||||
|
|
||||
|
init { |
||||
|
_areDriversLoading.value = true |
||||
|
viewModelScope.launch { |
||||
|
withContext(Dispatchers.IO) { |
||||
|
val drivers = GpuDriverHelper.getDrivers() |
||||
|
val currentDriverMetadata = GpuDriverHelper.customDriverData |
||||
|
for (i in drivers.indices) { |
||||
|
if (drivers[i].second == currentDriverMetadata) { |
||||
|
setSelectedDriverIndex(i) |
||||
|
break |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// 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()) |
||||
|
) |
||||
|
drivers.add(Pair(driverToSave.path, currentDriverMetadata)) |
||||
|
setSelectedDriverIndex(drivers.size - 1) |
||||
|
} |
||||
|
|
||||
|
_driverList.value = drivers |
||||
|
_areDriversLoading.value = false |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
fun setSelectedDriverIndex(value: Int) { |
||||
|
if (selectedDriver != -1) { |
||||
|
previouslySelectedDriver = 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) { |
||||
|
setSelectedDriverIndex(_driverList.value.size) |
||||
|
_driverList.value.add(driverData) |
||||
|
_selectedDriverMetadata.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) |
||||
|
} |
||||
|
|
||||
|
fun onCloseDriverManager() { |
||||
|
_isDeletingDrivers.value = true |
||||
|
viewModelScope.launch { |
||||
|
withContext(Dispatchers.IO) { |
||||
|
driversToDelete.forEach { |
||||
|
val driver = File(it) |
||||
|
if (driver.exists()) { |
||||
|
driver.delete() |
||||
|
} |
||||
|
} |
||||
|
driversToDelete.clear() |
||||
|
_isDeletingDrivers.value = false |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
if (GpuDriverHelper.customDriverData == driverList.value[selectedDriver].second) { |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
_isDriverReady.value = false |
||||
|
viewModelScope.launch { |
||||
|
withContext(Dispatchers.IO) { |
||||
|
if (selectedDriver == 0) { |
||||
|
GpuDriverHelper.installDefaultDriver() |
||||
|
setDriverReady() |
||||
|
return@withContext |
||||
|
} |
||||
|
|
||||
|
val driverToInstall = File(driverList.value[selectedDriver].first) |
||||
|
if (driverToInstall.exists()) { |
||||
|
GpuDriverHelper.installCustomDriver(driverToInstall) |
||||
|
} else { |
||||
|
GpuDriverHelper.installDefaultDriver() |
||||
|
} |
||||
|
setDriverReady() |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private fun setDriverReady() { |
||||
|
_isDriverReady.value = true |
||||
|
_selectedDriverMetadata.value = GpuDriverHelper.customDriverData.name |
||||
|
?: YuzuApplication.appContext.getString(R.string.system_gpu_driver) |
||||
|
} |
||||
|
} |
||||
@ -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/colorControlNormal" |
||||
|
android:pathData="M22.7,19l-9.1,-9.1c0.9,-2.3 0.4,-5 -1.5,-6.9 -2,-2 -5,-2.4 -7.4,-1.3L9,6 6,9 1.6,4.7C0.4,7.1 0.9,10.1 2.9,12.1c1.9,1.9 4.6,2.4 6.9,1.5l9.1,9.1c0.4,0.4 1,0.4 1.4,0l2.3,-2.3c0.5,-0.4 0.5,-1.1 0.1,-1.4z" /> |
||||
|
</vector> |
||||
@ -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/colorControlNormal" |
||||
|
android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z" /> |
||||
|
</vector> |
||||
@ -0,0 +1,89 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<com.google.android.material.card.MaterialCardView 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" |
||||
|
style="?attr/materialCardViewOutlinedStyle" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:layout_marginHorizontal="16dp" |
||||
|
android:layout_marginVertical="12dp" |
||||
|
android:background="?attr/selectableItemBackground" |
||||
|
android:clickable="true" |
||||
|
android:focusable="true"> |
||||
|
|
||||
|
<LinearLayout |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:orientation="horizontal" |
||||
|
android:layout_gravity="center" |
||||
|
android:padding="16dp"> |
||||
|
|
||||
|
<RadioButton |
||||
|
android:id="@+id/radio_button" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:layout_gravity="center_vertical" |
||||
|
android:clickable="false" |
||||
|
android:checked="false" /> |
||||
|
|
||||
|
<LinearLayout |
||||
|
android:layout_width="0dp" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:layout_weight="1" |
||||
|
android:orientation="vertical" |
||||
|
android:layout_gravity="center_vertical"> |
||||
|
|
||||
|
<com.google.android.material.textview.MaterialTextView |
||||
|
android:id="@+id/title" |
||||
|
style="@style/TextAppearance.Material3.TitleMedium" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:ellipsize="none" |
||||
|
android:marqueeRepeatLimit="marquee_forever" |
||||
|
android:requiresFadingEdge="horizontal" |
||||
|
android:singleLine="true" |
||||
|
android:textAlignment="viewStart" |
||||
|
tools:text="@string/select_gpu_driver_default" /> |
||||
|
|
||||
|
<com.google.android.material.textview.MaterialTextView |
||||
|
android:id="@+id/version" |
||||
|
style="@style/TextAppearance.Material3.BodyMedium" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:layout_marginTop="6dp" |
||||
|
android:ellipsize="none" |
||||
|
android:marqueeRepeatLimit="marquee_forever" |
||||
|
android:requiresFadingEdge="horizontal" |
||||
|
android:singleLine="true" |
||||
|
android:textAlignment="viewStart" |
||||
|
tools:text="@string/install_gpu_driver_description" /> |
||||
|
|
||||
|
<com.google.android.material.textview.MaterialTextView |
||||
|
android:id="@+id/description" |
||||
|
style="@style/TextAppearance.Material3.BodyMedium" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:layout_marginTop="6dp" |
||||
|
android:ellipsize="none" |
||||
|
android:marqueeRepeatLimit="marquee_forever" |
||||
|
android:requiresFadingEdge="horizontal" |
||||
|
android:singleLine="true" |
||||
|
android:textAlignment="viewStart" |
||||
|
tools:text="@string/install_gpu_driver_description" /> |
||||
|
|
||||
|
</LinearLayout> |
||||
|
|
||||
|
<Button |
||||
|
android:id="@+id/button_delete" |
||||
|
style="@style/Widget.Material3.Button.IconButton" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:layout_gravity="center_vertical" |
||||
|
android:contentDescription="@string/delete" |
||||
|
android:tooltipText="@string/delete" |
||||
|
app:icon="@drawable/ic_delete" |
||||
|
app:iconTint="?attr/colorControlNormal" /> |
||||
|
|
||||
|
</LinearLayout> |
||||
|
|
||||
|
</com.google.android.material.card.MaterialCardView> |
||||
@ -0,0 +1,48 @@ |
|||||
|
<?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/coordinator_licenses" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="match_parent" |
||||
|
android:background="?attr/colorSurface"> |
||||
|
|
||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="match_parent"> |
||||
|
|
||||
|
<com.google.android.material.appbar.AppBarLayout |
||||
|
android:id="@+id/appbar_drivers" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:fitsSystemWindows="true" |
||||
|
app:liftOnScrollTargetViewId="@id/list_drivers"> |
||||
|
|
||||
|
<com.google.android.material.appbar.MaterialToolbar |
||||
|
android:id="@+id/toolbar_drivers" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="?attr/actionBarSize" |
||||
|
app:navigationIcon="@drawable/ic_back" |
||||
|
app:title="@string/gpu_driver_manager" /> |
||||
|
|
||||
|
</com.google.android.material.appbar.AppBarLayout> |
||||
|
|
||||
|
<androidx.recyclerview.widget.RecyclerView |
||||
|
android:id="@+id/list_drivers" |
||||
|
android:layout_width="match_parent" |
||||
|
android:layout_height="match_parent" |
||||
|
android:clipToPadding="false" |
||||
|
app:layout_behavior="@string/appbar_scrolling_view_behavior" /> |
||||
|
|
||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
||||
|
|
||||
|
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton |
||||
|
android:id="@+id/button_install" |
||||
|
android:layout_width="wrap_content" |
||||
|
android:layout_height="wrap_content" |
||||
|
android:layout_gravity="bottom|end" |
||||
|
android:text="@string/install" |
||||
|
app:icon="@drawable/ic_add" |
||||
|
app:layout_constraintBottom_toBottomOf="parent" |
||||
|
app:layout_constraintEnd_toEndOf="parent" /> |
||||
|
|
||||
|
</androidx.constraintlayout.widget.ConstraintLayout> |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue