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.ActivityNotFoundException
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.DialogInterface
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
@ -14,10 +13,14 @@ import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import androidx.core.view.WindowInsetsCompat
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import com.google.android.material.color.MaterialColors
|
import com.google.android.material.color.MaterialColors
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import org.dolphinemu.dolphinemu.R
|
import org.dolphinemu.dolphinemu.R
|
||||||
import org.dolphinemu.dolphinemu.databinding.ActivityUserDataBinding
|
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.*
|
||||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper.enableScrollTint
|
import org.dolphinemu.dolphinemu.utils.ThemeHelper.enableScrollTint
|
||||||
import org.dolphinemu.dolphinemu.utils.ThemeHelper.setNavigationBarColor
|
import org.dolphinemu.dolphinemu.utils.ThemeHelper.setNavigationBarColor
|
||||||
|
@ -28,10 +31,9 @@ import java.io.IOException
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
import java.util.zip.ZipInputStream
|
import java.util.zip.ZipInputStream
|
||||||
import java.util.zip.ZipOutputStream
|
import java.util.zip.ZipOutputStream
|
||||||
import kotlin.system.exitProcess
|
|
||||||
|
|
||||||
class UserDataActivity : AppCompatActivity() {
|
class UserDataActivity : AppCompatActivity() {
|
||||||
private var sMustRestartApp = false
|
private lateinit var taskViewModel: TaskViewModel
|
||||||
|
|
||||||
private lateinit var mBinding: ActivityUserDataBinding
|
private lateinit var mBinding: ActivityUserDataBinding
|
||||||
|
|
||||||
|
@ -79,31 +81,32 @@ class UserDataActivity : AppCompatActivity() {
|
||||||
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||||
super.onActivityResult(requestCode, resultCode, data)
|
super.onActivityResult(requestCode, resultCode, data)
|
||||||
|
|
||||||
|
taskViewModel = ViewModelProvider(this)[TaskViewModel::class.java]
|
||||||
if (requestCode == REQUEST_CODE_IMPORT && resultCode == RESULT_OK) {
|
if (requestCode == REQUEST_CODE_IMPORT && resultCode == RESULT_OK) {
|
||||||
|
val arguments = Bundle()
|
||||||
|
arguments.putString(
|
||||||
|
UserDataImportWarningDialog.KEY_URI_RESULT,
|
||||||
|
data!!.data!!.toString()
|
||||||
|
)
|
||||||
|
|
||||||
MaterialAlertDialogBuilder(this)
|
val dialog = UserDataImportWarningDialog()
|
||||||
.setMessage(R.string.user_data_import_warning)
|
dialog.arguments = arguments
|
||||||
.setNegativeButton(R.string.no) { dialog: DialogInterface, _: Int -> dialog.dismiss() }
|
dialog.show(supportFragmentManager, UserDataImportWarningDialog.TAG)
|
||||||
.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()
|
|
||||||
} else if (requestCode == REQUEST_CODE_EXPORT && resultCode == RESULT_OK) {
|
} else if (requestCode == REQUEST_CODE_EXPORT && resultCode == RESULT_OK) {
|
||||||
ThreadUtil.runOnThreadAndShowResult(
|
taskViewModel.clear()
|
||||||
this,
|
taskViewModel.task = {
|
||||||
R.string.export_in_progress,
|
val resultResource = exportUserData(data!!.data!!)
|
||||||
0
|
taskViewModel.setResult(resultResource)
|
||||||
) { resources.getString(exportUserData(data!!.data!!)) }
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (e2: ActivityNotFoundException) {
|
||||||
// Activity not found. Perhaps it was removed by the OEM, or by some new Android version
|
// 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.
|
// that didn't exist at the time of writing. Not much we can do other than tell the user.
|
||||||
MaterialAlertDialogBuilder(this)
|
val arguments = Bundle()
|
||||||
.setMessage(R.string.user_data_open_system_file_manager_failed)
|
arguments.putInt(
|
||||||
.setPositiveButton(R.string.ok, null)
|
NotificationDialog.KEY_MESSAGE,
|
||||||
.show()
|
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)
|
startActivityForResult(intent, REQUEST_CODE_IMPORT)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun importUserData(source: Uri): Int {
|
fun importUserData(source: Uri): Int {
|
||||||
try {
|
try {
|
||||||
if (!isDolphinUserDataBackup(source))
|
if (!isDolphinUserDataBackup(source))
|
||||||
return R.string.user_data_import_invalid_file
|
return R.string.user_data_import_invalid_file
|
||||||
|
|
||||||
|
taskViewModel.mustRestartApp = true
|
||||||
|
|
||||||
contentResolver.openInputStream(source).use { `is` ->
|
contentResolver.openInputStream(source).use { `is` ->
|
||||||
ZipInputStream(`is`).use { zis ->
|
ZipInputStream(`is`).use { zis ->
|
||||||
val userDirectory = File(DirectoryInitialization.getUserDirectory())
|
val userDirectory = File(DirectoryInitialization.getUserDirectory())
|
||||||
val userDirectoryCanonicalized = userDirectory.canonicalPath + '/'
|
val userDirectoryCanonicalized = userDirectory.canonicalPath + '/'
|
||||||
|
|
||||||
sMustRestartApp = true
|
|
||||||
deleteChildrenRecursively(userDirectory)
|
deleteChildrenRecursively(userDirectory)
|
||||||
|
|
||||||
DirectoryInitialization.getGameListCache(this).delete()
|
DirectoryInitialization.getGameListCache(this).delete()
|
||||||
|
@ -262,8 +271,12 @@ class UserDataActivity : AppCompatActivity() {
|
||||||
private fun exportUserData(zos: ZipOutputStream, input: File, pathRelativeToRoot: File?) {
|
private fun exportUserData(zos: ZipOutputStream, input: File, pathRelativeToRoot: File?) {
|
||||||
if (input.isDirectory) {
|
if (input.isDirectory) {
|
||||||
val children = input.listFiles() ?: throw IOException("Could not find directory $input")
|
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) {
|
if (children.isEmpty() && pathRelativeToRoot != null) {
|
||||||
zos.putNextEntry(ZipEntry(pathRelativeToRoot.path + '/'))
|
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