Move date restore view state into ViewModel to survive configuration changes
This commit is contained in:
parent
5632f11878
commit
1924db7779
4 changed files with 88 additions and 49 deletions
|
@ -55,6 +55,9 @@ class InstallProgressFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun onInstallResult(installResult: InstallResult) {
|
||||
// skip this screen, if there are no apps to install
|
||||
if (installResult.isEmpty()) viewModel.onNextClicked()
|
||||
|
||||
val result = installResult.filterValues { it.status != QUEUED }
|
||||
val position = layoutManager.findFirstVisibleItemPosition()
|
||||
adapter.update(result.values)
|
||||
|
|
|
@ -9,20 +9,20 @@ import android.view.ViewGroup
|
|||
import android.widget.ImageView
|
||||
import android.widget.ProgressBar
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView.Adapter
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.getAppName
|
||||
import com.stevesoltys.seedvault.restore.AppRestoreStatus.IN_PROGRESS
|
||||
import com.stevesoltys.seedvault.restore.AppRestoreStatus.SUCCEEDED
|
||||
import com.stevesoltys.seedvault.restore.RestoreProgressAdapter.PackageViewHolder
|
||||
import java.util.*
|
||||
|
||||
internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
||||
|
||||
private val items = LinkedList<AppRestoreResult>().apply {
|
||||
add(AppRestoreResult(MAGIC_PACKAGE_MANAGER, true))
|
||||
}
|
||||
private var isComplete = false
|
||||
private val items = LinkedList<AppRestoreResult>()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PackageViewHolder {
|
||||
val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false)
|
||||
|
@ -32,28 +32,30 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
|||
override fun getItemCount() = items.size
|
||||
|
||||
override fun onBindViewHolder(holder: PackageViewHolder, position: Int) {
|
||||
holder.bind(items[position], position == 0)
|
||||
holder.bind(items[position])
|
||||
}
|
||||
|
||||
fun getLatest(): AppRestoreResult {
|
||||
return items[0]
|
||||
fun update(newItems: LinkedList<AppRestoreResult>) {
|
||||
val diffResult = DiffUtil.calculateDiff(Diff(items, newItems))
|
||||
items.clear()
|
||||
items.addAll(newItems)
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
fun setLatestFailed() {
|
||||
items[0] = AppRestoreResult(items[0].packageName, false)
|
||||
notifyItemChanged(0, items[0])
|
||||
}
|
||||
private class Diff(
|
||||
private val oldItems: LinkedList<AppRestoreResult>,
|
||||
private val newItems: LinkedList<AppRestoreResult>) : DiffUtil.Callback() {
|
||||
|
||||
fun add(item: AppRestoreResult) {
|
||||
items.addFirst(item)
|
||||
notifyItemInserted(0)
|
||||
notifyItemRangeChanged(1, items.size - 1)
|
||||
}
|
||||
override fun getOldListSize() = oldItems.size
|
||||
override fun getNewListSize() = newItems.size
|
||||
|
||||
fun setComplete(): List<String> {
|
||||
isComplete = true
|
||||
notifyItemChanged(0)
|
||||
return items.map { it.packageName }
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return oldItems[oldItemPosition].packageName == newItems[newItemPosition].packageName
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return oldItems[oldItemPosition] == newItems[newItemPosition]
|
||||
}
|
||||
}
|
||||
|
||||
inner class PackageViewHolder(v: View) : ViewHolder(v) {
|
||||
|
@ -65,7 +67,7 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
|||
private val appStatus: ImageView = v.findViewById(R.id.appStatus)
|
||||
private val progressBar: ProgressBar = v.findViewById(R.id.progressBar)
|
||||
|
||||
fun bind(item: AppRestoreResult, isLatest: Boolean) {
|
||||
fun bind(item: AppRestoreResult) {
|
||||
if (item.packageName == MAGIC_PACKAGE_MANAGER) {
|
||||
appIcon.setImageResource(R.drawable.ic_launcher_default)
|
||||
appName.text = context.getString(R.string.restore_magic_package)
|
||||
|
@ -77,11 +79,14 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
|||
}
|
||||
appName.text = getAppName(pm, item.packageName)
|
||||
}
|
||||
if (isLatest && !isComplete) {
|
||||
if (item.status == IN_PROGRESS) {
|
||||
appStatus.visibility = INVISIBLE
|
||||
progressBar.visibility = VISIBLE
|
||||
} else {
|
||||
appStatus.setImageResource(if (item.success) R.drawable.ic_check_green else R.drawable.ic_cancel_red)
|
||||
appStatus.setImageResource(
|
||||
if (item.status == SUCCEEDED) R.drawable.ic_check_green
|
||||
else R.drawable.ic_cancel_red
|
||||
)
|
||||
appStatus.visibility = VISIBLE
|
||||
progressBar.visibility = INVISIBLE
|
||||
}
|
||||
|
@ -91,4 +96,6 @@ internal class RestoreProgressAdapter : Adapter<PackageViewHolder>() {
|
|||
|
||||
}
|
||||
|
||||
data class AppRestoreResult(val packageName: String, val success: Boolean)
|
||||
internal enum class AppRestoreStatus { IN_PROGRESS, SUCCEEDED, FAILED }
|
||||
|
||||
internal data class AppRestoreResult(val packageName: String, val status: AppRestoreStatus)
|
||||
|
|
|
@ -51,28 +51,11 @@ class RestoreProgressFragment : Fragment() {
|
|||
backupNameView.text = restorableBackup.name
|
||||
})
|
||||
|
||||
viewModel.restoreProgress.observe(this, Observer { currentPackage ->
|
||||
stayScrolledAtTop {
|
||||
val latest = adapter.getLatest()
|
||||
if (viewModel.isFailedPackage(latest.packageName)) {
|
||||
adapter.setLatestFailed()
|
||||
}
|
||||
adapter.add(AppRestoreResult(currentPackage, true))
|
||||
}
|
||||
viewModel.restoreProgress.observe(this, Observer { list ->
|
||||
stayScrolledAtTop { adapter.update(list) }
|
||||
})
|
||||
|
||||
viewModel.restoreBackupResult.observe(this, Observer { finished ->
|
||||
val seenPackages = adapter.setComplete()
|
||||
stayScrolledAtTop {
|
||||
// add missing packages as failed
|
||||
val restorableBackup = viewModel.chosenRestorableBackup.value!!
|
||||
val expectedPackages = restorableBackup.packageMetadataMap.keys
|
||||
expectedPackages.removeAll(seenPackages)
|
||||
for (packageName: String in expectedPackages) {
|
||||
adapter.add(AppRestoreResult(packageName, false))
|
||||
}
|
||||
}
|
||||
|
||||
button.isEnabled = true
|
||||
if (finished.hasError()) {
|
||||
backupNameView.text = finished.errorMsg
|
||||
|
|
|
@ -15,8 +15,10 @@ import androidx.lifecycle.Transformations.switchMap
|
|||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.stevesoltys.seedvault.BackupMonitor
|
||||
import com.stevesoltys.seedvault.MAGIC_PACKAGE_MANAGER
|
||||
import com.stevesoltys.seedvault.R
|
||||
import com.stevesoltys.seedvault.crypto.KeyManager
|
||||
import com.stevesoltys.seedvault.restore.AppRestoreStatus.*
|
||||
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_APPS
|
||||
import com.stevesoltys.seedvault.restore.DisplayFragment.RESTORE_BACKUP
|
||||
import com.stevesoltys.seedvault.settings.SettingsManager
|
||||
|
@ -35,6 +37,7 @@ import kotlinx.coroutines.flow.flowOn
|
|||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.resume
|
||||
import kotlin.coroutines.suspendCoroutine
|
||||
|
@ -73,8 +76,12 @@ internal class RestoreViewModel(
|
|||
private val mNextButtonEnabled = MutableLiveData<Boolean>().apply { value = false }
|
||||
internal val nextButtonEnabled: LiveData<Boolean> = mNextButtonEnabled
|
||||
|
||||
private val mRestoreProgress = MutableLiveData<String>()
|
||||
internal val restoreProgress: LiveData<String> get() = mRestoreProgress
|
||||
private val mRestoreProgress = MutableLiveData<LinkedList<AppRestoreResult>>().apply {
|
||||
value = LinkedList<AppRestoreResult>().apply {
|
||||
add(AppRestoreResult(MAGIC_PACKAGE_MANAGER, IN_PROGRESS))
|
||||
}
|
||||
}
|
||||
internal val restoreProgress: LiveData<LinkedList<AppRestoreResult>> get() = mRestoreProgress
|
||||
|
||||
private val mRestoreBackupResult = MutableLiveData<RestoreBackupResult>()
|
||||
internal val restoreBackupResult: LiveData<RestoreBackupResult> get() = mRestoreBackupResult
|
||||
|
@ -164,7 +171,46 @@ internal class RestoreViewModel(
|
|||
}
|
||||
}
|
||||
|
||||
fun isFailedPackage(packageName: String) = restoreCoordinator.isFailedPackage(packageName)
|
||||
@WorkerThread
|
||||
// this should be called one package at a time and never concurrently for different packages
|
||||
private fun onRestoreStarted(packageName: String) {
|
||||
// list is never null and always has at least one package
|
||||
val list = mRestoreProgress.value!!
|
||||
|
||||
// check previous package first and change status
|
||||
updateLatestPackage(list)
|
||||
// add current package
|
||||
list.addFirst(AppRestoreResult(packageName, IN_PROGRESS))
|
||||
mRestoreProgress.postValue(list)
|
||||
}
|
||||
|
||||
private fun updateLatestPackage(list: LinkedList<AppRestoreResult>) {
|
||||
val latestResult = list[0]
|
||||
if (restoreCoordinator.isFailedPackage(latestResult.packageName)) {
|
||||
list[0] = latestResult.copy(status = FAILED)
|
||||
} else {
|
||||
list[0] = latestResult.copy(status = SUCCEEDED)
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private fun onRestoreComplete(result: RestoreBackupResult) {
|
||||
// update status of latest package
|
||||
val list = mRestoreProgress.value!!
|
||||
updateLatestPackage(list)
|
||||
|
||||
// add missing packages as failed
|
||||
val seenPackages = list.map { it.packageName }
|
||||
val restorableBackup = chosenRestorableBackup.value!!
|
||||
val expectedPackages = restorableBackup.packageMetadataMap.keys
|
||||
expectedPackages.removeAll(seenPackages)
|
||||
for (packageName: String in expectedPackages) {
|
||||
list.add(AppRestoreResult(packageName, FAILED))
|
||||
}
|
||||
mRestoreProgress.postValue(list)
|
||||
|
||||
mRestoreBackupResult.postValue(result)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
@ -189,7 +235,7 @@ internal class RestoreViewModel(
|
|||
* the current device. If no applicable datasets exist, restoreSets will be null.
|
||||
*/
|
||||
override fun restoreSetsAvailable(restoreSets: Array<out RestoreSet>?) {
|
||||
check (continuation != null) { "Getting restore sets without continuation" }
|
||||
check(continuation != null) { "Getting restore sets without continuation" }
|
||||
|
||||
val result = if (restoreSets == null || restoreSets.isEmpty()) {
|
||||
RestoreSetResult(app.getString(R.string.restore_set_empty_result))
|
||||
|
@ -241,7 +287,7 @@ internal class RestoreViewModel(
|
|||
*/
|
||||
override fun onUpdate(nowBeingRestored: Int, currentPackage: String) {
|
||||
// nowBeingRestored reporting is buggy, so don't use it
|
||||
mRestoreProgress.postValue(currentPackage)
|
||||
onRestoreStarted(currentPackage)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -255,7 +301,7 @@ internal class RestoreViewModel(
|
|||
if (result == 0) null
|
||||
else app.getString(R.string.restore_finished_error)
|
||||
)
|
||||
mRestoreBackupResult.postValue(restoreResult)
|
||||
onRestoreComplete(restoreResult)
|
||||
closeSession()
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue