| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,112 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.sysupdate.ui | ||
|
|
||
| import android.app.Dialog | ||
| import android.os.Bundle | ||
| import androidx.appcompat.app.AlertDialog | ||
| import androidx.appcompat.app.AppCompatActivity | ||
| 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.databinding.DialogProgressBinding | ||
| import org.dolphinemu.dolphinemu.databinding.DialogProgressTvBinding | ||
|
|
||
| class SystemUpdateProgressBarDialogFragment : DialogFragment() { | ||
| private lateinit var viewModel: SystemUpdateViewModel | ||
|
|
||
| private lateinit var binding: DialogProgressBinding | ||
| private lateinit var bindingTv: DialogProgressTvBinding | ||
|
|
||
| override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| viewModel = ViewModelProvider(requireActivity())[SystemUpdateViewModel::class.java] | ||
|
|
||
| // We need to set the message to something here, otherwise the text will not appear when we set it later. | ||
| val progressDialogBuilder = MaterialAlertDialogBuilder(requireContext()) | ||
| .setTitle(getString(R.string.updating)) | ||
| .setMessage("") | ||
| .setNegativeButton(getString(R.string.cancel), null) | ||
| .setCancelable(false) | ||
|
|
||
| // TODO: Remove dialog_progress_tv if we switch to an AppCompatActivity for leanback | ||
| if (activity is AppCompatActivity) { | ||
| binding = DialogProgressBinding.inflate(layoutInflater) | ||
| progressDialogBuilder.setView(binding.root) | ||
|
|
||
| viewModel.progressData.observe( | ||
| this | ||
| ) { progress: Int -> | ||
| binding.updateProgress.progress = progress | ||
| } | ||
|
|
||
| viewModel.totalData.observe(this) { total: Int -> | ||
| if (total == 0) { | ||
| return@observe | ||
| } | ||
| binding.updateProgress.max = total | ||
| } | ||
| } else { | ||
| bindingTv = DialogProgressTvBinding.inflate(layoutInflater) | ||
| progressDialogBuilder.setView(bindingTv.root) | ||
|
|
||
| viewModel.progressData.observe( | ||
| this | ||
| ) { progress: Int -> | ||
| bindingTv.updateProgress.progress = progress | ||
| } | ||
|
|
||
| viewModel.totalData.observe(this) { total: Int -> | ||
| if (total == 0) { | ||
| return@observe | ||
| } | ||
| bindingTv.updateProgress.max = total | ||
| } | ||
| } | ||
|
|
||
| val progressDialog = progressDialogBuilder.create() | ||
|
|
||
| viewModel.titleIdData.observe(this) { titleId: Long -> | ||
| progressDialog.setMessage(getString(R.string.updating_message, titleId)) | ||
| } | ||
|
|
||
| viewModel.resultData.observe(this) { result: Int -> | ||
| if (result == -1) { | ||
| // This is the default value, ignore | ||
| return@observe | ||
| } | ||
|
|
||
| val progressBarFragment = SystemUpdateResultFragment() | ||
| progressBarFragment.show(parentFragmentManager, SystemUpdateResultFragment.TAG) | ||
|
|
||
| dismiss() | ||
| } | ||
|
|
||
| if (savedInstanceState == null) { | ||
| viewModel.startUpdate() | ||
| } | ||
| return progressDialog | ||
| } | ||
|
|
||
| // By default, the ProgressDialog will immediately dismiss itself upon a button being pressed. | ||
| // Setting the OnClickListener again after the dialog is shown overrides this behavior. | ||
| override fun onResume() { | ||
| super.onResume() | ||
| val alertDialog = dialog as AlertDialog | ||
| val negativeButton = alertDialog.getButton(Dialog.BUTTON_NEGATIVE) | ||
| negativeButton.setOnClickListener { | ||
| alertDialog.setTitle(getString(R.string.cancelling)) | ||
| alertDialog.setMessage(getString(R.string.update_cancelling)) | ||
| viewModel.setCanceled() | ||
|
|
||
| if (activity is AppCompatActivity) | ||
| binding.updateProgress.isIndeterminate = true | ||
| else | ||
| bindingTv.updateProgress.isIndeterminate = true | ||
| } | ||
| } | ||
|
|
||
| companion object { | ||
| const val TAG = "SystemUpdateProgressBarDialogFragment" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,67 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.sysupdate.ui | ||
|
|
||
| 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.utils.WiiUtils | ||
|
|
||
| class SystemUpdateResultFragment : DialogFragment() { | ||
| private val resultKey = "result" | ||
|
|
||
| private var mResult = 0 | ||
|
|
||
| override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { | ||
| val viewModel = ViewModelProvider(requireActivity())[SystemUpdateViewModel::class.java] | ||
| if (savedInstanceState == null) { | ||
| mResult = viewModel.resultData.value!!.toInt() | ||
| viewModel.clear() | ||
| } else { | ||
| mResult = savedInstanceState.getInt(resultKey) | ||
| } | ||
| val message: String = when (mResult) { | ||
| WiiUtils.UPDATE_RESULT_SUCCESS -> getString(R.string.update_success) | ||
| WiiUtils.UPDATE_RESULT_ALREADY_UP_TO_DATE -> getString(R.string.already_up_to_date) | ||
| WiiUtils.UPDATE_RESULT_REGION_MISMATCH -> getString(R.string.region_mismatch) | ||
| WiiUtils.UPDATE_RESULT_MISSING_UPDATE_PARTITION -> getString(R.string.missing_update_partition) | ||
| WiiUtils.UPDATE_RESULT_DISC_READ_FAILED -> getString(R.string.disc_read_failed) | ||
| WiiUtils.UPDATE_RESULT_SERVER_FAILED -> getString(R.string.server_failed) | ||
| WiiUtils.UPDATE_RESULT_DOWNLOAD_FAILED -> getString(R.string.download_failed) | ||
| WiiUtils.UPDATE_RESULT_IMPORT_FAILED -> getString(R.string.import_failed) | ||
| WiiUtils.UPDATE_RESULT_CANCELLED -> getString(R.string.update_cancelled) | ||
| else -> throw IllegalStateException("Unexpected value: $mResult") | ||
| } | ||
| val title: String = when (mResult) { | ||
| WiiUtils.UPDATE_RESULT_SUCCESS, | ||
| WiiUtils.UPDATE_RESULT_ALREADY_UP_TO_DATE -> getString(R.string.update_success_title) | ||
| WiiUtils.UPDATE_RESULT_REGION_MISMATCH, | ||
| WiiUtils.UPDATE_RESULT_MISSING_UPDATE_PARTITION, | ||
| WiiUtils.UPDATE_RESULT_DISC_READ_FAILED, | ||
| WiiUtils.UPDATE_RESULT_SERVER_FAILED, | ||
| WiiUtils.UPDATE_RESULT_DOWNLOAD_FAILED, | ||
| WiiUtils.UPDATE_RESULT_IMPORT_FAILED -> getString(R.string.update_failed_title) | ||
| WiiUtils.UPDATE_RESULT_CANCELLED -> getString(R.string.update_cancelled_title) | ||
| else -> throw IllegalStateException("Unexpected value: $mResult") | ||
| } | ||
|
|
||
| return MaterialAlertDialogBuilder(requireContext()) | ||
| .setTitle(title) | ||
| .setMessage(message) | ||
| .setPositiveButton(R.string.ok) { _: DialogInterface?, _: Int -> dismiss() } | ||
| .create() | ||
| } | ||
|
|
||
| override fun onSaveInstanceState(outState: Bundle) { | ||
| super.onSaveInstanceState(outState) | ||
| outState.putInt(resultKey, mResult) | ||
| } | ||
|
|
||
| companion object { | ||
| const val TAG = "SystemUpdateResultFragment" | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.features.sysupdate.ui | ||
|
|
||
| import androidx.lifecycle.MutableLiveData | ||
| import androidx.lifecycle.ViewModel | ||
| import androidx.lifecycle.viewModelScope | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.launch | ||
| import kotlinx.coroutines.withContext | ||
| import org.dolphinemu.dolphinemu.utils.WiiUpdateCallback | ||
| import org.dolphinemu.dolphinemu.utils.WiiUtils | ||
|
|
||
| class SystemUpdateViewModel : ViewModel() { | ||
| val progressData = MutableLiveData<Int>() | ||
| val totalData = MutableLiveData<Int>() | ||
| val titleIdData = MutableLiveData<Long>() | ||
| val resultData = MutableLiveData<Int>() | ||
|
|
||
| private var isRunning = false | ||
| private var canceled = false | ||
| var region = -1 | ||
| var discPath: String = "" | ||
|
|
||
| init { | ||
| clear() | ||
| } | ||
|
|
||
| fun setCanceled() { | ||
| canceled = true | ||
| } | ||
|
|
||
| fun startUpdate() { | ||
| if (isRunning) return | ||
| isRunning = true | ||
|
|
||
| viewModelScope.launch { | ||
| withContext(Dispatchers.IO) { | ||
| if (discPath.isNotEmpty()) { | ||
| startDiscUpdate(discPath) | ||
| } else { | ||
| val region: String = when (region) { | ||
| 0 -> "EUR" | ||
| 1 -> "JPN" | ||
| 2 -> "KOR" | ||
| 3 -> "USA" | ||
| else -> "" | ||
| } | ||
| startOnlineUpdate(region) | ||
| } | ||
| isRunning = false | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private fun startOnlineUpdate(region: String) { | ||
| canceled = false | ||
| val result = WiiUtils.doOnlineUpdate(region, constructCallback()) | ||
| resultData.postValue(result) | ||
| } | ||
|
|
||
| private fun startDiscUpdate(path: String) { | ||
| canceled = false | ||
| val result = WiiUtils.doDiscUpdate(path, constructCallback()) | ||
| resultData.postValue(result) | ||
| } | ||
|
|
||
| fun clear() { | ||
| progressData.value = 0 | ||
| totalData.value = 0 | ||
| titleIdData.value = 0L | ||
| resultData.value = -1 | ||
| } | ||
|
|
||
| private fun constructCallback(): WiiUpdateCallback { | ||
| return WiiUpdateCallback { processed: Int, total: Int, titleId: Long -> | ||
| progressData.postValue(processed) | ||
| totalData.postValue(total) | ||
| titleIdData.postValue(titleId) | ||
| !canceled | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| // SPDX-License-Identifier: GPL-2.0-or-later | ||
|
|
||
| package org.dolphinemu.dolphinemu.utils | ||
|
|
||
| object WiiUtils { | ||
| const val RESULT_SUCCESS = 0 | ||
| const val RESULT_ERROR = 1 | ||
| const val RESULT_CANCELLED = 2 | ||
| const val RESULT_CORRUPTED_SOURCE = 3 | ||
| const val RESULT_TITLE_MISSING = 4 | ||
| const val UPDATE_RESULT_SUCCESS = 0 | ||
| const val UPDATE_RESULT_ALREADY_UP_TO_DATE = 1 | ||
| const val UPDATE_RESULT_REGION_MISMATCH = 2 | ||
| const val UPDATE_RESULT_MISSING_UPDATE_PARTITION = 3 | ||
| const val UPDATE_RESULT_DISC_READ_FAILED = 4 | ||
| const val UPDATE_RESULT_SERVER_FAILED = 5 | ||
| const val UPDATE_RESULT_DOWNLOAD_FAILED = 6 | ||
| const val UPDATE_RESULT_IMPORT_FAILED = 7 | ||
| const val UPDATE_RESULT_CANCELLED = 8 | ||
|
|
||
| @JvmStatic | ||
| external fun installWAD(file: String): Boolean | ||
|
|
||
| @JvmStatic | ||
| external fun importWiiSave(file: String, canOverwrite: BooleanSupplier): Int | ||
|
|
||
| @JvmStatic | ||
| external fun importNANDBin(file: String) | ||
| external fun doOnlineUpdate(region: String, callback: WiiUpdateCallback): Int | ||
| external fun doDiscUpdate(path: String, callback: WiiUpdateCallback): Int | ||
|
|
||
| @JvmStatic | ||
| external fun isSystemMenuInstalled(): Boolean | ||
|
|
||
| @JvmStatic | ||
| external fun isSystemMenuvWii(): Boolean | ||
|
|
||
| @JvmStatic | ||
| external fun getSystemMenuVersion(): String | ||
|
|
||
| @JvmStatic | ||
| external fun syncSdFolderToSdImage(): Boolean | ||
|
|
||
| @JvmStatic | ||
| external fun syncSdImageToSdFolder(): Boolean | ||
| } |