Initial support for caching updates

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-07-16 16:31:18 +07:00
parent bec1bea05a
commit 8b341c088c
18 changed files with 312 additions and 157 deletions

View File

@@ -33,19 +33,26 @@ import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.setupWithNavController
import com.aurora.extensions.accentColor
import com.aurora.extensions.applyThemeAccent
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.model.NetworkStatus
import com.aurora.store.data.providers.NetworkProvider
import com.aurora.store.databinding.ActivityMainBinding
import com.aurora.store.util.AppUtil
import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
import com.aurora.store.view.ui.sheets.NetworkDialogSheet
import com.google.android.material.navigation.NavigationBarView
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import javax.inject.Inject
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
@Inject
lateinit var appUtil: AppUtil
private lateinit var B: ActivityMainBinding
private lateinit var navController: NavController
@@ -129,6 +136,26 @@ class MainActivity : AppCompatActivity() {
}
}
}
// Updates
lifecycleScope.launch {
AuroraApp.events.installerEvent.collectLatest {
when (it) {
is InstallerEvent.Installed -> appUtil.deleteUpdate(it.packageName)
is InstallerEvent.Uninstalled -> appUtil.deleteUpdate(it.packageName)
else -> {}
}
}
}
lifecycleScope.launch {
appUtil.updates.collectLatest { list ->
(B.navView as NavigationBarView).getOrCreateBadge(R.id.updatesFragment).apply {
isVisible = !list.isNullOrEmpty()
number = list?.size ?: 0
}
}
}
}
private fun isIntroDone(): Boolean {

View File

@@ -8,10 +8,17 @@ import com.aurora.store.data.room.download.DownloadConverter
import com.aurora.store.data.room.download.DownloadDao
import com.aurora.store.data.room.favourites.Favourite
import com.aurora.store.data.room.favourites.FavouriteDao
import com.aurora.store.data.room.update.Update
import com.aurora.store.data.room.update.UpdateDao
@Database(entities = [Download::class, Favourite::class], version = 2, exportSchema = false)
@Database(
entities = [Download::class, Favourite::class, Update::class],
version = 3,
exportSchema = false
)
@TypeConverters(DownloadConverter::class)
abstract class AuroraDatabase : RoomDatabase() {
abstract fun downloadDao(): DownloadDao
abstract fun favouriteDao(): FavouriteDao
abstract fun updateDao(): UpdateDao
}

View File

@@ -5,6 +5,7 @@ import androidx.room.Room
import com.aurora.store.data.room.download.DownloadConverter
import com.aurora.store.data.room.download.DownloadDao
import com.aurora.store.data.room.favourites.FavouriteDao
import com.aurora.store.data.room.update.UpdateDao
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -39,4 +40,9 @@ object RoomModule {
fun providesFavouriteDao(auroraDatabase: AuroraDatabase): FavouriteDao {
return auroraDatabase.favouriteDao()
}
@Provides
fun providesUpdateDao(auroraDatabase: AuroraDatabase): UpdateDao {
return auroraDatabase.updateDao()
}
}

View File

@@ -6,6 +6,7 @@ import androidx.room.PrimaryKey
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.File
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.update.Update
import kotlinx.parcelize.Parcelize
@Parcelize
@@ -52,5 +53,26 @@ data class Download(
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) }
)
}
fun fromUpdate(update: Update): Download {
return Download(
update.packageName,
update.versionCode,
update.offerType,
true,
update.displayName,
update.iconURL,
update.size,
update.id,
DownloadStatus.QUEUED,
0,
0L,
0L,
0,
0,
update.fileList,
update.sharedLibs
)
}
}
}

View File

