diff --git a/app/src/main/java/app/simple/inure/adapters/batch/AdapterBatchUninstaller.kt b/app/src/main/java/app/simple/inure/adapters/batch/AdapterBatchUninstaller.kt index c21b1f06e..646a5eca9 100644 --- a/app/src/main/java/app/simple/inure/adapters/batch/AdapterBatchUninstaller.kt +++ b/app/src/main/java/app/simple/inure/adapters/batch/AdapterBatchUninstaller.kt @@ -30,10 +30,10 @@ class AdapterBatchUninstaller(private val results: ArrayList holder.itemView.context.getString(R.string.pending) + true -> holder.itemView.context.getString(R.string.uninstalled) + false -> holder.itemView.context.getString(R.string.failed) } } @@ -41,6 +41,16 @@ class AdapterBatchUninstaller(private val results: ArrayList) { + // Find which items changed and notify only those + for (i in results.indices) { + if (i < newResults.size && results[i].isSuccessful != newResults[i].isSuccessful) { + results[i] = newResults[i] + notifyItemChanged(i) + } + } + } + inner class Holder(itemView: View) : VerticalListViewHolder(itemView) { val icon: ImageView = itemView.findViewById(R.id.app_icon) val name: TypeFaceTextView = itemView.findViewById(R.id.app_name) diff --git a/app/src/main/java/app/simple/inure/dialogs/batch/BatchUninstaller.kt b/app/src/main/java/app/simple/inure/dialogs/batch/BatchUninstaller.kt index cf1ef62c5..1be2cd119 100644 --- a/app/src/main/java/app/simple/inure/dialogs/batch/BatchUninstaller.kt +++ b/app/src/main/java/app/simple/inure/dialogs/batch/BatchUninstaller.kt @@ -6,22 +6,21 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import app.simple.inure.R import app.simple.inure.adapters.batch.AdapterBatchUninstaller import app.simple.inure.constants.BundleConstants import app.simple.inure.decorations.overscroll.CustomVerticalRecyclerView -import app.simple.inure.decorations.views.LoaderImageView import app.simple.inure.extensions.fragments.ScopedBottomSheetFragment import app.simple.inure.factories.panels.BatchViewModelFactory import app.simple.inure.models.BatchPackageInfo import app.simple.inure.util.ParcelUtils.parcelableArrayList -import app.simple.inure.util.ViewUtils.gone import app.simple.inure.viewmodels.dialogs.BatchUninstallerViewModel import app.simple.inure.viewmodels.panels.BatchViewModel +import kotlinx.coroutines.launch class BatchUninstaller : ScopedBottomSheetFragment() { - private lateinit var loader: LoaderImageView private lateinit var recyclerView: CustomVerticalRecyclerView private var adapterBatchUninstaller: AdapterBatchUninstaller? = null @@ -32,7 +31,6 @@ class BatchUninstaller : ScopedBottomSheetFragment() { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.dialog_batch_uninstall, container, false) - loader = view.findViewById(R.id.loader) recyclerView = view.findViewById(R.id.recycler_view) appList = requireArguments().parcelableArrayList(BundleConstants.selectedBatchApps)!! @@ -47,12 +45,17 @@ class BatchUninstaller : ScopedBottomSheetFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - loader.start() - - batchUninstallerViewModel?.getData()?.observe(viewLifecycleOwner) { - loader.gone() - adapterBatchUninstaller = AdapterBatchUninstaller(it) - recyclerView.adapter = adapterBatchUninstaller + viewLifecycleOwner.lifecycleScope.launch { + batchUninstallerViewModel?.uninstallResults?.collect { results -> + if (adapterBatchUninstaller == null) { + // Initialize adapter with the first emission (all pending) + adapterBatchUninstaller = AdapterBatchUninstaller(results) + recyclerView.adapter = adapterBatchUninstaller + } else { + // Update existing adapter with new results + adapterBatchUninstaller?.updateResults(results) + } + } } } @@ -69,7 +72,7 @@ class BatchUninstaller : ScopedBottomSheetFragment() { data class BatchUninstallerResult( val packageInfo: PackageInfo, - val isSuccessful: Boolean + val isSuccessful: Boolean? // null = pending, true = success, false = failed ) } } diff --git a/app/src/main/java/app/simple/inure/viewmodels/dialogs/BatchUninstallerViewModel.kt b/app/src/main/java/app/simple/inure/viewmodels/dialogs/BatchUninstallerViewModel.kt index 03a851e8b..c4848a512 100644 --- a/app/src/main/java/app/simple/inure/viewmodels/dialogs/BatchUninstallerViewModel.kt +++ b/app/src/main/java/app/simple/inure/viewmodels/dialogs/BatchUninstallerViewModel.kt @@ -3,8 +3,6 @@ package app.simple.inure.viewmodels.dialogs import android.app.Application import android.content.pm.ApplicationInfo import android.content.pm.PackageInfo -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.viewModelScope import app.simple.inure.apk.utils.PackageUtils.safeApplicationInfo import app.simple.inure.constants.Warnings @@ -14,39 +12,38 @@ import app.simple.inure.helpers.ShizukuServiceHelper import app.simple.inure.models.BatchPackageInfo import com.topjohnwu.superuser.Shell import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch class BatchUninstallerViewModel(application: Application, val list: ArrayList) : RootShizukuViewModel(application) { - private val data: MutableLiveData> by lazy { - MutableLiveData>().also { - initializeCoreFramework() - } - } + private val _uninstallResults = MutableStateFlow>(arrayListOf()) + val uninstallResults: StateFlow> = _uninstallResults.asStateFlow() - fun getData(): LiveData> { - return data + init { + // Initialize with pending state for all apps + val initialResults = ArrayList(list.map { BatchUninstallerResult(it.packageInfo, null) }) + _uninstallResults.value = initialResults + initializeCoreFramework() } override fun onShellCreated(shell: Shell?) { viewModelScope.launch(Dispatchers.IO) { - val uninstalledApps = arrayListOf() + val results = ArrayList(_uninstallResults.value) - for (app in list) { + for ((index, app) in list.withIndex()) { runCatching { Shell.cmd(app.packageInfo.getUninstallCommand()).exec().let { - if (it.isSuccess) { - uninstalledApps.add(BatchUninstallerResult(app.packageInfo, true)) - } else { - uninstalledApps.add(BatchUninstallerResult(app.packageInfo, false)) - } + results[index] = BatchUninstallerResult(app.packageInfo, it.isSuccess) + _uninstallResults.value = ArrayList(results) } }.onFailure { - uninstalledApps.add(BatchUninstallerResult(app.packageInfo, false)) + results[index] = BatchUninstallerResult(app.packageInfo, false) + _uninstallResults.value = ArrayList(results) } } - - data.postValue(uninstalledApps) } } @@ -64,23 +61,19 @@ class BatchUninstallerViewModel(application: Application, val list: ArrayList() + val results = ArrayList(_uninstallResults.value) - for (app in list) { + for ((index, app) in list.withIndex()) { runCatching { shizukuServiceHelper.service!!.simpleExecute(app.packageInfo.getUninstallCommand()).let { - if (it.isSuccess) { - uninstalledApps.add(BatchUninstallerResult(app.packageInfo, true)) - } else { - uninstalledApps.add(BatchUninstallerResult(app.packageInfo, false)) - } + results[index] = BatchUninstallerResult(app.packageInfo, it.isSuccess) + _uninstallResults.value = ArrayList(results) } }.onFailure { - uninstalledApps.add(BatchUninstallerResult(app.packageInfo, false)) + results[index] = BatchUninstallerResult(app.packageInfo, false) + _uninstallResults.value = ArrayList(results) } } - - data.postValue(uninstalledApps) } } } diff --git a/app/src/main/res/layout/dialog_batch_uninstall.xml b/app/src/main/res/layout/dialog_batch_uninstall.xml index 53574add1..56b0cb71d 100644 --- a/app/src/main/res/layout/dialog_batch_uninstall.xml +++ b/app/src/main/res/layout/dialog_batch_uninstall.xml @@ -25,11 +25,6 @@ app:isTopFadingEdgeOnly="false" app:statusBarPaddingRequired="false" /> -