Android: Use DialogFragments to direct UserData actions
This commit is contained in:
parent
8d1cf14565
commit
c904e068f0
|
@ -4,7 +4,6 @@ package org.dolphinemu.dolphinemu.activities
|
|||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
|
@ -14,10 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.databinding.ActivityUserDataBinding
|
||||
import org.dolphinemu.dolphinemu.dialogs.NotificationDialog
|
||||
import org.dolphinemu.dolphinemu.dialogs.TaskDialog
|
||||
import org.dolphinemu.dolphinemu.dialogs.UserDataImportWarningDialog
|
||||
import org.dolphinemu.dolphinemu.model.TaskViewModel
|
||||
import org.dolphinemu.dolphinemu.utils.*
|
||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper.enableScrollTint
|
||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper.setNavigationBarColor
|
||||
|
@ -28,10 +31,9 @@ import java.io.IOException
|
|||
import java.util.zip.ZipEntry
|
||||
import java.util.zip.ZipInputStream
|
||||
import java.util.zip.ZipOutputStream
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class UserDataActivity : AppCompatActivity() {
|
||||
private var sMustRestartApp = false
|
||||
private lateinit var taskViewModel: TaskViewModel
|
||||
|
||||
private lateinit var mBinding: ActivityUserDataBinding
|
||||
|
||||
|
@ -79,31 +81,32 @@ class UserDataActivity : AppCompatActivity() {
|
|||
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
|
||||
taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java]
|
||||
if (requestCode == REQUEST_CODE_IMPORT && resultCode == RESULT_OK) {
|
||||
val arguments = Bundle()
|
||||
arguments.putString(
|
||||
UserDataImportWarningDialog.KEY_URI_RESULT,
|
||||
data!!.data!!.toString()
|
||||
)
|
||||
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setMessage(R.string.user_data_import_warning)
|
||||
.setNegativeButton(R.string.no) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
|
||||
.setPositiveButton(R.string.yes) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
|
||||
ThreadUtil.runOnThreadAndShowResult(
|
||||
this,
|
||||
R.string.import_in_progress,
|
||||
R.string.do_not_close_app,
|
||||
{ resources.getString(importUserData(data!!.data!!)) }) {
|
||||
if (sMustRestartApp) {
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
.show()
|
||||
val dialog = UserDataImportWarningDialog()
|
||||
dialog.arguments = arguments
|
||||
dialog.show(supportFragmentManager, UserDataImportWarningDialog.TAG)
|
||||
} else if (requestCode == REQUEST_CODE_EXPORT && resultCode == RESULT_OK) {
|
||||
ThreadUtil.runOnThreadAndShowResult(
|
||||
this,
|
||||
R.string.export_in_progress,
|
||||
0
|
||||
) { resources.getString(exportUserData(data!!.data!!)) }
|
||||
taskViewModel.clear()
|
||||
taskViewModel.task = {
|
||||
val resultResource = exportUserData(data!!.data!!)
|
||||
taskViewModel.setResult(resultResource)
|
||||
}
|
||||
|
||||
val arguments = Bundle()
|
||||
arguments.putInt(TaskDialog.KEY_TITLE, R.string.export_in_progress)
|
||||
arguments.putInt(TaskDialog.KEY_MESSAGE, 0)
|
||||
arguments.putBoolean(TaskDialog.KEY_CANCELLABLE, true)
|
||||
|
||||
val dialog = TaskDialog()
|
||||
dialog.arguments = arguments
|
||||
dialog.show(supportFragmentManager, TaskDialog.TAG)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -118,10 +121,15 @@ class UserDataActivity : AppCompatActivity() {
|
|||
} catch (e2: ActivityNotFoundException) {
|
||||
// Activity not found. Perhaps it was removed by the OEM, or by some new Android version
|
||||
// that didn't exist at the time of writing. Not much we can do other than tell the user.
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setMessage(R.string.user_data_open_system_file_manager_failed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
val arguments = Bundle()
|
||||
arguments.putInt(
|
||||
NotificationDialog.KEY_MESSAGE,
|
||||
R.string.user_data_open_system_file_manager_failed
|
||||
)
|
||||
|
||||
val dialog = NotificationDialog()
|
||||
dialog.arguments = arguments
|
||||
dialog.show(supportFragmentManager, NotificationDialog.TAG)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -140,17 +148,18 @@ class UserDataActivity : AppCompatActivity() {
|
|||
startActivityForResult(intent, REQUEST_CODE_IMPORT)
|
||||
}
|
||||
|
||||
private fun importUserData(source: Uri): Int {
|
||||
fun importUserData(source: Uri): Int {
|
||||
try {
|
||||
if (!isDolphinUserDataBackup(source))
|
||||
return R.string.user_data_import_invalid_file
|
||||
|
||||
taskViewModel.mustRestartApp = true
|
||||
|
||||
contentResolver.openInputStream(source).use { `is` ->
|
||||
ZipInputStream(`is`).use { zis ->
|
||||
val userDirectory = File(DirectoryInitialization.getUserDirectory())
|
||||
val userDirectoryCanonicalized = userDirectory.canonicalPath + '/'
|
||||
|
||||
sMustRestartApp = true
|
||||
deleteChildrenRecursively(userDirectory)
|
||||
|
||||
DirectoryInitialization.getGameListCache(this).delete()
|
||||
|
@ -262,8 +271,12 @@ class UserDataActivity : AppCompatActivity() {
|
|||
private fun exportUserData(zos: ZipOutputStream, input: File, pathRelativeToRoot: File?) {
|
||||
if (input.isDirectory) {
|
||||
val children = input.listFiles() ?: throw IOException("Could not find directory $input")
|
||||
for (child in children) {
|
||||
exportUserData(zos, child, File(pathRelativeToRoot, child.name))
|
||||
|
||||
// Check if the coroutine was cancelled
|
||||
if (!taskViewModel.cancelled) {
|
||||
for (child in children) {
|
||||
exportUserData(zos, child, File(pathRelativeToRoot, child.name))
|
||||
}
|
||||
}
|
||||
if (children.isEmpty() && pathRelativeToRoot != null) {
|
||||
zos.putNextEntry(ZipEntry(pathRelativeToRoot.path + '/'))
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
class NotificationDialog : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(requireArguments().getInt(KEY_MESSAGE))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
return dialog.create()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "NotificationDialog"
|
||||
const val KEY_MESSAGE = "message"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.dolphinemu.dolphinemu.model.TaskViewModel
|
||||
|
||||
class TaskCompleteDialog : DialogFragment() {
|
||||
private lateinit var viewModel: TaskViewModel
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
viewModel = ViewModelProvider(requireActivity())[TaskViewModel::class.java]
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(requireArguments().getInt(KEY_MESSAGE))
|
||||
.setPositiveButton(android.R.string.ok, null)
|
||||
return dialog.create()
|
||||
}
|
||||
|
||||
override fun onDismiss(dialog: DialogInterface) {
|
||||
super.onDismiss(dialog)
|
||||
if (viewModel.onResultDismiss != null)
|
||||
viewModel.onResultDismiss!!.invoke()
|
||||
|
||||
viewModel.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "TaskCompleteDialog"
|
||||
const val KEY_MESSAGE = "message"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.model.TaskViewModel
|
||||
|
||||
class TaskDialog : DialogFragment() {
|
||||
private lateinit var viewModel: TaskViewModel
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
viewModel = ViewModelProvider(requireActivity())[TaskViewModel::class.java]
|
||||
|
||||
val dialogBuilder = MaterialAlertDialogBuilder(requireContext())
|
||||
.setTitle(requireArguments().getInt(KEY_TITLE))
|
||||
.setView(R.layout.dialog_indeterminate_progress)
|
||||
if (requireArguments().getBoolean(KEY_CANCELLABLE)) {
|
||||
dialogBuilder.setCancelable(true)
|
||||
.setNegativeButton(android.R.string.cancel) { dialog: DialogInterface, _: Int ->
|
||||
viewModel.cancelled = true
|
||||
dialog.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = dialogBuilder.create()
|
||||
dialog.setCanceledOnTouchOutside(false)
|
||||
|
||||
val progressMessage = requireArguments().getInt(KEY_MESSAGE)
|
||||
if (progressMessage != 0) dialog.setMessage(resources.getString(progressMessage))
|
||||
|
||||
viewModel.isComplete.observe(this) { complete: Boolean ->
|
||||
if (complete && viewModel.result.value != null) {
|
||||
dialog.dismiss()
|
||||
val notificationArguments = Bundle()
|
||||
notificationArguments.putInt(
|
||||
TaskCompleteDialog.KEY_MESSAGE,
|
||||
viewModel.result.value!!
|
||||
)
|
||||
|
||||
val taskCompleteDialog = TaskCompleteDialog()
|
||||
taskCompleteDialog.arguments = notificationArguments
|
||||
taskCompleteDialog.show(
|
||||
requireActivity().supportFragmentManager,
|
||||
TaskCompleteDialog.TAG
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
viewModel.runTask()
|
||||
return dialog
|
||||
}
|
||||
|
||||
override fun onCancel(dialog: DialogInterface) {
|
||||
super.onCancel(dialog)
|
||||
viewModel.cancelled = true
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "TaskDialog"
|
||||
const val KEY_TITLE = "title"
|
||||
const val KEY_MESSAGE = "message"
|
||||
const val KEY_CANCELLABLE = "cancellable"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.dialogs
|
||||
|
||||
import android.app.Dialog
|
||||
import android.content.DialogInterface
|
||||
import android.os.Bundle
|
||||
import androidx.core.net.toUri
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import org.dolphinemu.dolphinemu.R
|
||||
import org.dolphinemu.dolphinemu.activities.UserDataActivity
|
||||
import org.dolphinemu.dolphinemu.model.TaskViewModel
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class UserDataImportWarningDialog : DialogFragment() {
|
||||
private lateinit var taskViewModel: TaskViewModel
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
taskViewModel = ViewModelProvider(requireActivity())[TaskViewModel::class.java]
|
||||
|
||||
val dialog = MaterialAlertDialogBuilder(requireContext())
|
||||
.setMessage(R.string.user_data_import_warning)
|
||||
.setNegativeButton(R.string.no) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
|
||||
.setPositiveButton(R.string.yes) { dialog: DialogInterface, _: Int ->
|
||||
dialog.dismiss()
|
||||
|
||||
val taskArguments = Bundle()
|
||||
taskArguments.putInt(TaskDialog.KEY_TITLE, R.string.import_in_progress)
|
||||
taskArguments.putInt(TaskDialog.KEY_MESSAGE, R.string.do_not_close_app)
|
||||
taskArguments.putBoolean(TaskDialog.KEY_CANCELLABLE, false)
|
||||
|
||||
taskViewModel.task = {
|
||||
taskViewModel.setResult(
|
||||
(requireActivity() as UserDataActivity).importUserData(
|
||||
requireArguments().getString(KEY_URI_RESULT)!!.toUri()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
taskViewModel.onResultDismiss = {
|
||||
if (taskViewModel.mustRestartApp) {
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
||||
|
||||
val taskDialog = TaskDialog()
|
||||
taskDialog.arguments = taskArguments
|
||||
taskDialog.show(requireActivity().supportFragmentManager, TaskDialog.TAG)
|
||||
}
|
||||
return dialog.create()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "UserDataImportWarningDialog"
|
||||
const val KEY_URI_RESULT = "uri"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.dolphinemu.dolphinemu.model
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
class TaskViewModel : ViewModel() {
|
||||
var cancelled = false
|
||||
var mustRestartApp = false
|
||||
|
||||
private val _result = MutableLiveData<Int>()
|
||||
val result: LiveData<Int> get() = _result
|
||||
|
||||
private val _isComplete = MutableLiveData<Boolean>()
|
||||
val isComplete: LiveData<Boolean> get() = _isComplete
|
||||
|
||||
private val _isRunning = MutableLiveData<Boolean>()
|
||||
val isRunning: LiveData<Boolean> get() = _isRunning
|
||||
|
||||
lateinit var task: () -> Unit
|
||||
var onResultDismiss: (() -> Unit)? = null
|
||||
|
||||
init {
|
||||
clear()
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
_result.value = 0
|
||||
_isComplete.value = false
|
||||
cancelled = false
|
||||
mustRestartApp = false
|
||||
onResultDismiss = null
|
||||
_isRunning.value = false
|
||||
}
|
||||
|
||||
fun runTask() {
|
||||
if (isRunning.value == true) return
|
||||
_isRunning.value = true
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(Dispatchers.IO) {
|
||||
task.invoke()
|
||||
_isRunning.postValue(false)
|
||||
_isComplete.postValue(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setResult(result: Int) {
|
||||
_result.postValue(result)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue