UpdatesFragment: Refactor to work with new downloads logic
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -26,6 +26,7 @@ import com.aurora.extensions.getString
|
||||
import com.aurora.extensions.runOnUiThread
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
import com.aurora.store.data.model.DownloadStatus
|
||||
import com.aurora.store.databinding.ViewUpdateButtonBinding
|
||||
|
||||
class UpdateButton : RelativeLayout {
|
||||
@@ -72,12 +73,12 @@ class UpdateButton : RelativeLayout {
|
||||
B.btnPositive.text = getString(text)
|
||||
}
|
||||
|
||||
fun updateState(state: State) {
|
||||
val displayChild = when (state) {
|
||||
State.IDLE, State.CANCELED -> 0
|
||||
State.QUEUED -> 1
|
||||
State.PROGRESS -> 2
|
||||
State.COMPLETE -> 3
|
||||
fun updateState(downloadStatus: DownloadStatus) {
|
||||
val displayChild = when (downloadStatus) {
|
||||
DownloadStatus.QUEUED,
|
||||
DownloadStatus.DOWNLOADING -> 2
|
||||
DownloadStatus.COMPLETED -> 3
|
||||
else -> 0
|
||||
}
|
||||
|
||||
if (B.viewFlipper.displayedChild != displayChild) {
|
||||
|
||||
@@ -34,13 +34,17 @@ import com.airbnb.epoxy.ModelView
|
||||
import com.airbnb.epoxy.OnViewRecycled
|
||||
import com.aurora.extensions.invisible
|
||||
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.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.util.CommonUtil
|
||||
import com.aurora.store.util.PathUtil
|
||||
import com.aurora.store.view.epoxy.views.BaseView
|
||||
import java.io.File
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
@ModelView(
|
||||
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
|
||||
@@ -72,76 +76,77 @@ class AppUpdateView : RelativeLayout {
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun updateFile(updateFile: UpdateFile?) {
|
||||
if (updateFile != null) {
|
||||
|
||||
/*Inflate App details*/
|
||||
with(updateFile.app) {
|
||||
B.txtLine1.text = displayName
|
||||
B.imgIcon.load(iconArtwork.url) {
|
||||
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
|
||||
}
|
||||
}
|
||||
fun app(app: App) {
|
||||
/*Inflate App details*/
|
||||
with(app) {
|
||||
B.txtLine1.text = displayName
|
||||
B.imgIcon.load(iconArtwork.url) {
|
||||
placeholder(R.drawable.bg_placeholder)
|
||||
transformations(RoundedCornersTransformation(8.px.toFloat()))
|
||||
}
|
||||
|
||||
/*Inflate Download details*/
|
||||
updateFile.group?.let {
|
||||
when (updateFile.state) {
|
||||
State.QUEUED -> {
|
||||
B.progressDownload.progress = 0
|
||||
B.progressDownload.show()
|
||||
B.btnAction.updateState(State.QUEUED)
|
||||
}
|
||||
State.IDLE, State.CANCELED -> {
|
||||
B.progressDownload.progress = 0
|
||||
B.progressDownload.invisible()
|
||||
B.btnAction.updateState(State.IDLE)
|
||||
}
|
||||
State.PROGRESS -> {
|
||||
val progress = it.groupDownloadProgress
|
||||
if (progress > 0) {
|
||||
if (progress == 100) {
|
||||
B.progressDownload.invisible()
|
||||
} else {
|
||||
B.progressDownload.progress = progress
|
||||
B.progressDownload.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
State.COMPLETE -> {
|
||||
B.progressDownload.invisible()
|
||||
B.btnAction.updateState(State.COMPLETE)
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ModelProp
|
||||
fun state(state: State?) {
|
||||
state?.let {
|
||||
B.btnAction.updateState(it)
|
||||
fun download(download: Download?) {
|
||||
if (download != null) {
|
||||
/*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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,26 +20,21 @@
|
||||
package com.aurora.store.view.ui.updates
|
||||
|
||||
import android.Manifest
|
||||
import android.content.ComponentName
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.os.IBinder
|
||||
import android.provider.Settings
|
||||
import android.view.View
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.aurora.extensions.isRAndAbove
|
||||
import com.aurora.extensions.toast
|
||||
import com.aurora.gplayapi.data.models.App
|
||||
import com.aurora.store.R
|
||||
import com.aurora.store.State
|
||||
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.data.room.download.Download
|
||||
import com.aurora.store.databinding.FragmentUpdatesBinding
|
||||
import com.aurora.store.util.PathUtil
|
||||
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.ui.commons.BaseFragment
|
||||
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 java.io.File
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.io.path.pathString
|
||||
|
||||
@AndroidEntryPoint
|
||||
class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
|
||||
@@ -67,113 +64,30 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
|
||||
private val startForStorageManagerResult =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||
if (isRAndAbove() && Environment.isExternalStorageManager()) {
|
||||
updateSingle(app)
|
||||
viewModel.download(app)
|
||||
} else {
|
||||
toast(R.string.permissions_denied)
|
||||
}
|
||||
}
|
||||
private val startForPermissions =
|
||||
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?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
_binding = FragmentUpdatesBinding.bind(view)
|
||||
|
||||
fetchListener = object : AbstractFetchGroupListener() {
|
||||
|
||||
override fun onAdded(groupId: Int, download: Download, fetchGroup: FetchGroup) {
|
||||
viewModel.updateDownload(groupId, fetchGroup)
|
||||
}
|
||||
|
||||
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)
|
||||
viewModel.observe()
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewModel.updates.combine(viewModel.downloadsList) { uList, dList ->
|
||||
uList?.associateWith { a ->
|
||||
dList.find { it.packageName == a.packageName && it.versionCode == a.versionCode }
|
||||
}
|
||||
}.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 {
|
||||
@@ -183,10 +97,15 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
|
||||
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 {
|
||||
setFilterDuplicates(true)
|
||||
if (updateFileMap == null) {
|
||||
if (appList == null) {
|
||||
for (i in 1..10) {
|
||||
add(
|
||||
AppListViewShimmerModel_()
|
||||
@@ -194,7 +113,7 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (updateFileMap.isEmpty()) {
|
||||
if (appList.isEmpty()) {
|
||||
add(
|
||||
NoAppViewModel_()
|
||||
.id("no_update")
|
||||
@@ -206,8 +125,8 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
|
||||
UpdateHeaderViewModel_()
|
||||
.id("header_all")
|
||||
.title(
|
||||
"${updateFileMap.size} " +
|
||||
if (updateFileMap.size == 1)
|
||||
"${appList.size} " +
|
||||
if (appList.size == 1)
|
||||
getString(R.string.update_available)
|
||||
else
|
||||
getString(R.string.updates_available)
|
||||
@@ -219,42 +138,46 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
|
||||
getString(R.string.action_update_all)
|
||||
)
|
||||
.click { _ ->
|
||||
if (viewModel.updateAllEnqueued)
|
||||
if (viewModel.updateAllEnqueued) {
|
||||
cancelAll()
|
||||
else
|
||||
updateFileMap.values.forEach { updateSingle(it.app, true) }
|
||||
|
||||
} else {
|
||||
appList.keys.forEach { updateSingle(it, true) }
|
||||
}
|
||||
requestModelBuild()
|
||||
}
|
||||
)
|
||||
|
||||
updateFileMap.values.forEach { updateFile ->
|
||||
for ((app, download) in appList) {
|
||||
add(
|
||||
AppUpdateViewModel_()
|
||||
.id(updateFile.hashCode())
|
||||
.updateFile(updateFile)
|
||||
.click { _ ->
|
||||
openDetailsFragment(
|
||||
updateFile.app.packageName,
|
||||
updateFile.app
|
||||
)
|
||||
}
|
||||
.id(app.packageName)
|
||||
.app(app)
|
||||
.download(download)
|
||||
.click { _ -> openDetailsFragment(app.packageName, app) }
|
||||
.longClick { _ ->
|
||||
openAppMenuSheet(updateFile.app)
|
||||
openAppMenuSheet(app)
|
||||
false
|
||||
}
|
||||
.positiveAction { _ -> updateSingle(updateFile.app) }
|
||||
.negativeAction { _ -> cancelSingle(updateFile.app) }
|
||||
.positiveAction { _ -> updateSingle(app) }
|
||||
.negativeAction { _ -> cancelSingle(app) }
|
||||
.installAction { _ ->
|
||||
updateFile.group?.downloads?.let {
|
||||
viewModel.install(
|
||||
val files = File(
|
||||
PathUtil.getAppDownloadDir(
|
||||
requireContext(),
|
||||
updateFile.app.packageName,
|
||||
it
|
||||
)
|
||||
app.packageName,
|
||||
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) {
|
||||
this.app = app
|
||||
runInService {
|
||||
viewModel.updateState(app.getGroupId(requireContext()), State.QUEUED)
|
||||
viewModel.updateAllEnqueued = updateAll
|
||||
viewModel.updateAllEnqueued = updateAll
|
||||
|
||||
if (PathUtil.needsStorageManagerPerm(app.fileList) ||
|
||||
requireContext().isExternalStorageEnable()
|
||||
) {
|
||||
if (isRAndAbove()) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
startForStorageManagerResult.launch(
|
||||
Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
)
|
||||
} else {
|
||||
updateService?.updateApp(app)
|
||||
}
|
||||
if (PathUtil.needsStorageManagerPerm(app.fileList) ||
|
||||
requireContext().isExternalStorageEnable()
|
||||
) {
|
||||
if (isRAndAbove()) {
|
||||
if (!Environment.isExternalStorageManager()) {
|
||||
startForStorageManagerResult.launch(
|
||||
Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
|
||||
)
|
||||
} else {
|
||||
if (ContextCompat.checkSelfPermission(
|
||||
requireContext(),
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
updateService?.updateApp(app)
|
||||
} else {
|
||||
startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||
}
|
||||
viewModel.download(app)
|
||||
}
|
||||
} 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) {
|
||||
runInService {
|
||||
updateService?.fetch?.cancelGroup(app.getGroupId(requireContext()))
|
||||
}
|
||||
viewModel.cancelDownload(app)
|
||||
}
|
||||
|
||||
private fun cancelAll() {
|
||||
runInService {
|
||||
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
|
||||
)
|
||||
}
|
||||
viewModel.cancelAll()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,172 +23,74 @@ import android.app.Application
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.model.UpdateFile
|
||||
import com.tonyodev.fetch2.Download
|
||||
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 com.aurora.store.util.DownloadWorkerUtil
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.io.File
|
||||
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
|
||||
|
||||
var updateFileMap: MutableMap<Int, UpdateFile> = mutableMapOf()
|
||||
var liveUpdateData: MutableLiveData<MutableMap<Int, UpdateFile>> = MutableLiveData()
|
||||
var updateAllEnqueued: Boolean = false
|
||||
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
private val _updates = MutableSharedFlow<List<App>?>()
|
||||
val updates = _updates.asSharedFlow()
|
||||
|
||||
requestState = RequestState.Init
|
||||
observe()
|
||||
}
|
||||
val downloadsList get() = downloadWorkerUtil.downloadsList
|
||||
|
||||
override fun observe() {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val marketApps = getFilteredApps()
|
||||
checkUpdate(marketApps)
|
||||
} catch (e: Exception) {
|
||||
requestState = RequestState.Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
getFilteredApps().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 ->
|
||||
_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
|
||||
fun install(context: Context, packageName: String, files: List<Download>) {
|
||||
if (files.all { File(it.file).exists() }) {
|
||||
fun install(context: Context, packageName: String, files: List<File>) {
|
||||
try {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
AppInstaller.getInstance(context).getPreferredInstaller().install(
|
||||
packageName,
|
||||
files.filter { it.file.endsWith(".apk") }.map { it.file }.toList()
|
||||
)
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to install $packageName", exception)
|
||||
}
|
||||
AppInstaller.getInstance(context).getPreferredInstaller()
|
||||
.install(packageName, files)
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, "Given files doesn't exists!")
|
||||
} catch (exception: Exception) {
|
||||
Log.e(TAG, "Failed to install app", exception)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateListAndPost(packageName: String) {
|
||||
val groupIDsOfPackageName = RequestGroupIdBuilder.getGroupIDsForApp(getApplication<Application>().applicationContext, packageName.hashCode())
|
||||
groupIDsOfPackageName.forEach {
|
||||
//Remove from map
|
||||
updateFileMap.remove(it)
|
||||
}
|
||||
|
||||
//Post new update list
|
||||
liveUpdateData.postValue(updateFileMap)
|
||||
fun download(app: App) {
|
||||
viewModelScope.launch { downloadWorkerUtil.enqueueApp(app) }
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
EventBus.getDefault().unregister(this)
|
||||
super.onCleared()
|
||||
fun cancelDownload(app: App) {
|
||||
viewModelScope.launch { downloadWorkerUtil.cancelDownload(app.packageName) }
|
||||
}
|
||||
|
||||
fun cancelAll() {
|
||||
viewModelScope.launch { downloadWorkerUtil.cancelAll() }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import com.aurora.store.data.providers.AuthProvider
|
||||
import com.aurora.store.util.DownloadWorkerUtil
|
||||
import com.google.gson.GsonBuilder
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import java.io.File
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
@@ -146,7 +147,7 @@ class AppDetailsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun install(context: Context, packageName: String, files: List<Any>) {
|
||||
fun install(context: Context, packageName: String, files: List<File>) {
|
||||
try {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
AppInstaller.getInstance(context).getPreferredInstaller()
|
||||
|
||||
Reference in New Issue
Block a user