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 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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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_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)
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user