From 96a4642f4fee64f4a371e6cd891216ebfc9f1c21 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Wed, 8 Jan 2020 15:26:58 -0300 Subject: [PATCH] Show list of re-installed apps and let the user review it before restoring data --- .../restore/InstallProgressAdapter.kt | 72 +++++++++++++++++++ .../restore/InstallProgressFragment.kt | 35 +++++++-- .../seedvault/restore/RestoreViewModel.kt | 14 +++- .../seedvault/transport/restore/ApkRestore.kt | 9 ++- app/src/main/res/drawable/ic_cancel_red.xml | 9 +++ app/src/main/res/drawable/ic_check_green.xml | 9 +++ .../res/layout/fragment_install_progress.xml | 50 ++++++------- .../main/res/layout/list_item_app_status.xml | 53 ++++++++++++++ app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/strings.xml | 1 + 10 files changed, 212 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt create mode 100644 app/src/main/res/drawable/ic_cancel_red.xml create mode 100644 app/src/main/res/drawable/ic_check_green.xml create mode 100644 app/src/main/res/layout/list_item_app_status.xml diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt new file mode 100644 index 00000000..3e4b6c45 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressAdapter.kt @@ -0,0 +1,72 @@ +package com.stevesoltys.seedvault.restore + +import android.view.LayoutInflater +import android.view.View +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.ProgressBar +import android.widget.TextView +import androidx.recyclerview.widget.RecyclerView.Adapter +import androidx.recyclerview.widget.RecyclerView.ViewHolder +import androidx.recyclerview.widget.SortedList +import androidx.recyclerview.widget.SortedListAdapterCallback +import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.transport.restore.ApkRestoreResult +import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.* + +internal class InstallProgressAdapter : Adapter() { + + private val items = SortedList(ApkRestoreResult::class.java, object : SortedListAdapterCallback(this) { + override fun areItemsTheSame(item1: ApkRestoreResult, item2: ApkRestoreResult) = item1.packageName == item2.packageName + override fun areContentsTheSame(oldItem: ApkRestoreResult, newItem: ApkRestoreResult) = oldItem == newItem + override fun compare(item1: ApkRestoreResult, item2: ApkRestoreResult) = item1.compareTo(item2) + }) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AppViewHolder { + val v = LayoutInflater.from(parent.context).inflate(R.layout.list_item_app_status, parent, false) + return AppViewHolder(v) + } + + override fun getItemCount() = items.size() + + override fun onBindViewHolder(holder: AppViewHolder, position: Int) { + holder.bind(items[position]) + } + + fun update(items: Collection) { + this.items.replaceAll(items) + } +} + +internal class AppViewHolder(v: View) : ViewHolder(v) { + + private val appIcon: ImageView = v.findViewById(R.id.appIcon) + private val appName: TextView = v.findViewById(R.id.appName) + private val appStatus: ImageView = v.findViewById(R.id.appStatus) + private val progressBar: ProgressBar = v.findViewById(R.id.progressBar) + + fun bind(item: ApkRestoreResult) { + appIcon.setImageDrawable(item.icon) + appName.text = item.name + when (item.status) { + IN_PROGRESS -> { + appStatus.visibility = INVISIBLE + progressBar.visibility = VISIBLE + } + SUCCEEDED -> { + appStatus.setImageResource(R.drawable.ic_check_green) + appStatus.visibility = VISIBLE + progressBar.visibility = INVISIBLE + } + FAILED -> { + appStatus.setImageResource(R.drawable.ic_cancel_red) + appStatus.visibility = VISIBLE + progressBar.visibility = INVISIBLE + } + QUEUED -> throw AssertionError() + } + } + +} diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressFragment.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressFragment.kt index 42e6e4fa..0914b45d 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressFragment.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/InstallProgressFragment.kt @@ -6,23 +6,38 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.Observer +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager.VERTICAL import com.stevesoltys.seedvault.R +import com.stevesoltys.seedvault.transport.restore.ApkRestoreStatus.QUEUED import com.stevesoltys.seedvault.transport.restore.InstallResult import com.stevesoltys.seedvault.transport.restore.getInProgress import kotlinx.android.synthetic.main.fragment_install_progress.* import kotlinx.android.synthetic.main.fragment_restore_progress.backupNameView -import kotlinx.android.synthetic.main.fragment_restore_progress.currentPackageView import org.koin.androidx.viewmodel.ext.android.sharedViewModel class InstallProgressFragment : Fragment() { private val viewModel: RestoreViewModel by sharedViewModel() + private val layoutManager = LinearLayoutManager(context) + private val adapter = InstallProgressAdapter() + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_install_progress, container, false) } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + appList.apply { + layoutManager = this@InstallProgressFragment.layoutManager + adapter = this@InstallProgressFragment.adapter + addItemDecoration(DividerItemDecoration(context, VERTICAL)) + } + nextButton.setOnClickListener { viewModel.onNextClicked() } + } + override fun onActivityCreated(savedInstanceState: Bundle?) { super.onActivityCreated(savedInstanceState) @@ -33,16 +48,22 @@ class InstallProgressFragment : Fragment() { viewModel.installResult.observe(this, Observer { result -> onInstallResult(result) }) + + viewModel.nextButtonEnabled.observe(this, Observer { enabled -> + nextButton.isEnabled = enabled + }) } private fun onInstallResult(installResult: InstallResult) { - installResult.getInProgress()?.let { result -> - currentPackageView.text = result.name - result.icon?.let { currentPackageImageView.setImageDrawable(it) } - progressBar.progress = result.progress - progressBar.max = result.total + val result = installResult.filterValues { it.status != QUEUED } + val position = layoutManager.findFirstVisibleItemPosition() + adapter.update(result.values) + if (position == 0) layoutManager.scrollToPosition(0) + + result.getInProgress()?.let { + progressBar.progress = it.progress + progressBar.max = it.total } - // TODO add finished apps to list of (failed?) apps and continue on button press } } diff --git a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt index f60e5a43..8b6e3725 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/restore/RestoreViewModel.kt @@ -70,6 +70,9 @@ internal class RestoreViewModel( getInstallResult(backup) } + private val mNextButtonEnabled = MutableLiveData().apply { value = false } + internal val nextButtonEnabled: LiveData = mNextButtonEnabled + private val mRestoreProgress = MutableLiveData() internal val restoreProgress: LiveData get() = mRestoreProgress @@ -125,13 +128,20 @@ internal class RestoreViewModel( Log.d(TAG, "Exception in InstallResult Flow", e) }.onCompletion { e -> Log.d(TAG, "Completed InstallResult Flow", e) - mDisplayFragment.postEvent(RESTORE_BACKUP) - startRestore(restorableBackup.token) + mNextButtonEnabled.postValue(true) } .flowOn(ioDispatcher) .asLiveData() } + internal fun onNextClicked() { + mDisplayFragment.postEvent(RESTORE_BACKUP) + val token = mChosenRestorableBackup.value?.token ?: throw AssertionError() + viewModelScope.launch(ioDispatcher) { + startRestore(token) + } + } + @WorkerThread private suspend fun startRestore(token: Long) { Log.d(TAG, "Starting new restore session to restore backup $token") diff --git a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt index 01a52f35..b45e1ba2 100644 --- a/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt +++ b/app/src/main/java/com/stevesoltys/seedvault/transport/restore/ApkRestore.kt @@ -40,7 +40,7 @@ internal class ApkRestore( val installResult = MutableInstallResult(total) packages.forEach { (packageName, _) -> progress++ - installResult[packageName] = ApkRestoreResult(progress, total, QUEUED) + installResult[packageName] = ApkRestoreResult(packageName, progress, total, QUEUED) } emit(installResult) @@ -155,12 +155,17 @@ internal class MutableInstallResult(initialCapacity: Int) : ConcurrentHashMap { + override fun compareTo(other: ApkRestoreResult): Int { + return other.progress.compareTo(progress) + } +} internal enum class ApkRestoreStatus { QUEUED, IN_PROGRESS, SUCCEEDED, FAILED diff --git a/app/src/main/res/drawable/ic_cancel_red.xml b/app/src/main/res/drawable/ic_cancel_red.xml new file mode 100644 index 00000000..2ced3613 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel_red.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_check_green.xml b/app/src/main/res/drawable/ic_check_green.xml new file mode 100644 index 00000000..4e0d2011 --- /dev/null +++ b/app/src/main/res/drawable/ic_check_green.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_install_progress.xml b/app/src/main/res/layout/fragment_install_progress.xml index 43617d49..5bba535a 100644 --- a/app/src/main/res/layout/fragment_install_progress.xml +++ b/app/src/main/res/layout/fragment_install_progress.xml @@ -54,40 +54,30 @@ app:layout_constraintTop_toBottomOf="@+id/titleView" tools:text="Pixel 2 XL" /> - - - + +