android: Use callback to update progress bar dialogs

This commit is contained in:
t895 2024-01-19 01:06:10 -05:00
parent ccd3dd842f
commit d79d4d5986
10 changed files with 236 additions and 121 deletions

View file

@ -156,22 +156,22 @@ class AddonsFragment : Fragment() {
descriptionId = R.string.invalid_directory_description
)
if (isValid) {
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.installing_game_content,
false
) {
) { progressCallback, _ ->
val parentDirectoryName = externalAddonDirectory.name
val internalAddonDirectory =
File(args.game.addonDir + parentDirectoryName)
try {
externalAddonDirectory.copyFilesTo(internalAddonDirectory)
externalAddonDirectory.copyFilesTo(internalAddonDirectory, progressCallback)
} catch (_: Exception) {
return@newInstance errorMessage
}
addonViewModel.refreshAddons()
return@newInstance getString(R.string.addon_installed_successfully)
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
} else {
errorMessage.show(parentFragmentManager, MessageDialogFragment.TAG)
}

View file

@ -173,11 +173,11 @@ class DriverManagerFragment : Fragment() {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.installing_driver,
false
) {
) { _, _ ->
val driverPath =
"${GpuDriverHelper.driverStoragePath}${FileUtil.getFilename(result)}"
val driverFile = File(driverPath)
@ -213,6 +213,6 @@ class DriverManagerFragment : Fragment() {
}
}
return@newInstance Any()
}.show(childFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(childFragmentManager, ProgressDialogFragment.TAG)
}
}

View file

@ -44,7 +44,6 @@ import org.yuzu.yuzu_emu.utils.FileUtil
import org.yuzu.yuzu_emu.utils.GameIconUtils
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
import org.yuzu.yuzu_emu.utils.MemoryUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
@ -357,27 +356,17 @@ class GamePropertiesFragment : Fragment() {
return@registerForActivityResult
}
val inputZip = requireContext().contentResolver.openInputStream(result)
val savesFolder = File(args.game.saveDir)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
if (inputZip == null) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
) {
) { _, _ ->
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
FileUtil.unzipToInternalStorage(result.toString(), cacheSaveDir)
val files = cacheSaveDir.listFiles()
var savesFolderFile: File? = null
if (files != null) {
@ -422,7 +411,7 @@ class GamePropertiesFragment : Fragment() {
Toast.LENGTH_LONG
).show()
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
/**
@ -436,11 +425,11 @@ class GamePropertiesFragment : Fragment() {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
) {
) { _, _ ->
val saveLocation = args.game.saveDir
val zipResult = FileUtil.zipFromInternalStorage(
File(saveLocation),
@ -452,6 +441,6 @@ class GamePropertiesFragment : Fragment() {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
}

View file

@ -34,7 +34,6 @@ import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.ui.main.MainActivity
import org.yuzu.yuzu_emu.utils.DirectoryInitialization
import org.yuzu.yuzu_emu.utils.FileUtil
import java.io.BufferedInputStream
import java.io.BufferedOutputStream
import java.io.File
import java.math.BigInteger
@ -195,26 +194,20 @@ class InstallableFragment : Fragment() {
return@registerForActivityResult
}
val inputZip = requireContext().contentResolver.openInputStream(result)
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
if (inputZip == null) {
Toast.makeText(
YuzuApplication.appContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_importing,
false
) {
) { progressCallback, _ ->
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheSaveDir)
FileUtil.unzipToInternalStorage(
result.toString(),
cacheSaveDir,
progressCallback
)
val files = cacheSaveDir.listFiles()
var successfulImports = 0
var failedImports = 0
@ -287,7 +280,7 @@ class InstallableFragment : Fragment() {
Toast.LENGTH_LONG
).show()
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
private val exportSaves = registerForActivityResult(
@ -297,11 +290,11 @@ class InstallableFragment : Fragment() {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
requireActivity(),
R.string.save_files_exporting,
false
) {
) { _, _ ->
val cacheSaveDir = File("${requireContext().cacheDir.path}/saves/")
cacheSaveDir.mkdir()
@ -338,6 +331,6 @@ class InstallableFragment : Fragment() {
TaskState.Completed -> getString(R.string.export_success)
TaskState.Cancelled, TaskState.Failed -> getString(R.string.export_failed)
}
}.show(parentFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(parentFragmentManager, ProgressDialogFragment.TAG)
}
}

View file

@ -23,11 +23,13 @@ import org.yuzu.yuzu_emu.R
import org.yuzu.yuzu_emu.databinding.DialogProgressBarBinding
import org.yuzu.yuzu_emu.model.TaskViewModel
class IndeterminateProgressDialogFragment : DialogFragment() {
class ProgressDialogFragment : DialogFragment() {
private val taskViewModel: TaskViewModel by activityViewModels()
private lateinit var binding: DialogProgressBarBinding
private val PROGRESS_BAR_RESOLUTION = 1000
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val titleId = requireArguments().getInt(TITLE)
val cancellable = requireArguments().getBoolean(CANCELLABLE)
@ -61,6 +63,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.message.isSelected = true
viewLifecycleOwner.lifecycleScope.apply {
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
@ -97,6 +100,35 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.progress.collect {
if (it != 0.0) {
binding.progressBar.apply {
isIndeterminate = false
progress = (
(it / taskViewModel.maxProgress.value) *
PROGRESS_BAR_RESOLUTION
).toInt()
min = 0
max = PROGRESS_BAR_RESOLUTION
}
}
}
}
}
launch {
repeatOnLifecycle(Lifecycle.State.CREATED) {
taskViewModel.message.collect {
if (it.isEmpty()) {
binding.message.visibility = View.GONE
} else {
binding.message.visibility = View.VISIBLE
binding.message.text = it
}
}
}
}
}
}
@ -108,6 +140,7 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE)
negativeButton.setOnClickListener {
alertDialog.setTitle(getString(R.string.cancelling))
binding.progressBar.isIndeterminate = true
taskViewModel.setCancelled(true)
}
}
@ -122,9 +155,12 @@ class IndeterminateProgressDialogFragment : DialogFragment() {
activity: FragmentActivity,
titleId: Int,
cancellable: Boolean = false,
task: suspend () -> Any
): IndeterminateProgressDialogFragment {
val dialog = IndeterminateProgressDialogFragment()
task: suspend (
progressCallback: (max: Long, progress: Long) -> Boolean,
messageCallback: (message: String) -> Unit
) -> Any
): ProgressDialogFragment {
val dialog = ProgressDialogFragment()
val args = Bundle()
ViewModelProvider(activity)[TaskViewModel::class.java].task = task
args.putInt(TITLE, titleId)

View file

@ -8,6 +8,7 @@ import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
class TaskViewModel : ViewModel() {
@ -23,13 +24,28 @@ class TaskViewModel : ViewModel() {
val cancelled: StateFlow<Boolean> get() = _cancelled
private val _cancelled = MutableStateFlow(false)
lateinit var task: suspend () -> Any
private val _progress = MutableStateFlow(0.0)
val progress = _progress.asStateFlow()
private val _maxProgress = MutableStateFlow(0.0)
val maxProgress = _maxProgress.asStateFlow()
private val _message = MutableStateFlow("")
val message = _message.asStateFlow()
lateinit var task: suspend (
progressCallback: (max: Long, progress: Long) -> Boolean,
messageCallback: (message: String) -> Unit
) -> Any
fun clear() {
_result.value = Any()
_isComplete.value = false
_isRunning.value = false
_cancelled.value = false
_progress.value = 0.0
_maxProgress.value = 0.0
_message.value = ""
}
fun setCancelled(value: Boolean) {
@ -43,7 +59,16 @@ class TaskViewModel : ViewModel() {
_isRunning.value = true
viewModelScope.launch(Dispatchers.IO) {
val res = task()
val res = task(
{ max, progress ->
_maxProgress.value = max.toDouble()
_progress.value = progress.toDouble()
return@task cancelled.value
},
{ message ->
_message.value = message
}
)
_result.value = res
_isComplete.value = true
_isRunning.value = false

View file

@ -38,12 +38,13 @@ import org.yuzu.yuzu_emu.activities.EmulationActivity
import org.yuzu.yuzu_emu.databinding.ActivityMainBinding
import org.yuzu.yuzu_emu.features.settings.model.Settings
import org.yuzu.yuzu_emu.fragments.AddGameFolderDialogFragment
import org.yuzu.yuzu_emu.fragments.IndeterminateProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.ProgressDialogFragment
import org.yuzu.yuzu_emu.fragments.MessageDialogFragment
import org.yuzu.yuzu_emu.model.AddonViewModel
import org.yuzu.yuzu_emu.model.DriverViewModel
import org.yuzu.yuzu_emu.model.GamesViewModel
import org.yuzu.yuzu_emu.model.HomeViewModel
import org.yuzu.yuzu_emu.model.InstallResult
import org.yuzu.yuzu_emu.model.TaskState
import org.yuzu.yuzu_emu.model.TaskViewModel
import org.yuzu.yuzu_emu.utils.*
@ -369,26 +370,23 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
val inputZip = contentResolver.openInputStream(result)
if (inputZip == null) {
Toast.makeText(
applicationContext,
getString(R.string.fatal_error),
Toast.LENGTH_LONG
).show()
return@registerForActivityResult
}
val filterNCA = FilenameFilter { _, dirName -> dirName.endsWith(".nca") }
val firmwarePath =
File(DirectoryInitialization.userDirectory + "/nand/system/Contents/registered/")
val cacheFirmwareDir = File("${cacheDir.path}/registered/")
val task: () -> Any = {
ProgressDialogFragment.newInstance(
this,
R.string.firmware_installing
) { progressCallback, _ ->
var messageToShow: Any
try {
FileUtil.unzipToInternalStorage(BufferedInputStream(inputZip), cacheFirmwareDir)
FileUtil.unzipToInternalStorage(
result.toString(),
cacheFirmwareDir,
progressCallback
)
val unfilteredNumOfFiles = cacheFirmwareDir.list()?.size ?: -1
val filteredNumOfFiles = cacheFirmwareDir.list(filterNCA)?.size ?: -2
messageToShow = if (unfilteredNumOfFiles != filteredNumOfFiles) {
@ -404,18 +402,13 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
getString(R.string.save_file_imported_success)
}
} catch (e: Exception) {
Log.error("[MainActivity] Firmware install failed - ${e.message}")
messageToShow = getString(R.string.fatal_error)
} finally {
cacheFirmwareDir.deleteRecursively()
}
messageToShow
}
IndeterminateProgressDialogFragment.newInstance(
this,
R.string.firmware_installing,
task = task
).show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val getAmiiboKey =
@ -474,11 +467,11 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
this@MainActivity,
R.string.verifying_content,
false
) {
) { _, _ ->
var updatesMatchProgram = true
for (document in documents) {
val valid = NativeLibrary.doesUpdateMatchProgram(
@ -501,44 +494,42 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
positiveAction = { homeViewModel.setContentToInstall(documents) }
)
}
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
private fun installContent(documents: List<Uri>) {
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
this@MainActivity,
R.string.installing_game_content
) {
) { progressCallback, messageCallback ->
var installSuccess = 0
var installOverwrite = 0
var errorBaseGame = 0
var errorExtension = 0
var errorOther = 0
var error = 0
documents.forEach {
messageCallback.invoke(FileUtil.getFilename(it))
when (
NativeLibrary.installFileToNand(
it.toString(),
FileUtil.getExtension(it)
InstallResult.from(
NativeLibrary.installFileToNand(
it.toString(),
progressCallback
)
)
) {
NativeLibrary.InstallFileToNandResult.Success -> {
InstallResult.Success -> {
installSuccess += 1
}
NativeLibrary.InstallFileToNandResult.SuccessFileOverwritten -> {
InstallResult.Overwrite -> {
installOverwrite += 1
}
NativeLibrary.InstallFileToNandResult.ErrorBaseGame -> {
InstallResult.BaseInstallAttempted -> {
errorBaseGame += 1
}
NativeLibrary.InstallFileToNandResult.ErrorFilenameExtension -> {
errorExtension += 1
}
else -> {
errorOther += 1
InstallResult.Failure -> {
error += 1
}
}
}
@ -565,7 +556,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
installResult.append(separator)
}
val errorTotal: Int = errorBaseGame + errorExtension + errorOther
val errorTotal: Int = errorBaseGame + error
if (errorTotal > 0) {
installResult.append(separator)
installResult.append(
@ -582,14 +573,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
)
installResult.append(separator)
}
if (errorExtension > 0) {
installResult.append(separator)
installResult.append(
getString(R.string.install_game_content_failure_file_extension)
)
installResult.append(separator)
}
if (errorOther > 0) {
if (error > 0) {
installResult.append(
getString(R.string.install_game_content_failure_description)
)
@ -608,7 +592,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
descriptionString = installResult.toString().trim()
)
}
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val exportUserData = registerForActivityResult(
@ -618,16 +602,16 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
this,
R.string.exporting_user_data,
true
) {
) { progressCallback, _ ->
val zipResult = FileUtil.zipFromInternalStorage(
File(DirectoryInitialization.userDirectory!!),
DirectoryInitialization.userDirectory!!,
BufferedOutputStream(contentResolver.openOutputStream(result)),
taskViewModel.cancelled,
progressCallback,
compression = false
)
return@newInstance when (zipResult) {
@ -635,7 +619,7 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
TaskState.Failed -> R.string.export_failed
TaskState.Cancelled -> R.string.user_data_export_cancelled
}
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
val importUserData =
@ -644,10 +628,10 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
return@registerForActivityResult
}
IndeterminateProgressDialogFragment.newInstance(
ProgressDialogFragment.newInstance(
this,
R.string.importing_user_data
) {
) { progressCallback, _ ->
val checkStream =
ZipInputStream(BufferedInputStream(contentResolver.openInputStream(result)))
var isYuzuBackup = false
@ -676,8 +660,9 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
// Copy archive to internal storage
try {
FileUtil.unzipToInternalStorage(
BufferedInputStream(contentResolver.openInputStream(result)),
File(DirectoryInitialization.userDirectory!!)
result.toString(),
File(DirectoryInitialization.userDirectory!!),
progressCallback
)
} catch (e: Exception) {
return@newInstance MessageDialogFragment.newInstance(
@ -694,6 +679,6 @@ class MainActivity : AppCompatActivity(), ThemeProvider {
driverViewModel.reloadDriverData()
return@newInstance getString(R.string.user_data_import_success)
}.show(supportFragmentManager, IndeterminateProgressDialogFragment.TAG)
}.show(supportFragmentManager, ProgressDialogFragment.TAG)
}
}

View file

@ -7,7 +7,6 @@ import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
import kotlinx.coroutines.flow.StateFlow
import java.io.BufferedInputStream
import java.io.File
import java.io.IOException
@ -19,6 +18,7 @@ import org.yuzu.yuzu_emu.YuzuApplication
import org.yuzu.yuzu_emu.model.MinimalDocumentFile
import org.yuzu.yuzu_emu.model.TaskState
import java.io.BufferedOutputStream
import java.io.OutputStream
import java.lang.NullPointerException
import java.nio.charset.StandardCharsets
import java.util.zip.Deflater
@ -283,12 +283,34 @@ object FileUtil {
/**
* Extracts the given zip file into the given directory.
* @param path String representation of a [Uri] or a typical path delimited by '/'
* @param destDir Location to unzip the contents of [path] into
* @param progressCallback Lambda that is called with the total number of files and the current
* progress through the process. Stops execution as soon as possible if this returns true.
*/
@Throws(SecurityException::class)
fun unzipToInternalStorage(zipStream: BufferedInputStream, destDir: File) {
ZipInputStream(zipStream).use { zis ->
fun unzipToInternalStorage(
path: String,
destDir: File,
progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false }
) {
var totalEntries = 0L
ZipInputStream(getInputStream(path)).use { zis ->
var tempEntry = zis.nextEntry
while (tempEntry != null) {
tempEntry = zis.nextEntry
totalEntries++
}
}
var progress = 0L
ZipInputStream(getInputStream(path)).use { zis ->
var entry: ZipEntry? = zis.nextEntry
while (entry != null) {
if (progressCallback.invoke(totalEntries, progress)) {
return@use
}
val newFile = File(destDir, entry.name)
val destinationDirectory = if (entry.isDirectory) newFile else newFile.parentFile
@ -304,6 +326,7 @@ object FileUtil {
newFile.outputStream().use { fos -> zis.copyTo(fos) }
}
entry = zis.nextEntry
progress++
}
}
}
@ -313,14 +336,15 @@ object FileUtil {
* @param inputFile File representation of the item that will be zipped
* @param rootDir Directory containing the inputFile
* @param outputStream Stream where the zip file will be output
* @param cancelled [StateFlow] that reports whether this process has been cancelled
* @param progressCallback Lambda that is called with the total number of files and the current
* progress through the process. Stops execution as soon as possible if this returns true.
* @param compression Disables compression if true
*/
fun zipFromInternalStorage(
inputFile: File,
rootDir: String,
outputStream: BufferedOutputStream,
cancelled: StateFlow<Boolean>? = null,
progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false },
compression: Boolean = true
): TaskState {
try {
@ -330,8 +354,10 @@ object FileUtil {
zos.setLevel(Deflater.NO_COMPRESSION)
}
var count = 0L
val totalFiles = inputFile.walkTopDown().count().toLong()
inputFile.walkTopDown().forEach { file ->
if (cancelled?.value == true) {
if (progressCallback.invoke(totalFiles, count)) {
return TaskState.Cancelled
}
@ -343,6 +369,7 @@ object FileUtil {
if (file.isFile) {
file.inputStream().use { fis -> fis.copyTo(zos) }
}
count++
}
}
}
@ -356,9 +383,14 @@ object FileUtil {
/**
* Helper function that copies the contents of a DocumentFile folder into a [File]
* @param file [File] representation of the folder to copy into
* @param progressCallback Lambda that is called with the total number of files and the current
* progress through the process. Stops execution as soon as possible if this returns true.
* @throws IllegalStateException Fails when trying to copy a folder into a file and vice versa
*/
fun DocumentFile.copyFilesTo(file: File) {
fun DocumentFile.copyFilesTo(
file: File,
progressCallback: (max: Long, progress: Long) -> Boolean = { _, _ -> false }
) {
file.mkdirs()
if (!this.isDirectory || !file.isDirectory) {
throw IllegalStateException(
@ -366,7 +398,13 @@ object FileUtil {
)
}
var count = 0L
val totalFiles = this.listFiles().size.toLong()
this.listFiles().forEach {
if (progressCallback.invoke(totalFiles, count)) {
return
}
val newFile = File(file, it.name!!)
if (it.isDirectory) {
newFile.mkdirs()
@ -381,6 +419,7 @@ object FileUtil {
newFile.outputStream().use { os -> bos.copyTo(os) }
}
}
count++
}
}
@ -427,6 +466,18 @@ object FileUtil {
}
}
fun getInputStream(path: String) = if (path.contains("content://")) {
Uri.parse(path).inputStream()
} else {
File(path).inputStream()
}
fun getOutputStream(path: String) = if (path.contains("content://")) {
Uri.parse(path).outputStream()
} else {
File(path).outputStream()
}
@Throws(IOException::class)
fun getStringFromFile(file: File): String =
String(file.readBytes(), StandardCharsets.UTF_8)
@ -434,4 +485,19 @@ object FileUtil {
@Throws(IOException::class)
fun getStringFromInputStream(stream: InputStream): String =
String(stream.readBytes(), StandardCharsets.UTF_8)
fun DocumentFile.inputStream(): InputStream =
YuzuApplication.appContext.contentResolver.openInputStream(uri)!!
fun DocumentFile.outputStream(): OutputStream =
YuzuApplication.appContext.contentResolver.openOutputStream(uri)!!
fun Uri.inputStream(): InputStream =
YuzuApplication.appContext.contentResolver.openInputStream(this)!!
fun Uri.outputStream(): OutputStream =
YuzuApplication.appContext.contentResolver.openOutputStream(this)!!
fun Uri.asDocumentFile(): DocumentFile? =
DocumentFile.fromSingleUri(YuzuApplication.appContext, this)
}

View file

@ -5,7 +5,6 @@ package org.yuzu.yuzu_emu.utils
import android.net.Uri
import android.os.Build
import java.io.BufferedInputStream
import java.io.File
import java.io.IOException
import org.yuzu.yuzu_emu.NativeLibrary
@ -123,7 +122,7 @@ object GpuDriverHelper {
// Unzip the driver.
try {
FileUtil.unzipToInternalStorage(
BufferedInputStream(copiedFile.inputStream()),
copiedFile.path,
File(driverInstallationPath!!)
)
} catch (e: SecurityException) {
@ -156,7 +155,7 @@ object GpuDriverHelper {
// Unzip the driver to the private installation directory
try {
FileUtil.unzipToInternalStorage(
BufferedInputStream(driver.inputStream()),
driver.path,
File(driverInstallationPath!!)
)
} catch (e: SecurityException) {

View file

@ -1,8 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.progressindicator.LinearProgressIndicator xmlns:android="http://schemas.android.com/apk/res/android"
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
app:trackCornerRadius="4dp" />
android:orientation="vertical">
<com.google.android.material.textview.MaterialTextView
android:id="@+id/message"
style="@style/TextAppearance.Material3.BodyMedium"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="24dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="6dp"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:requiresFadingEdge="horizontal"
android:singleLine="true"
android:textAlignment="viewStart"
android:visibility="gone" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="24dp"
app:trackCornerRadius="4dp" />
</LinearLayout>