@@ -0,0 +1,67 @@
package com.aurora.store.data.room.update
import android.content.Context
import android.os.Parcelable
import androidx.room.Entity
import androidx.room.PrimaryKey
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.File
import com.aurora.store.data.room.download.SharedLib
import com.aurora.store.util.CertUtil
import com.aurora.store.util.PackageUtil
import kotlinx.parcelize.Parcelize
@Parcelize
@Entity(tableName = "update")
data class Update(
@PrimaryKey
val packageName: String,
val versionCode: Int,
val versionName: String,
val displayName: String,
val iconURL: String,
val changelog: String,
val id: Int,
val developerName: String,
val size: Long,
val updatedOn: String,
val hasValidCert: Boolean,
val offerType: Int,
var fileList: List<File>,
val sharedLibs: List<SharedLib>
) : Parcelable {
companion object {
fun fromApp(context: Context, app: App): Update {
return Update(
app.packageName,
app.versionCode,
app.versionName,
app.displayName,
app.iconArtwork.url,
app.changes,
app.id,
app.developerName,
app.size,
app.updatedOn,
app.certificateSetList.any {
it.certificateSet in CertUtil.getEncodedCertificateHashes(
context, app.packageName
)
},
app.offerType,
app.fileList.filterNot { it.url.isBlank() },
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) }
)
}
}
fun isSelfUpdate(): Boolean {
return packageName == Constants.APP_ID
}
fun isInstalled(context: Context): Boolean {
return PackageUtil.isInstalled(context, packageName)
}
}

View File

@@ -0,0 +1,30 @@
package com.aurora.store.data.room.update
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
@Dao
interface UpdateDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(updates: List<Update>)
@Query("SELECT * FROM `update` ORDER BY displayName ASC")
fun updates(): Flow<List<Update>>
@Query("DELETE FROM `update` WHERE packageName = :packageName")
suspend fun delete(packageName: String)
@Query("DELETE FROM `update`")
suspend fun deleteAll()
@Transaction
suspend fun insertUpdates(updates: List<Update>) {
deleteAll()
insertAll(updates)
}
}

View File

