UpdatesFragment: Refactor to work with new downloads logic

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2023-12-13 22:17:49 +05:30
parent bd55b157e6
commit 63c55b8858
5 changed files with 193 additions and 397 deletions

View File

@@ -26,6 +26,7 @@ import com.aurora.extensions.getString
import com.aurora.extensions.runOnUiThread import com.aurora.extensions.runOnUiThread
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.State import com.aurora.store.State
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.databinding.ViewUpdateButtonBinding import com.aurora.store.databinding.ViewUpdateButtonBinding
class UpdateButton : RelativeLayout { class UpdateButton : RelativeLayout {
@@ -72,12 +73,12 @@ class UpdateButton : RelativeLayout {
B.btnPositive.text = getString(text) B.btnPositive.text = getString(text)
} }
fun updateState(state: State) { fun updateState(downloadStatus: DownloadStatus) {
val displayChild = when (state) { val displayChild = when (downloadStatus) {
State.IDLE, State.CANCELED -> 0 DownloadStatus.QUEUED,
State.QUEUED -> 1 DownloadStatus.DOWNLOADING -> 2
State.PROGRESS -> 2 DownloadStatus.COMPLETED -> 3
State.COMPLETE -> 3 else -> 0
} }
if (B.viewFlipper.displayedChild != displayChild) { if (B.viewFlipper.displayedChild != displayChild) {

View File

@@ -34,13 +34,17 @@ import com.airbnb.epoxy.ModelView
import com.airbnb.epoxy.OnViewRecycled import com.airbnb.epoxy.OnViewRecycled
import com.aurora.extensions.invisible import com.aurora.extensions.invisible
import com.aurora.extensions.px import com.aurora.extensions.px
import com.aurora.extensions.show import com.aurora.gplayapi.data.models.App
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.State import com.aurora.store.State
import com.aurora.store.data.model.UpdateFile import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.download.Download
import com.aurora.store.databinding.ViewAppUpdateBinding import com.aurora.store.databinding.ViewAppUpdateBinding
import com.aurora.store.util.CommonUtil import com.aurora.store.util.CommonUtil
import com.aurora.store.util.PathUtil
import com.aurora.store.view.epoxy.views.BaseView import com.aurora.store.view.epoxy.views.BaseView
import java.io.File
import kotlin.io.path.pathString
@ModelView( @ModelView(
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
@@ -72,76 +76,77 @@ class AppUpdateView : RelativeLayout {
} }
@ModelProp @ModelProp
fun updateFile(updateFile: UpdateFile?) { fun app(app: App) {
if (updateFile != null) { /*Inflate App details*/
with(app) {
/*Inflate App details*/ B.txtLine1.text = displayName
with(updateFile.app) { B.imgIcon.load(iconArtwork.url) {
B.txtLine1.text = displayName placeholder(R.drawable.bg_placeholder)
B.imgIcon.load(iconArtwork.url) { transformations(RoundedCornersTransformation(8.px.toFloat()))
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(8.px.toFloat()))
}
B.txtLine2.text = developerName
B.txtLine3.text = ("${CommonUtil.addSiPrefix(size)}$updatedOn")
B.txtLine4.text = ("$versionName (${versionCode})")
B.txtChangelog.text = if (changes.isNotEmpty())
HtmlCompat.fromHtml(
changes,
HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
)
else
context.getString(R.string.details_changelog_unavailable)
B.headerIndicator.setOnClickListener {
if (B.txtChangelog.isVisible) {
B.headerIndicator.icon = ContextCompat.getDrawable(context, R.drawable.ic_arrow_down)
B.txtChangelog.visibility = View.GONE
} else {
B.headerIndicator.icon = ContextCompat.getDrawable(context, R.drawable.ic_arrow_up)
B.txtChangelog.visibility = View.VISIBLE
}
}
} }
/*Inflate Download details*/ B.txtLine2.text = developerName
updateFile.group?.let { B.txtLine3.text = ("${CommonUtil.addSiPrefix(size)}$updatedOn")
when (updateFile.state) { B.txtLine4.text = ("$versionName (${versionCode})")
State.QUEUED -> { B.txtChangelog.text = if (changes.isNotEmpty())
B.progressDownload.progress = 0 HtmlCompat.fromHtml(
B.progressDownload.show() changes,
B.btnAction.updateState(State.QUEUED) HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
} )
State.IDLE, State.CANCELED -> { else
B.progressDownload.progress = 0 context.getString(R.string.details_changelog_unavailable)
B.progressDownload.invisible()
B.btnAction.updateState(State.IDLE) B.headerIndicator.setOnClickListener {
} if (B.txtChangelog.isVisible) {
State.PROGRESS -> { B.headerIndicator.icon = ContextCompat.getDrawable(context, R.drawable.ic_arrow_down)
val progress = it.groupDownloadProgress B.txtChangelog.visibility = View.GONE
if (progress > 0) { } else {
if (progress == 100) { B.headerIndicator.icon = ContextCompat.getDrawable(context, R.drawable.ic_arrow_up)
B.progressDownload.invisible() B.txtChangelog.visibility = View.VISIBLE
} else {
B.progressDownload.progress = progress
B.progressDownload.show()
}
}
}
State.COMPLETE -> {
B.progressDownload.invisible()
B.btnAction.updateState(State.COMPLETE)
}
} }
} }
} }
} }
@ModelProp @ModelProp
fun state(state: State?) { fun download(download: Download?) {
state?.let { if (download != null) {
B.btnAction.updateState(it) /*Inflate Download details*/
B.btnAction.updateState(download.status)
when (download.status) {
DownloadStatus.QUEUED -> {
B.progressDownload.progress = 0
B.progressDownload.show()
}
DownloadStatus.DOWNLOADING -> {
if (download.progress > 0) {
if (download.progress == 100) {
B.progressDownload.invisible()
} else {
B.progressDownload.progress = download.progress
B.progressDownload.show()
}
}
}
DownloadStatus.COMPLETED -> {
B.progressDownload.invisible()
// It is possible that while the app was downloaded in past, files have been removed
// Check once again to reflect appropriate status
val files = File(
PathUtil.getAppDownloadDir(
context,
download.packageName,
download.versionCode
).pathString
).listFiles()
if (files.isNullOrEmpty()) B.btnAction.updateState(DownloadStatus.UNAVAILABLE)
}
else -> {
B.progressDownload.progress = 0
B.progressDownload.invisible()
}
}
} }
} }

View File

@@ -20,26 +20,21 @@
package com.aurora.store.view.ui.updates package com.aurora.store.view.ui.updates
import android.Manifest import android.Manifest
import android.content.ComponentName
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.os.IBinder
import android.provider.Settings import android.provider.Settings
import android.view.View import android.view.View
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.aurora.extensions.isRAndAbove import com.aurora.extensions.isRAndAbove
import com.aurora.extensions.toast import com.aurora.extensions.toast
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.State import com.aurora.store.data.room.download.Download
import com.aurora.store.data.downloader.getGroupId
import com.aurora.store.data.model.UpdateFile
import com.aurora.store.data.service.UpdateService
import com.aurora.store.databinding.FragmentUpdatesBinding import com.aurora.store.databinding.FragmentUpdatesBinding
import com.aurora.store.util.PathUtil import com.aurora.store.util.PathUtil
import com.aurora.store.util.isExternalStorageEnable import com.aurora.store.util.isExternalStorageEnable
@@ -49,10 +44,12 @@ import com.aurora.store.view.epoxy.views.app.NoAppViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.view.ui.commons.BaseFragment import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.all.UpdatesViewModel import com.aurora.store.viewmodel.all.UpdatesViewModel
import com.tonyodev.fetch2.AbstractFetchGroupListener
import com.tonyodev.fetch2.Download
import com.tonyodev.fetch2.FetchGroup
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import java.io.File
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import kotlin.io.path.pathString
@AndroidEntryPoint @AndroidEntryPoint
class UpdatesFragment : BaseFragment(R.layout.fragment_updates) { class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
@@ -67,113 +64,30 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
private val startForStorageManagerResult = private val startForStorageManagerResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (isRAndAbove() && Environment.isExternalStorageManager()) { if (isRAndAbove() && Environment.isExternalStorageManager()) {
updateSingle(app) viewModel.download(app)
} else { } else {
toast(R.string.permissions_denied) toast(R.string.permissions_denied)
} }
} }
private val startForPermissions = private val startForPermissions =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { perm -> registerForActivityResult(ActivityResultContracts.RequestPermission()) { perm ->
if (perm) updateSingle(app) else toast(R.string.permissions_denied) if (perm) viewModel.download(app) else toast(R.string.permissions_denied)
} }
val listOfActionsWhenServiceAttaches = ArrayList<Runnable>()
private lateinit var fetchListener: AbstractFetchGroupListener
private var updateService: UpdateService? = null
private var attachToServiceCalled = false
private var serviceConnection = object : ServiceConnection {
override fun onServiceConnected(name: ComponentName, binder: IBinder) {
updateService = (binder as UpdateService.UpdateServiceBinder).getUpdateService()
updateService!!.registerFetchListener(fetchListener)
if (listOfActionsWhenServiceAttaches.isNotEmpty()) {
val iterator = listOfActionsWhenServiceAttaches.iterator()
while (iterator.hasNext()) {
val next = iterator.next()
next.run()
iterator.remove()
}
}
}
override fun onServiceDisconnected(name: ComponentName) {
updateService = null
attachToServiceCalled = false
}
}
private var updateFileMap: MutableMap<Int, UpdateFile> = mutableMapOf()
override fun onResume() {
getUpdateServiceInstance()
super.onResume()
}
override fun onPause() {
if (updateService != null) {
updateService = null
attachToServiceCalled = false
requireContext().unbindService(serviceConnection)
}
super.onPause()
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
override fun onDestroy() {
super.onDestroy()
if (updateService != null) {
updateService = null
attachToServiceCalled = false
requireContext().unbindService(serviceConnection)
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentUpdatesBinding.bind(view) _binding = FragmentUpdatesBinding.bind(view)
fetchListener = object : AbstractFetchGroupListener() { viewModel.observe()
viewLifecycleOwner.lifecycleScope.launch {
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) { viewModel.updates.combine(viewModel.downloadsList) { uList, dList ->
viewModel.updateDownload(groupId, fetchGroup) uList?.associateWith { a ->
} dList.find { it.packageName == a.packageName && it.versionCode == a.versionCode }
override fun onProgress(
groupId: Int,
download: Download,
etaInMilliSeconds: Long,
downloadedBytesPerSecond: Long,
fetchGroup: FetchGroup
) {
viewModel.updateDownload(groupId, fetchGroup)
}
override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
if (fetchGroup.groupDownloadProgress == 100) {
viewModel.updateDownload(groupId, fetchGroup, isComplete = true)
} }
}.collectLatest {
updateController(it)
binding.swipeRefreshLayout.isRefreshing = false
} }
override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) {
viewModel.updateDownload(groupId, fetchGroup, isCancelled = true)
}
override fun onDeleted(groupId: Int, download: Download, fetchGroup: FetchGroup) {
viewModel.updateDownload(groupId, fetchGroup, isCancelled = true)
}
}
getUpdateServiceInstance()
viewModel.liveUpdateData.observe(viewLifecycleOwner) {
updateFileMap = it
updateController(updateFileMap)
binding.swipeRefreshLayout.isRefreshing = false
updateService?.liveUpdateData?.postValue(updateFileMap)
} }
binding.swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
@@ -183,10 +97,15 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
updateController(null) updateController(null)
} }
private fun updateController(updateFileMap: MutableMap<Int, UpdateFile>?) { override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
private fun updateController(appList: Map<App, Download?>?) {
binding.recycler.withModels { binding.recycler.withModels {
setFilterDuplicates(true) setFilterDuplicates(true)
if (updateFileMap == null) { if (appList == null) {
for (i in 1..10) { for (i in 1..10) {
add( add(
AppListViewShimmerModel_() AppListViewShimmerModel_()
@@ -194,7 +113,7 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
) )
} }
} else { } else {
if (updateFileMap.isEmpty()) { if (appList.isEmpty()) {
add( add(
NoAppViewModel_() NoAppViewModel_()
.id("no_update") .id("no_update")
@@ -206,8 +125,8 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
UpdateHeaderViewModel_() UpdateHeaderViewModel_()
.id("header_all") .id("header_all")
.title( .title(
"${updateFileMap.size} " + "${appList.size} " +
if (updateFileMap.size == 1) if (appList.size == 1)
getString(R.string.update_available) getString(R.string.update_available)
else else
getString(R.string.updates_available) getString(R.string.updates_available)
@@ -219,42 +138,46 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
getString(R.string.action_update_all) getString(R.string.action_update_all)
) )
.click { _ -> .click { _ ->
if (viewModel.updateAllEnqueued) if (viewModel.updateAllEnqueued) {
cancelAll() cancelAll()
else } else {
updateFileMap.values.forEach { updateSingle(it.app, true) } appList.keys.forEach { updateSingle(it, true) }
}
requestModelBuild() requestModelBuild()
} }
) )
updateFileMap.values.forEach { updateFile -> for ((app, download) in appList) {
add( add(
AppUpdateViewModel_() AppUpdateViewModel_()
.id(updateFile.hashCode()) .id(app.packageName)
.updateFile(updateFile) .app(app)
.click { _ -> .download(download)
openDetailsFragment( .click { _ -> openDetailsFragment(app.packageName, app) }
updateFile.app.packageName,
updateFile.app
)
}
.longClick { _ -> .longClick { _ ->
openAppMenuSheet(updateFile.app) openAppMenuSheet(app)
false false
} }
.positiveAction { _ -> updateSingle(updateFile.app) } .positiveAction { _ -> updateSingle(app) }
.negativeAction { _ -> cancelSingle(updateFile.app) } .negativeAction { _ -> cancelSingle(app) }
.installAction { _ -> .installAction { _ ->
updateFile.group?.downloads?.let { val files = File(
viewModel.install( PathUtil.getAppDownloadDir(
requireContext(), requireContext(),
updateFile.app.packageName, app.packageName,
it app.versionCode
) ).pathString
).listFiles()
// Downloaded files are missing, trigger re-download
if (files.isNullOrEmpty()) {
updateSingle(app)
return@installAction
} }
val apkFiles = files.filter { it.path.endsWith(".apk") }
viewModel.install(requireContext(), app.packageName, apkFiles)
} }
.state(updateFile.state)
) )
} }
} }
@@ -262,78 +185,42 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
} }
} }
private fun runInService(runnable: Runnable) {
if (updateService == null) {
listOfActionsWhenServiceAttaches.add(runnable)
getUpdateServiceInstance()
} else {
runnable.run()
}
}
private fun updateSingle(app: App, updateAll: Boolean = false) { private fun updateSingle(app: App, updateAll: Boolean = false) {
this.app = app this.app = app
runInService { viewModel.updateAllEnqueued = updateAll
viewModel.updateState(app.getGroupId(requireContext()), State.QUEUED)
viewModel.updateAllEnqueued = updateAll
if (PathUtil.needsStorageManagerPerm(app.fileList) || if (PathUtil.needsStorageManagerPerm(app.fileList) ||
requireContext().isExternalStorageEnable() requireContext().isExternalStorageEnable()
) { ) {
if (isRAndAbove()) { if (isRAndAbove()) {
if (!Environment.isExternalStorageManager()) { if (!Environment.isExternalStorageManager()) {
startForStorageManagerResult.launch( startForStorageManagerResult.launch(
Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION) Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
) )
} else {
updateService?.updateApp(app)
}
} else { } else {
if (ContextCompat.checkSelfPermission( viewModel.download(app)
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
updateService?.updateApp(app)
} else {
startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
} }
} else { } else {
updateService?.updateApp(app) if (ContextCompat.checkSelfPermission(
requireContext(),
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
viewModel.download(app)
} else {
startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
} }
} else {
viewModel.download(app)
} }
} }
private fun cancelSingle(app: App) { private fun cancelSingle(app: App) {
runInService { viewModel.cancelDownload(app)
updateService?.fetch?.cancelGroup(app.getGroupId(requireContext()))
}
} }
private fun cancelAll() { private fun cancelAll() {
runInService { viewModel.cancelAll()
viewModel.updateAllEnqueued = false
updateFileMap.values.forEach {
updateService?.fetch?.cancelGroup(
it.app.getGroupId(
requireContext()
)
)
}
}
}
private fun getUpdateServiceInstance() {
if (updateService == null && !attachToServiceCalled) {
attachToServiceCalled = true
val intent = Intent(requireContext(), UpdateService::class.java)
requireContext().startService(intent)
requireContext().bindService(
intent,
serviceConnection,
0
)
}
} }
} }

View File

@@ -23,172 +23,74 @@ import android.app.Application
import android.content.Context import android.content.Context
import android.util.Log import android.util.Log
import androidx.core.content.pm.PackageInfoCompat import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.store.State
import com.aurora.store.data.RequestState
import com.aurora.store.data.downloader.RequestGroupIdBuilder
import com.aurora.store.data.downloader.getGroupId
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.installer.AppInstaller import com.aurora.store.data.installer.AppInstaller
import com.aurora.store.data.model.UpdateFile import com.aurora.store.util.DownloadWorkerUtil
import com.tonyodev.fetch2.Download import dagger.hilt.android.lifecycle.HiltViewModel
import com.tonyodev.fetch2.FetchGroup
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.io.File import java.io.File
import java.util.Locale import java.util.Locale
import javax.inject.Inject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.launch
class UpdatesViewModel(application: Application) : BaseAppsViewModel(application) { @HiltViewModel
class UpdatesViewModel @Inject constructor(
application: Application,
private val downloadWorkerUtil: DownloadWorkerUtil
) : BaseAppsViewModel(application) {
private val TAG = UpdatesViewModel::class.java.simpleName private val TAG = UpdatesViewModel::class.java.simpleName
var updateFileMap: MutableMap<Int, UpdateFile> = mutableMapOf()
var liveUpdateData: MutableLiveData<MutableMap<Int, UpdateFile>> = MutableLiveData()
var updateAllEnqueued: Boolean = false var updateAllEnqueued: Boolean = false
init { private val _updates = MutableSharedFlow<List<App>?>()
EventBus.getDefault().register(this) val updates = _updates.asSharedFlow()
requestState = RequestState.Init val downloadsList get() = downloadWorkerUtil.downloadsList
observe()
}
override fun observe() { override fun observe() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
val marketApps = getFilteredApps() getFilteredApps().filter {
checkUpdate(marketApps) val packageInfo = packageInfoMap[it.packageName]
} catch (e: Exception) { if (packageInfo != null) {
requestState = RequestState.Pending it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo)
} } else {
} false
}
private fun checkUpdate(subAppList: List<App>) {
subAppList.filter {
val packageInfo = packageInfoMap[it.packageName]
if (packageInfo != null) {
it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo)
} else {
false
}
}.sortedBy { it.displayName.lowercase(Locale.getDefault()) }.also { apps ->
updateFileMap.clear()
apps.forEach {
updateFileMap[it.getGroupId(getApplication<Application>().applicationContext)] = UpdateFile(it)
}
liveUpdateData.postValue(updateFileMap)
requestState = RequestState.Complete
}
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onBusEvent(event: BusEvent) {
when (event) {
is BusEvent.InstallEvent -> {
updateListAndPost(event.packageName)
}
is BusEvent.UninstallEvent -> {
updateListAndPost(event.packageName)
}
is BusEvent.Blacklisted -> {
updateListAndPost(event.packageName)
}
else -> {
}
}
}
@Subscribe(threadMode = ThreadMode.BACKGROUND)
fun onInstallerEvent(event: InstallerEvent) {
when (event) {
is InstallerEvent.Success -> {
}
is InstallerEvent.Cancelled -> {
}
is InstallerEvent.Failed -> {
val packageName = event.packageName
packageName?.let {
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(getApplication<Application>().applicationContext, packageName.hashCode())
groupIDsOfPackageName.forEach {
updateDownload(it, null, true)
} }
}.sortedBy { it.displayName.lowercase(Locale.getDefault()) }.also { apps ->
_updates.emit(apps)
} }
} catch (exception: Exception) {
Log.d(TAG, "Failed to get updates", exception)
} }
} }
} }
fun updateState(id: Int, state: State) {
updateFileMap[id]?.state = state
liveUpdateData.postValue(updateFileMap)
}
fun updateDownload(
id: Int,
group: FetchGroup?,
isCancelled: Boolean = false,
isComplete: Boolean = false
) {
when {
isCancelled -> {
updateFileMap[id]?.state = State.IDLE
updateFileMap[id]?.group = null
}
isComplete -> {
updateFileMap[id]?.state = State.COMPLETE
updateFileMap[id]?.group = group
}
else -> {
updateFileMap[id]?.state = State.PROGRESS
updateFileMap[id]?.group = group
}
}
liveUpdateData.postValue(updateFileMap)
}
@Synchronized @Synchronized
fun install(context: Context, packageName: String, files: List<Download>) { fun install(context: Context, packageName: String, files: List<File>) {
if (files.all { File(it.file).exists() }) { try {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { AppInstaller.getInstance(context).getPreferredInstaller()
AppInstaller.getInstance(context).getPreferredInstaller().install( .install(packageName, files)
packageName,
files.filter { it.file.endsWith(".apk") }.map { it.file }.toList()
)
} catch (exception: Exception) {
Log.e(TAG, "Failed to install $packageName", exception)
}
} }
} else { } catch (exception: Exception) {
Log.e(TAG, "Given files doesn't exists!") Log.e(TAG, "Failed to install app", exception)
} }
} }
private fun updateListAndPost(packageName: String) { fun download(app: App) {
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(getApplication<Application>().applicationContext, packageName.hashCode()) viewModelScope.launch { downloadWorkerUtil.enqueueApp(app) }
groupIDsOfPackageName.forEach {
//Remove from map
updateFileMap.remove(it)
}
//Post new update list
liveUpdateData.postValue(updateFileMap)
} }
override fun onCleared() { fun cancelDownload(app: App) {
EventBus.getDefault().unregister(this) viewModelScope.launch { downloadWorkerUtil.cancelDownload(app.packageName) }
super.onCleared() }
fun cancelAll() {
viewModelScope.launch { downloadWorkerUtil.cancelAll() }
} }
} }

View File

@@ -17,6 +17,7 @@ import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.DownloadWorkerUtil import com.aurora.store.util.DownloadWorkerUtil
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import java.io.File
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
@@ -146,7 +147,7 @@ class AppDetailsViewModel @Inject constructor(
} }
@Synchronized @Synchronized
fun install(context: Context, packageName: String, files: List<Any>) { fun install(context: Context, packageName: String, files: List<File>) {
try { try {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
AppInstaller.getInstance(context).getPreferredInstaller() AppInstaller.getInstance(context).getPreferredInstaller()