Initial support for caching updates
Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
@@ -33,19 +33,26 @@ import androidx.navigation.fragment.NavHostFragment
|
|||||||
import androidx.navigation.ui.setupWithNavController
|
import androidx.navigation.ui.setupWithNavController
|
||||||
import com.aurora.extensions.accentColor
|
import com.aurora.extensions.accentColor
|
||||||
import com.aurora.extensions.applyThemeAccent
|
import com.aurora.extensions.applyThemeAccent
|
||||||
|
import com.aurora.store.data.event.InstallerEvent
|
||||||
import com.aurora.store.data.model.NetworkStatus
|
import com.aurora.store.data.model.NetworkStatus
|
||||||
import com.aurora.store.data.providers.NetworkProvider
|
import com.aurora.store.data.providers.NetworkProvider
|
||||||
import com.aurora.store.databinding.ActivityMainBinding
|
import com.aurora.store.databinding.ActivityMainBinding
|
||||||
|
import com.aurora.store.util.AppUtil
|
||||||
import com.aurora.store.util.Preferences
|
import com.aurora.store.util.Preferences
|
||||||
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
import com.aurora.store.util.Preferences.PREFERENCE_DEFAULT_SELECTED_TAB
|
||||||
import com.aurora.store.view.ui.sheets.NetworkDialogSheet
|
import com.aurora.store.view.ui.sheets.NetworkDialogSheet
|
||||||
import com.google.android.material.navigation.NavigationBarView
|
import com.google.android.material.navigation.NavigationBarView
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var appUtil: AppUtil
|
||||||
|
|
||||||
private lateinit var B: ActivityMainBinding
|
private lateinit var B: ActivityMainBinding
|
||||||
private lateinit var navController: NavController
|
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 {
|
private fun isIntroDone(): Boolean {
|
||||||
|
|||||||
@@ -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.download.DownloadDao
|
||||||
import com.aurora.store.data.room.favourites.Favourite
|
import com.aurora.store.data.room.favourites.Favourite
|
||||||
import com.aurora.store.data.room.favourites.FavouriteDao
|
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)
|
@TypeConverters(DownloadConverter::class)
|
||||||
abstract class AuroraDatabase : RoomDatabase() {
|
abstract class AuroraDatabase : RoomDatabase() {
|
||||||
abstract fun downloadDao(): DownloadDao
|
abstract fun downloadDao(): DownloadDao
|
||||||
abstract fun favouriteDao(): FavouriteDao
|
abstract fun favouriteDao(): FavouriteDao
|
||||||
|
abstract fun updateDao(): UpdateDao
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.room.Room
|
|||||||
import com.aurora.store.data.room.download.DownloadConverter
|
import com.aurora.store.data.room.download.DownloadConverter
|
||||||
import com.aurora.store.data.room.download.DownloadDao
|
import com.aurora.store.data.room.download.DownloadDao
|
||||||
import com.aurora.store.data.room.favourites.FavouriteDao
|
import com.aurora.store.data.room.favourites.FavouriteDao
|
||||||
|
import com.aurora.store.data.room.update.UpdateDao
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
@@ -39,4 +40,9 @@ object RoomModule {
|
|||||||
fun providesFavouriteDao(auroraDatabase: AuroraDatabase): FavouriteDao {
|
fun providesFavouriteDao(auroraDatabase: AuroraDatabase): FavouriteDao {
|
||||||
return auroraDatabase.favouriteDao()
|
return auroraDatabase.favouriteDao()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providesUpdateDao(auroraDatabase: AuroraDatabase): UpdateDao {
|
||||||
|
return auroraDatabase.updateDao()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import androidx.room.PrimaryKey
|
|||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.File
|
import com.aurora.gplayapi.data.models.File
|
||||||
import com.aurora.store.data.model.DownloadStatus
|
import com.aurora.store.data.model.DownloadStatus
|
||||||
|
import com.aurora.store.data.room.update.Update
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
@Parcelize
|
@Parcelize
|
||||||
@@ -52,5 +53,26 @@ data class Download(
|
|||||||
app.dependencies.dependentLibraries.map { SharedLib.fromApp(it) }
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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_AUTO
|
||||||
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
|
import com.aurora.store.util.Preferences.PREFERENCE_UPDATES_CHECK_INTERVAL
|
||||||
import com.aurora.store.util.save
|
import com.aurora.store.util.save
|
||||||
import com.google.gson.Gson
|
|
||||||
import dagger.assisted.Assisted
|
import dagger.assisted.Assisted
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@@ -42,8 +41,8 @@ import java.util.concurrent.TimeUnit.MINUTES
|
|||||||
*/
|
*/
|
||||||
@HiltWorker
|
@HiltWorker
|
||||||
class UpdateWorker @AssistedInject constructor(
|
class UpdateWorker @AssistedInject constructor(
|
||||||
|
private val appUtil: AppUtil,
|
||||||
private val downloadWorkerUtil: DownloadWorkerUtil,
|
private val downloadWorkerUtil: DownloadWorkerUtil,
|
||||||
private val gson: Gson,
|
|
||||||
private val authProvider: AuthProvider,
|
private val authProvider: AuthProvider,
|
||||||
@Assisted private val appContext: Context,
|
@Assisted private val appContext: Context,
|
||||||
@Assisted workerParams: WorkerParameters
|
@Assisted workerParams: WorkerParameters
|
||||||
@@ -128,13 +127,7 @@ class UpdateWorker @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val updatesList = AppUtil.getUpdatableApps(
|
val updatesList = appUtil.checkUpdates().filterNot { it.isSelfUpdate() }
|
||||||
context = appContext,
|
|
||||||
authData = authProvider.authData!!,
|
|
||||||
gson = gson,
|
|
||||||
verifyCert = true,
|
|
||||||
selfUpdate = false
|
|
||||||
)
|
|
||||||
|
|
||||||
if (updatesList.isNotEmpty()) {
|
if (updatesList.isNotEmpty()) {
|
||||||
if (autoUpdatesMode == 1) {
|
if (autoUpdatesMode == 1) {
|
||||||
@@ -150,7 +143,7 @@ class UpdateWorker @AssistedInject constructor(
|
|||||||
if (list.isEmpty()) return@let
|
if (list.isEmpty()) return@let
|
||||||
|
|
||||||
Log.i(TAG, "Found auto-update enabled apps, updating!")
|
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)
|
// Notify about remaining apps (if any)
|
||||||
|
|||||||
@@ -6,30 +6,47 @@ import android.util.Log
|
|||||||
import androidx.core.content.pm.PackageInfoCompat
|
import androidx.core.content.pm.PackageInfoCompat
|
||||||
import com.aurora.Constants
|
import com.aurora.Constants
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.gplayapi.data.models.AuthData
|
|
||||||
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
import com.aurora.gplayapi.helpers.AppDetailsHelper
|
||||||
|
import com.aurora.store.AuroraApp
|
||||||
import com.aurora.store.BuildConfig
|
import com.aurora.store.BuildConfig
|
||||||
import com.aurora.store.data.model.SelfUpdate
|
import com.aurora.store.data.model.SelfUpdate
|
||||||
import com.aurora.store.data.network.HttpClient
|
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.providers.BlacklistProvider
|
||||||
|
import com.aurora.store.data.room.update.Update
|
||||||
|
import com.aurora.store.data.room.update.UpdateDao
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
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 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 val TAG = AppUtil::class.java.simpleName
|
||||||
private const val RELEASE = "release"
|
private val RELEASE = "release"
|
||||||
|
|
||||||
suspend fun getUpdatableApps(
|
private val isExtendedUpdateEnabled get() = Preferences.getBoolean(
|
||||||
context: Context,
|
context, Preferences.PREFERENCE_UPDATES_EXTENDED
|
||||||
authData: AuthData,
|
)
|
||||||
gson: Gson,
|
val updates = updateDao.updates()
|
||||||
verifyCert: Boolean,
|
.map { list -> list.filter { it.isInstalled(context) } }
|
||||||
selfUpdate: Boolean = true
|
.map { list -> if (!isExtendedUpdateEnabled) list.filter { it.hasValidCert } else list }
|
||||||
): List<App> {
|
.stateIn(AuroraApp.scope, SharingStarted.WhileSubscribed(), null)
|
||||||
|
|
||||||
|
suspend fun checkUpdates(): List<Update> {
|
||||||
|
Log.i(TAG, "Checking for updates")
|
||||||
val packageInfoMap = PackageUtil.getPackageInfoMap(context)
|
val packageInfoMap = PackageUtil.getPackageInfoMap(context)
|
||||||
val appUpdatesList = getFilteredInstalledApps(context, authData, packageInfoMap).filter {
|
val appUpdatesList = getFilteredInstalledApps(packageInfoMap).filter {
|
||||||
val packageInfo = packageInfoMap[it.packageName]
|
val packageInfo = packageInfoMap[it.packageName]
|
||||||
if (packageInfo != null) {
|
if (packageInfo != null) {
|
||||||
it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo)
|
it.versionCode.toLong() > PackageInfoCompat.getLongVersionCode(packageInfo)
|
||||||
@@ -38,33 +55,26 @@ object AppUtil {
|
|||||||
}
|
}
|
||||||
}.toMutableList()
|
}.toMutableList()
|
||||||
|
|
||||||
val verifiedUpdatesList = if (verifyCert) {
|
if (canSelfUpdate(context)) {
|
||||||
appUpdatesList.filter { app ->
|
getSelfUpdate(context, gson)?.let { appUpdatesList.add(it) }
|
||||||
app.certificateSetList.any {
|
|
||||||
it.certificateSet in CertUtil.getEncodedCertificateHashes(
|
|
||||||
context, app.packageName
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}.toMutableList()
|
|
||||||
} else {
|
|
||||||
appUpdatesList
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (canSelfUpdate(context) && selfUpdate) {
|
return appUpdatesList.map { Update.fromApp(context, it) }.also {
|
||||||
getSelfUpdate(context, gson)?.let { verifiedUpdatesList.add(it) }
|
// Cache the updates into the database
|
||||||
|
updateDao.insertUpdates(it)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return verifiedUpdatesList
|
suspend fun deleteUpdate(packageName: String) {
|
||||||
|
updateDao.delete(packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getFilteredInstalledApps(
|
suspend fun getFilteredInstalledApps(
|
||||||
context: Context,
|
|
||||||
authData: AuthData,
|
|
||||||
packageInfoMap: MutableMap<String, PackageInfo>? = null
|
packageInfoMap: MutableMap<String, PackageInfo>? = null
|
||||||
): List<App> {
|
): List<App> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
val blackList = BlacklistProvider.with(context).getBlackList()
|
val blackList = BlacklistProvider.with(context).getBlackList()
|
||||||
val appDetailsHelper = AppDetailsHelper(authData)
|
val appDetailsHelper = AppDetailsHelper(authProvider.authData!!)
|
||||||
.using(HttpClient.getPreferredClient(context))
|
.using(HttpClient.getPreferredClient(context))
|
||||||
|
|
||||||
(packageInfoMap ?: PackageUtil.getPackageInfoMap(context)).keys.let { packages ->
|
(packageInfoMap ?: PackageUtil.getPackageInfoMap(context)).keys.let { packages ->
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import com.aurora.store.AuroraApp
|
|||||||
import com.aurora.store.data.model.DownloadStatus
|
import com.aurora.store.data.model.DownloadStatus
|
||||||
import com.aurora.store.data.room.download.Download
|
import com.aurora.store.data.room.download.Download
|
||||||
import com.aurora.store.data.room.download.DownloadDao
|
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.aurora.store.data.work.DownloadWorker
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
@@ -86,6 +87,14 @@ class DownloadWorkerUtil @Inject constructor(
|
|||||||
downloadDao.insert(Download.fromApp(app))
|
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
|
* Cancels the download for the given package
|
||||||
* @param packageName Name of the package to cancel download
|
* @param packageName Name of the package to cancel download
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import com.aurora.store.R
|
|||||||
import com.aurora.store.data.activity.InstallActivity
|
import com.aurora.store.data.activity.InstallActivity
|
||||||
import com.aurora.store.data.model.DownloadStatus
|
import com.aurora.store.data.model.DownloadStatus
|
||||||
import com.aurora.store.data.room.download.Download
|
import com.aurora.store.data.room.download.Download
|
||||||
|
import com.aurora.store.data.room.update.Update
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import com.aurora.store.data.room.download.Download as AuroraDownload
|
import com.aurora.store.data.room.download.Download as AuroraDownload
|
||||||
|
|
||||||
@@ -185,7 +186,7 @@ object NotificationUtil {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getUpdateNotification(context: Context, updatesList: List<App>): Notification {
|
fun getUpdateNotification(context: Context, updatesList: List<Update>): Notification {
|
||||||
val contentIntent = NavDeepLinkBuilder(context)
|
val contentIntent = NavDeepLinkBuilder(context)
|
||||||
.setGraph(R.navigation.mobile_navigation)
|
.setGraph(R.navigation.mobile_navigation)
|
||||||
.setDestination(R.id.updatesFragment)
|
.setDestination(R.id.updatesFragment)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ import com.aurora.gplayapi.data.models.App
|
|||||||
import com.aurora.store.R
|
import com.aurora.store.R
|
||||||
import com.aurora.store.data.model.DownloadStatus
|
import com.aurora.store.data.model.DownloadStatus
|
||||||
import com.aurora.store.data.room.download.Download
|
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.databinding.ViewAppUpdateBinding
|
||||||
import com.aurora.store.util.CommonUtil
|
import com.aurora.store.util.CommonUtil
|
||||||
import com.aurora.store.view.epoxy.views.BaseModel
|
import com.aurora.store.view.epoxy.views.BaseModel
|
||||||
@@ -59,11 +60,11 @@ class AppUpdateView @JvmOverloads constructor(
|
|||||||
private val cornersTransformation = RoundedCornersTransformation(8.px.toFloat())
|
private val cornersTransformation = RoundedCornersTransformation(8.px.toFloat())
|
||||||
|
|
||||||
@ModelProp
|
@ModelProp
|
||||||
fun app(app: App) {
|
fun update(update: Update) {
|
||||||
/*Inflate App details*/
|
/*Inflate App details*/
|
||||||
with(app) {
|
with(update) {
|
||||||
binding.txtLine1.text = displayName
|
binding.txtLine1.text = displayName
|
||||||
binding.imgIcon.load(iconArtwork.url) {
|
binding.imgIcon.load(iconURL) {
|
||||||
placeholder(R.drawable.bg_placeholder)
|
placeholder(R.drawable.bg_placeholder)
|
||||||
transformations(cornersTransformation)
|
transformations(cornersTransformation)
|
||||||
listener { _, result ->
|
listener { _, result ->
|
||||||
@@ -74,9 +75,9 @@ class AppUpdateView @JvmOverloads constructor(
|
|||||||
binding.txtLine2.text = developerName
|
binding.txtLine2.text = developerName
|
||||||
binding.txtLine3.text = ("${CommonUtil.addSiPrefix(size)} • $updatedOn")
|
binding.txtLine3.text = ("${CommonUtil.addSiPrefix(size)} • $updatedOn")
|
||||||
binding.txtLine4.text = ("$versionName (${versionCode})")
|
binding.txtLine4.text = ("$versionName (${versionCode})")
|
||||||
binding.txtChangelog.text = if (changes.isNotEmpty())
|
binding.txtChangelog.text = if (changelog.isNotEmpty())
|
||||||
HtmlCompat.fromHtml(
|
HtmlCompat.fromHtml(
|
||||||
changes,
|
changelog,
|
||||||
HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
|
HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -22,13 +22,14 @@ package com.aurora.store.view.epoxy.views.app
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.core.view.isVisible
|
||||||
|
import com.airbnb.epoxy.CallbackProp
|
||||||
import com.airbnb.epoxy.ModelProp
|
import com.airbnb.epoxy.ModelProp
|
||||||
import com.airbnb.epoxy.ModelView
|
import com.airbnb.epoxy.ModelView
|
||||||
import com.aurora.store.databinding.ViewNoAppBinding
|
import com.aurora.store.databinding.ViewNoAppBinding
|
||||||
import com.aurora.store.view.epoxy.views.BaseModel
|
import com.aurora.store.view.epoxy.views.BaseModel
|
||||||
import com.aurora.store.view.epoxy.views.BaseView
|
import com.aurora.store.view.epoxy.views.BaseView
|
||||||
|
|
||||||
|
|
||||||
@ModelView(
|
@ModelView(
|
||||||
autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT,
|
autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT,
|
||||||
baseModelClass = BaseModel::class
|
baseModelClass = BaseModel::class
|
||||||
@@ -50,4 +51,19 @@ class NoAppView @JvmOverloads constructor(
|
|||||||
binding.img.setImageDrawable(ContextCompat.getDrawable(context, icon))
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,6 @@ import androidx.fragment.app.activityViewModels
|
|||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.gplayapi.data.models.App
|
||||||
import com.aurora.store.AuroraApp
|
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.data.event.InstallerEvent
|
||||||
import com.aurora.store.databinding.FragmentAppsBinding
|
import com.aurora.store.databinding.FragmentAppsBinding
|
||||||
import com.aurora.store.view.epoxy.views.HeaderViewModel_
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
@@ -75,7 +59,18 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,20 +26,17 @@ import android.os.Environment
|
|||||||
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.activityViewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.aurora.Constants
|
import com.aurora.Constants
|
||||||
import com.aurora.extensions.browse
|
import com.aurora.extensions.browse
|
||||||
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.store.AuroraApp
|
|
||||||
import com.aurora.store.MobileNavigationDirections
|
import com.aurora.store.MobileNavigationDirections
|
||||||
import com.aurora.store.R
|
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.download.Download
|
||||||
|
import com.aurora.store.data.room.update.Update
|
||||||
import com.aurora.store.databinding.FragmentUpdatesBinding
|
import com.aurora.store.databinding.FragmentUpdatesBinding
|
||||||
import com.aurora.store.util.PackageUtil
|
import com.aurora.store.util.PackageUtil
|
||||||
import com.aurora.store.util.PathUtil
|
import com.aurora.store.util.PathUtil
|
||||||
@@ -57,14 +54,14 @@ import kotlinx.coroutines.launch
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
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 =
|
private val startForStorageManagerResult =
|
||||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
if (isRAndAbove() && Environment.isExternalStorageManager()) {
|
if (isRAndAbove() && Environment.isExternalStorageManager()) {
|
||||||
viewModel.download(app)
|
viewModel.download(update)
|
||||||
} else {
|
} else {
|
||||||
toast(R.string.permissions_denied)
|
toast(R.string.permissions_denied)
|
||||||
}
|
}
|
||||||
@@ -72,7 +69,7 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
|
|
||||||
private val startForPermissions =
|
private val startForPermissions =
|
||||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { perm ->
|
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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
@@ -102,7 +99,6 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
}
|
}
|
||||||
}.collectLatest { map ->
|
}.collectLatest { map ->
|
||||||
updateController(map)
|
updateController(map)
|
||||||
binding.swipeRefreshLayout.isRefreshing = false
|
|
||||||
viewModel.updateAllEnqueued =
|
viewModel.updateAllEnqueued =
|
||||||
map?.values?.all { it?.isRunning == true } ?: false
|
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 {
|
binding.swipeRefreshLayout.setOnRefreshListener {
|
||||||
viewModel.fetchUpdates()
|
viewModel.fetchUpdates()
|
||||||
}
|
}
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
AuroraApp.events.busEvent.collect { onEvent(it) }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onEvent(event: Event) {
|
private fun updateController(appList: Map<Update, Download?>?) {
|
||||||
when (event) {
|
|
||||||
is InstallerEvent.Installed, is InstallerEvent.Uninstalled -> {
|
|
||||||
viewModel.fetchUpdates()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateController(appList: Map<App, Download?>?) {
|
|
||||||
binding.recycler.withModels {
|
binding.recycler.withModels {
|
||||||
setFilterDuplicates(true)
|
setFilterDuplicates(true)
|
||||||
if (appList == null) {
|
if (appList == null) {
|
||||||
@@ -175,6 +164,9 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
.id("no_update")
|
.id("no_update")
|
||||||
.icon(R.drawable.ic_updates)
|
.icon(R.drawable.ic_updates)
|
||||||
.message(getString(R.string.details_no_updates))
|
.message(getString(R.string.details_no_updates))
|
||||||
|
.showAction(true)
|
||||||
|
.actionMessage(getString(R.string.check_updates))
|
||||||
|
.actionCallback { _ -> viewModel.fetchUpdates() }
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
add(
|
add(
|
||||||
@@ -193,25 +185,21 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
for ((app, download) in appList) {
|
for ((update, download) in appList) {
|
||||||
add(
|
add(
|
||||||
AppUpdateViewModel_()
|
AppUpdateViewModel_()
|
||||||
.id(app.packageName)
|
.id(update.packageName)
|
||||||
.app(app)
|
.update(update)
|
||||||
.download(download)
|
.download(download)
|
||||||
.click { _ ->
|
.click { _ ->
|
||||||
if (app.packageName == Constants.APP_ID) {
|
if (update.packageName == Constants.APP_ID) {
|
||||||
requireContext().browse(Constants.GITLAB_URL)
|
requireContext().browse(Constants.GITLAB_URL)
|
||||||
} else {
|
} else {
|
||||||
openDetailsFragment(app.packageName, app)
|
openDetailsFragment(update.packageName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.longClick { _ ->
|
.positiveAction { _ -> updateSingle(update) }
|
||||||
openAppMenuSheet(app)
|
.negativeAction { _ -> cancelSingle(update) }
|
||||||
false
|
|
||||||
}
|
|
||||||
.positiveAction { _ -> updateSingle(app) }
|
|
||||||
.negativeAction { _ -> cancelSingle(app) }
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -219,18 +207,18 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSingle(app: App, updateAll: Boolean = false) {
|
private fun updateSingle(update: Update, updateAll: Boolean = false) {
|
||||||
this.app = app
|
this.update = update
|
||||||
viewModel.updateAllEnqueued = updateAll
|
viewModel.updateAllEnqueued = updateAll
|
||||||
|
|
||||||
if (PathUtil.needsStorageManagerPerm(app.fileList)) {
|
if (PathUtil.needsStorageManagerPerm(update.fileList)) {
|
||||||
if (isRAndAbove()) {
|
if (isRAndAbove()) {
|
||||||
if (!Environment.isExternalStorageManager()) {
|
if (!Environment.isExternalStorageManager()) {
|
||||||
startForStorageManagerResult.launch(
|
startForStorageManagerResult.launch(
|
||||||
PackageUtil.getStorageManagerIntent(requireContext())
|
PackageUtil.getStorageManagerIntent(requireContext())
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
viewModel.download(app)
|
viewModel.download(update)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (ContextCompat.checkSelfPermission(
|
if (ContextCompat.checkSelfPermission(
|
||||||
@@ -238,18 +226,18 @@ class UpdatesFragment : BaseFragment<FragmentUpdatesBinding>() {
|
|||||||
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
Manifest.permission.WRITE_EXTERNAL_STORAGE
|
||||||
) == PackageManager.PERMISSION_GRANTED
|
) == PackageManager.PERMISSION_GRANTED
|
||||||
) {
|
) {
|
||||||
viewModel.download(app)
|
viewModel.download(update)
|
||||||
} else {
|
} else {
|
||||||
startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
startForPermissions.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
viewModel.download(app)
|
viewModel.download(update)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelSingle(app: App) {
|
private fun cancelSingle(update: Update) {
|
||||||
viewModel.cancelDownload(app)
|
viewModel.cancelDownload(update.packageName)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelAll() {
|
private fun cancelAll() {
|
||||||
|
|||||||
@@ -19,16 +19,13 @@
|
|||||||
|
|
||||||
package com.aurora.store.viewmodel.all
|
package com.aurora.store.viewmodel.all
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
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.data.providers.AuthProvider
|
|
||||||
import com.aurora.store.util.AppUtil
|
import com.aurora.store.util.AppUtil
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -37,10 +34,7 @@ import java.util.Locale
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class InstalledViewModel @Inject constructor(
|
class InstalledViewModel @Inject constructor(private val appUtil: AppUtil) : ViewModel() {
|
||||||
@ApplicationContext private val context: Context,
|
|
||||||
private val authProvider: AuthProvider
|
|
||||||
) : ViewModel() {
|
|
||||||
|
|
||||||
private val TAG = InstalledViewModel::class.java.simpleName
|
private val TAG = InstalledViewModel::class.java.simpleName
|
||||||
|
|
||||||
@@ -56,7 +50,7 @@ class InstalledViewModel @Inject constructor(
|
|||||||
fun fetchApps() {
|
fun fetchApps() {
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
val apps = AppUtil.getFilteredInstalledApps(context, authProvider.authData!!)
|
val apps = appUtil.getFilteredInstalledApps()
|
||||||
_installedApps.emit(apps.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
|
_installedApps.emit(apps.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
Log.e(TAG, "Failed to get installed apps", exception)
|
Log.e(TAG, "Failed to get installed apps", exception)
|
||||||
|
|||||||
@@ -20,76 +20,52 @@
|
|||||||
package com.aurora.store.viewmodel.all
|
package com.aurora.store.viewmodel.all
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.aurora.gplayapi.data.models.App
|
import com.aurora.store.data.room.update.Update
|
||||||
import com.aurora.store.data.providers.AuthProvider
|
|
||||||
import com.aurora.store.util.AppUtil
|
import com.aurora.store.util.AppUtil
|
||||||
import com.aurora.store.util.DownloadWorkerUtil
|
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.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.supervisorScope
|
|
||||||
import java.util.Locale
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
|
||||||
class UpdatesViewModel @Inject constructor(
|
class UpdatesViewModel @Inject constructor(
|
||||||
@ApplicationContext private val context: Context,
|
private val appUtil: AppUtil,
|
||||||
private val gson: Gson,
|
|
||||||
private val downloadWorkerUtil: DownloadWorkerUtil,
|
private val downloadWorkerUtil: DownloadWorkerUtil,
|
||||||
private val authProvider: AuthProvider
|
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val TAG = UpdatesViewModel::class.java.simpleName
|
private val TAG = UpdatesViewModel::class.java.simpleName
|
||||||
|
|
||||||
var updateAllEnqueued: Boolean = false
|
var updateAllEnqueued: Boolean = false
|
||||||
|
|
||||||
private val _updates = MutableStateFlow<List<App>?>(null)
|
|
||||||
val updates = _updates.asStateFlow()
|
|
||||||
|
|
||||||
val downloadsList get() = downloadWorkerUtil.downloadsList
|
val downloadsList get() = downloadWorkerUtil.downloadsList
|
||||||
|
val updates get() = appUtil.updates
|
||||||
|
|
||||||
init {
|
private val _fetchingUpdates = MutableStateFlow(false)
|
||||||
fetchUpdates()
|
val fetchingUpdates = _fetchingUpdates.asStateFlow()
|
||||||
}
|
|
||||||
|
|
||||||
fun fetchUpdates() {
|
fun fetchUpdates() {
|
||||||
|
_fetchingUpdates.value = true
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
supervisorScope {
|
try {
|
||||||
try {
|
appUtil.checkUpdates()
|
||||||
val isExtendedUpdateEnabled = Preferences.getBoolean(
|
} catch (exception: Exception) {
|
||||||
context, Preferences.PREFERENCE_UPDATES_EXTENDED
|
Log.d(TAG, "Failed to get updates", exception)
|
||||||
)
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}.invokeOnCompletion { _fetchingUpdates.value = false }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun download(app: App) {
|
fun download(update: Update) {
|
||||||
viewModelScope.launch { downloadWorkerUtil.enqueueApp(app) }
|
viewModelScope.launch { downloadWorkerUtil.enqueueUpdate(update) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelDownload(app: App) {
|
fun cancelDownload(packageName: String) {
|
||||||
viewModelScope.launch { downloadWorkerUtil.cancelDownload(app.packageName) }
|
viewModelScope.launch { downloadWorkerUtil.cancelDownload(packageName) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelAll() {
|
fun cancelAll() {
|
||||||
|
|||||||
@@ -28,7 +28,8 @@
|
|||||||
android:layout_width="@dimen/icon_size"
|
android:layout_width="@dimen/icon_size"
|
||||||
android:layout_height="@dimen/icon_size"
|
android:layout_height="@dimen/icon_size"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
app:tint="?android:textColorPrimary" />
|
app:tint="?android:textColorPrimary"
|
||||||
|
tools:src="@drawable/ic_updates" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/txt"
|
android:id="@+id/txt"
|
||||||
@@ -36,7 +37,16 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/img"
|
android:layout_below="@id/img"
|
||||||
android:layout_centerInParent="true"
|
android:layout_centerInParent="true"
|
||||||
android:layout_marginTop="@dimen/margin_small"
|
android:layout_marginVertical="@dimen/margin_small"
|
||||||
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
android:textAppearance="@style/TextAppearance.Aurora.Line1"
|
||||||
tools:text="No updates available" />
|
tools:text="No updates available" />
|
||||||
</RelativeLayout>
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/txt"
|
||||||
|
android:layout_centerInParent="true"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:text="@string/check_updates" />
|
||||||
|
</RelativeLayout>
|
||||||
|
|||||||
@@ -469,4 +469,7 @@
|
|||||||
<!-- LogoutDialog -->
|
<!-- LogoutDialog -->
|
||||||
<string name="action_logout_confirmation_title">Log out?</string>
|
<string name="action_logout_confirmation_title">Log out?</string>
|
||||||
<string name="action_logout_confirmation_message">Are you sure you want to log out?</string>
|
<string name="action_logout_confirmation_message">Are you sure you want to log out?</string>
|
||||||
|
|
||||||
|
<!-- UpdatesFragment -->
|
||||||
|
<string name="check_updates">Check updates</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user