@@ -27,7 +27,6 @@ import com.aurora.store.util.Preferences
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_AUTO
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
import com.aurora.store.util.save
import com.google.gson.Gson
import dagger.assisted.Assisted
import dagger.assisted.AssistedInject
import kotlinx.coroutines.Dispatchers
@@ -42,8 +41,8 @@ import java.util.concurrent.TimeUnit.MINUTES
*/
@HiltWorker
class UpdateWorker @AssistedInject constructor(
private val appUtil: AppUtil,
private val downloadWorkerUtil: DownloadWorkerUtil,
private val gson: Gson,
private val authProvider: AuthProvider,
@Assisted private val appContext: Context,
@Assisted workerParams: WorkerParameters
@@ -128,13 +127,7 @@ class UpdateWorker @AssistedInject constructor(
}
try {
val updatesList = AppUtil.getUpdatableApps(
context = appContext,
authData = authProvider.authData!!,
gson = gson,
verifyCert = true,
selfUpdate = false
)
val updatesList = appUtil.checkUpdates().filterNot { it.isSelfUpdate() }
if (updatesList.isNotEmpty()) {
if (autoUpdatesMode == 1) {
@@ -150,7 +143,7 @@ class UpdateWorker @AssistedInject constructor(
if (list.isEmpty()) return@let
Log.i(TAG, "Found auto-update enabled apps, updating!")
list.forEach { downloadWorkerUtil.enqueueApp(it) }
list.forEach { downloadWorkerUtil.enqueueUpdate(it) }
}
// Notify about remaining apps (if any)

View File

@@ -6,30 +6,47 @@ import android.util.Log
import androidx.core.content.pm.PackageInfoCompat
import com.aurora.Constants
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.data.models.AuthData
import com.aurora.gplayapi.helpers.AppDetailsHelper
import com.aurora.store.AuroraApp
import com.aurora.store.BuildConfig
import com.aurora.store.data.model.SelfUpdate
import com.aurora.store.data.network.HttpClient
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.data.room.update.Update
import com.aurora.store.data.room.update.UpdateDao
import com.google.gson.Gson
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.shareIn
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.withContext
import javax.inject.Inject
object AppUtil {
class AppUtil @Inject constructor(
private val gson: Gson,
private val authProvider: AuthProvider,
private val updateDao: UpdateDao,
@ApplicationContext private val context: Context
) {
private const val TAG = "AppUtil"
private const val RELEASE = "release"
private val TAG = AppUtil::class.java.simpleName
private val RELEASE = "release"
suspend fun getUpdatableApps(
context: Context,
authData: AuthData,
gson: Gson,
verifyCert: Boolean,
selfUpdate: Boolean = true
): List<App> {
private val isExtendedUpdateEnabled get() = Preferences.getBoolean(
context, Preferences.PREFERENCE_UPDATES_EXTENDED
)
val updates = updateDao.updates()
.map { list -> list.filter { it.isInstalled(context) } }
.map { list -> if (!isExtendedUpdateEnabled) list.filter { it.hasValidCert } else list }
.stateIn(AuroraApp.scope, SharingStarted.WhileSubscribed(), null)
suspend fun checkUpdates(): List<Update> {
Log.i(TAG, "Checking for updates")
val packageInfoMap = PackageUtil.getPackageInfoMap(context)
val appUpdatesList = getFilteredInstalledApps(context, authData, packageInfoMap).filter {
val appUpdatesList = getFilteredInstalledApps(packageInfoMap).filter {
val packageInfo = packageInfoMap[it.packageName]
if (packageInfo != null) {
it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo)
@@ -38,33 +55,26 @@ object AppUtil {
}
}.toMutableList()
val verifiedUpdatesList = if (verifyCert) {
appUpdatesList.filter { app ->
app.certificateSetList.any {
it.certificateSet in CertUtil.getEncodedCertificateHashes(
context, app.packageName
)
}
}.toMutableList()
} else {
appUpdatesList
if (canSelfUpdate(context)) {
getSelfUpdate(context, gson)?.let { appUpdatesList.add(it) }
}
if (canSelfUpdate(context) && selfUpdate) {
getSelfUpdate(context, gson)?.let { verifiedUpdatesList.add(it) }
return appUpdatesList.map { Update.fromApp(context, it) }.also {
// Cache the updates into the database
updateDao.insertUpdates(it)
}
}
return verifiedUpdatesList
suspend fun deleteUpdate(packageName: String) {
updateDao.delete(packageName)
}
suspend fun getFilteredInstalledApps(
context: Context,
authData: AuthData,
packageInfoMap: MutableMap<String, PackageInfo>? = null
): List<App> {
return withContext(Dispatchers.IO) {
val blackList = BlacklistProvider.with(context).getBlackList()
val appDetailsHelper = AppDetailsHelper(authData)
val appDetailsHelper = AppDetailsHelper(authProvider.authData!!)
.using(HttpClient.getPreferredClient(context))
(packageInfoMap ?: PackageUtil.getPackageInfoMap(context)).keys.let { packages ->

View File

@@ -12,6 +12,7 @@ import com.aurora.store.AuroraApp
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.download.Download
import com.aurora.store.data.room.download.DownloadDao
import com.aurora.store.data.room.update.Update
import com.aurora.store.data.work.DownloadWorker
import com.google.gson.Gson
import dagger.hilt.android.qualifiers.ApplicationContext
@@ -86,6 +87,14 @@ class DownloadWorkerUtil @Inject constructor(
downloadDao.insert(Download.fromApp(app))
}
/**
* Enqueues an update for download & install
* @param update [Update] to download
*/
suspend fun enqueueUpdate(update: Update) {
downloadDao.insert(Download.fromUpdate(update))
}
/**
* Cancels the download for the given package
* @param packageName Name of the package to cancel download

View File

@@ -25,6 +25,7 @@ import com.aurora.store.R
import com.aurora.store.data.activity.InstallActivity
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.download.Download
import com.aurora.store.data.room.update.Update
import java.util.UUID
import com.aurora.store.data.room.download.Download as AuroraDownload
@@ -185,7 +186,7 @@ object NotificationUtil {
.build()
}
fun getUpdateNotification(context: Context, updatesList: List<App>): Notification {
fun getUpdateNotification(context: Context, updatesList: List<Update>): Notification {
val contentIntent = NavDeepLinkBuilder(context)
.setGraph(R.navigation.mobile_navigation)
.setDestination(R.id.updatesFragment)

View File

@@ -41,6 +41,7 @@ import com.aurora.gplayapi.data.models.App
import com.aurora.store.R
import com.aurora.store.data.model.DownloadStatus
import com.aurora.store.data.room.download.Download
import com.aurora.store.data.room.update.Update
import com.aurora.store.databinding.ViewAppUpdateBinding
import com.aurora.store.util.CommonUtil
import com.aurora.store.view.epoxy.views.BaseModel
@@ -59,11 +60,11 @@ class AppUpdateView @JvmOverloads constructor(
private val cornersTransformation = RoundedCornersTransformation(8.px.toFloat())
@ModelProp
fun app(app: App) {
fun update(update: Update) {
/*Inflate App details*/
with(app) {
with(update) {
binding.txtLine1.text = displayName
binding.imgIcon.load(iconArtwork.url) {
binding.imgIcon.load(iconURL) {
placeholder(R.drawable.bg_placeholder)
transformations(cornersTransformation)
listener { _, result ->
@@ -74,9 +75,9 @@ class AppUpdateView @JvmOverloads constructor(
binding.txtLine2.text = developerName
binding.txtLine3.text = ("${CommonUtil.addSiPrefix(size)}$updatedOn")
binding.txtLine4.text = ("$versionName (${versionCode})")
binding.txtChangelog.text = if (changes.isNotEmpty())
binding.txtChangelog.text = if (changelog.isNotEmpty())
HtmlCompat.fromHtml(
changes,
changelog,
HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
)
else

View File

@@ -22,13 +22,14 @@ package com.aurora.store.view.epoxy.views.app
import android.content.Context
import android.util.AttributeSet
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import com.airbnb.epoxy.CallbackProp
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.aurora.store.databinding.ViewNoAppBinding
import com.aurora.store.view.epoxy.views.BaseModel
import com.aurora.store.view.epoxy.views.BaseView
@ModelView(
autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT,
baseModelClass = BaseModel::class
@@ -50,4 +51,19 @@ class NoAppView @JvmOverloads constructor(
binding.img.setImageDrawable(ContextCompat.getDrawable(context, icon))
}
}
@ModelProp
fun showAction(visibility: Boolean) {
binding.button.isVisible = visibility
}
@ModelProp
fun actionMessage(message: String) {
binding.button.text = message
}
@CallbackProp
fun actionCallback(viewOnClickListener: OnClickListener?) {
binding.button.setOnClickListener(viewOnClickListener)
}
}

View File

@@ -26,8 +26,6 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.AuroraApp
import com.aurora.store.data.event.BusEvent
import com.aurora.store.data.event.Event
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.databinding.FragmentAppsBinding
import com.aurora.store.view.epoxy.views.HeaderViewModel_
@@ -51,20 +49,6 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
}
}
private fun onEvent(event: Event) {
when (event) {
is InstallerEvent.Installed,
is InstallerEvent.Uninstalled,
is BusEvent.Blacklisted -> {
viewModel.fetchApps()
}
else -> {
Log.i(TAG, "Got an unhandled event: $event")
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@@ -75,7 +59,18 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
}
viewLifecycleOwner.lifecycleScope.launch {
AuroraApp.events.busEvent.collect { onEvent(it) }
AuroraApp.events.installerEvent.collect {
when (it) {
is InstallerEvent.Installed,
is InstallerEvent.Uninstalled -> {
viewModel.fetchApps()
}
else -> {
Log.i(TAG, "Got an unhandled event: $it")
}
}
}
}
}

View File

@@ -26,20 +26,17 @@ import android.os.Environment
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.aurora.Constants
import com.aurora.extensions.browse
import com.aurora.extensions.isRAndAbove
import com.aurora.extensions.toast
import com.aurora.gplayapi.data.models.App
import com.aurora.store.AuroraApp
import com.aurora.store.MobileNavigationDirections
import com.aurora.store.R
import com.aurora.store.data.event.Event
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.room.download.Download
import com.aurora.store.data.room.update.Update
import com.aurora.store.databinding.FragmentUpdatesBinding
import com.aurora.store.util.PackageUtil
import com.aurora.store.util.PathUtil
@@ -57,14 +54,14 @@ import kotlinx.coroutines.launch
@AndroidEntryPoint
class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
private lateinit var app: App
private lateinit var update: Update
private val viewModel: UpdatesViewModel by activityViewModels()
private val viewModel: UpdatesViewModel by viewModels()
private val startForStorageManagerResult =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (isRAndAbove() && Environment.isExternalStorageManager()) {
viewModel.download(app)
viewModel.download(update)
} else {
toast(R.string.permissions_denied)
}
@@ -72,7 +69,7 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
private val startForPermissions =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { perm ->
if (perm) viewModel.download(app) else toast(R.string.permissions_denied)
if (perm) viewModel.download(update) else toast(R.string.permissions_denied)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@@ -102,7 +99,6 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
}
}.collectLatest { map ->
updateController(map)
binding.swipeRefreshLayout.isRefreshing = false
viewModel.updateAllEnqueued =
map?.values?.all { it?.isRunning == true } ?: false
@@ -139,26 +135,19 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
}
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.fetchingUpdates.collect {
binding.swipeRefreshLayout.isRefreshing = it
if (it && viewModel.updates.value.isNullOrEmpty()) updateController(null)
}
}
binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.fetchUpdates()
}
viewLifecycleOwner.lifecycleScope.launch {
AuroraApp.events.busEvent.collect { onEvent(it) }
}
}
private fun onEvent(event: Event) {
when (event) {
is InstallerEvent.Installed, is InstallerEvent.Uninstalled -> {
viewModel.fetchUpdates()
}
else -> {}
}
}
private fun updateController(appList: Map<App, Download?>?) {
private fun updateController(appList: Map<Update, Download?>?) {
binding.recycler.withModels {
setFilterDuplicates(true)
if (appList == null) {
@@ -175,6 +164,9 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
.id("no_update")
.icon(R.drawable.ic_updates)
.message(getString(R.string.details_no_updates))
.showAction(true)
.actionMessage(getString(R.string.check_updates))
.actionCallback { _ -> viewModel.fetchUpdates() }
)
} else {
add(
@@ -193,25 +185,21 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
}
)
for ((app, download) in appList) {
for ((update, download) in appList) {
add(
AppUpdateViewModel_()
.id(app.packageName)
.app(app)
.id(update.packageName)
.update(update)
.download(download)
.click { _ ->
if (app.packageName == Constants.APP_ID) {
if (update.packageName == Constants.APP_ID) {
requireContext().browse(Constants.GITLAB_URL)
} else {
openDetailsFragment(app.packageName, app)
openDetailsFragment(update.packageName)
}
}
.longClick { _ ->
openAppMenuSheet(app)
false
}
.positiveAction { _ -> updateSingle(app) }
.negativeAction { _ -> cancelSingle(app) }
.positiveAction { _ -> updateSingle(update) }
.negativeAction { _ -> cancelSingle(update) }
)
}
}
@@ -219,18 +207,18 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
}
}
private fun updateSingle(app: App, updateAll: Boolean = false) {
this.app = app
private fun updateSingle(update: Update, updateAll: Boolean = false) {
this.update = update
viewModel.updateAllEnqueued = updateAll
if (PathUtil.needsStorageManagerPerm(app.fileList)) {
if (PathUtil.needsStorageManagerPerm(update.fileList)) {
if (isRAndAbove()) {
if (!Environment.isExternalStorageManager()) {
startForStorageManagerResult.launch(
PackageUtil.getStorageManagerIntent(requireContext())
)
} else {
viewModel.download(app)
viewModel.download(update)
}
} else {
if (ContextCompat.checkSelfPermission(
@@ -238,18 +226,18 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
Manifest.permission.WRITE_EXTERNAL_STORAGE
) == PackageManager.PERMISSION_GRANTED
) {
viewModel.download(app)
viewModel.download(update)
} else {
startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
}
}
} else {
viewModel.download(app)
viewModel.download(update)
}
}
private fun cancelSingle(app: App) {
viewModel.cancelDownload(app)
private fun cancelSingle(update: Update) {
viewModel.cancelDownload(update.packageName)
}
private fun cancelAll() {

View File

@@ -19,16 +19,13 @@
package com.aurora.store.viewmodel.all
import android.content.Context
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.util.AppUtil
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -37,10 +34,7 @@ import java.util.Locale
import javax.inject.Inject
@HiltViewModel
class InstalledViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val authProvider: AuthProvider
) : ViewModel() {
class InstalledViewModel @Inject constructor(private val appUtil: AppUtil) : ViewModel() {
private val TAG = InstalledViewModel::class.java.simpleName
@@ -56,7 +50,7 @@ class InstalledViewModel @Inject constructor(
fun fetchApps() {
viewModelScope.launch(Dispatchers.IO) {
try {
val apps = AppUtil.getFilteredInstalledApps(context, authProvider.authData!!)
val apps = appUtil.getFilteredInstalledApps()
_installedApps.emit(apps.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
} catch (exception: Exception) {
Log.e(TAG, "Failed to get installed apps", exception)

View File

@@ -20,76 +20,52 @@
package com.aurora.store.viewmodel.all
import android.annotation.SuppressLint
import android.content.Context
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.data.providers.AuthProvider
import com.aurora.store.data.room.update.Update
import com.aurora.store.util.AppUtil
import com.aurora.store.util.DownloadWorkerUtil
import com.aurora.store.util.Preferences
import com.google.gson.Gson
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import java.util.Locale
import javax.inject.Inject
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class UpdatesViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val gson: Gson,
private val appUtil: AppUtil,
private val downloadWorkerUtil: DownloadWorkerUtil,
private val authProvider: AuthProvider
) : ViewModel() {
private val TAG = UpdatesViewModel::class.java.simpleName
var updateAllEnqueued: Boolean = false
private val _updates = MutableStateFlow<List<App>?>(null)
val updates = _updates.asStateFlow()
val downloadsList get() = downloadWorkerUtil.downloadsList
val updates get() = appUtil.updates
init {
fetchUpdates()
}
private val _fetchingUpdates = MutableStateFlow(false)
val fetchingUpdates = _fetchingUpdates.asStateFlow()
fun fetchUpdates() {
_fetchingUpdates.value = true
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
val isExtendedUpdateEnabled = Preferences.getBoolean(
context, Preferences.PREFERENCE_UPDATES_EXTENDED
)
val updates = AppUtil.getUpdatableApps(
context,
authProvider.authData!!,
gson,
!isExtendedUpdateEnabled
).sortedBy { it.displayName.lowercase(Locale.getDefault()) }
_updates.emit(updates)
} catch (exception: Exception) {
Log.d(TAG, "Failed to get updates", exception)
}
try {
appUtil.checkUpdates()
} catch (exception: Exception) {
Log.d(TAG, "Failed to get updates", exception)
}
}
}.invokeOnCompletion { _fetchingUpdates.value = false }
}
fun download(app: App) {
viewModelScope.launch { downloadWorkerUtil.enqueueApp(app) }
fun download(update: Update) {
viewModelScope.launch { downloadWorkerUtil.enqueueUpdate(update) }
}
fun cancelDownload(app: App) {
viewModelScope.launch { downloadWorkerUtil.cancelDownload(app.packageName) }
fun cancelDownload(packageName: String) {
viewModelScope.launch { downloadWorkerUtil.cancelDownload(packageName) }
}
fun cancelAll() {