Update: Migrate ignoring updates logic to Extras

This commit is contained in:
machiav3lli 2022-06-23 00:52:53 +02:00
parent bed6c203a1
commit 9941354bcb
6 changed files with 129 additions and 119 deletions

View File

@ -15,7 +15,6 @@ import coil.ImageLoader
import coil.ImageLoaderFactory
import com.looker.droidify.content.Cache
import com.looker.droidify.content.Preferences
import com.looker.droidify.content.ProductPreferences
import com.looker.droidify.database.DatabaseX
import com.looker.droidify.index.RepositoryUpdater
import com.looker.droidify.network.CoilDownloader
@ -45,7 +44,6 @@ class MainApplication : Application(), ImageLoaderFactory {
db = DatabaseX.getInstance(applicationContext)
Preferences.init(this)
ProductPreferences.init(this)
RepositoryUpdater.init(this)
listenApplications()
listenPreferences()

View File

@ -38,8 +38,6 @@ import com.looker.droidify.ROW_UPDATED
import com.looker.droidify.ROW_VERSION_CODE
import com.looker.droidify.TABLE_CATEGORY
import com.looker.droidify.TABLE_CATEGORY_NAME
import com.looker.droidify.TABLE_IGNORED
import com.looker.droidify.TABLE_IGNORED_NAME
import com.looker.droidify.TABLE_EXTRAS
import com.looker.droidify.TABLE_EXTRAS_NAME
import com.looker.droidify.TABLE_INSTALLED
@ -199,8 +197,8 @@ interface ProductDao : BaseDao<Product> {
builder += """SELECT $TABLE_PRODUCT.rowid AS $ROW_ID, $TABLE_PRODUCT.$ROW_REPOSITORY_ID,
$TABLE_PRODUCT.$ROW_PACKAGE_NAME, $TABLE_PRODUCT.$ROW_LABEL,
$TABLE_PRODUCT.$ROW_SUMMARY, $TABLE_PRODUCT.$ROW_DESCRIPTION,
(COALESCE($TABLE_IGNORED.$ROW_VERSION_CODE, -1) NOT IN (0, $TABLE_PRODUCT.$ROW_VERSION_CODE) AND
$TABLE_PRODUCT.$ROW_COMPATIBLE != 0 AND
(COALESCE($TABLE_EXTRAS.$ROW_IGNORED_VERSION, -1) != $TABLE_PRODUCT.$ROW_VERSION_CODE AND
COALESCE($TABLE_EXTRAS.$ROW_IGNORE_UPDATES, 0) = 0 AND $TABLE_PRODUCT.$ROW_COMPATIBLE != 0 AND
$TABLE_PRODUCT.$ROW_VERSION_CODE > COALESCE($TABLE_INSTALLED.$ROW_VERSION_CODE, 0xffffffff) AND
$signatureMatches) AS $ROW_CAN_UPDATE, $TABLE_PRODUCT.$ROW_ICON,
$TABLE_PRODUCT.$ROW_METADATA_ICON, $TABLE_PRODUCT.$ROW_RELEASES, $TABLE_PRODUCT.$ROW_CATEGORIES,
@ -227,9 +225,9 @@ interface ProductDao : BaseDao<Product> {
builder += """JOIN $TABLE_REPOSITORY_NAME AS $TABLE_REPOSITORY
ON $TABLE_PRODUCT.$ROW_REPOSITORY_ID = $TABLE_REPOSITORY.$ROW_ID"""
// Merge the matching locks
builder += """LEFT JOIN $TABLE_IGNORED_NAME AS $TABLE_IGNORED
ON $TABLE_PRODUCT.$ROW_PACKAGE_NAME = $TABLE_IGNORED.$ROW_PACKAGE_NAME"""
// Merge the matching extras
builder += """LEFT JOIN $TABLE_EXTRAS_NAME AS $TABLE_EXTRAS
ON $TABLE_PRODUCT.$ROW_PACKAGE_NAME = $TABLE_EXTRAS.$ROW_PACKAGE_NAME"""
// Merge the matching installed
if (!installed && !updates) builder += "LEFT"

View File

@ -7,7 +7,7 @@ import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import com.looker.droidify.database.entity.Category
import com.looker.droidify.database.entity.CategoryTemp
import com.looker.droidify.database.entity.Ignored
import com.looker.droidify.database.entity.Extras
import com.looker.droidify.database.entity.Installed
import com.looker.droidify.database.entity.Product
import com.looker.droidify.database.entity.ProductTemp
@ -27,8 +27,8 @@ import kotlinx.coroutines.launch
Category::class,
CategoryTemp::class,
Installed::class,
Ignored::class
], version = 7
Extras::class
], version = 8
)
@TypeConverters(Converters::class)
abstract class DatabaseX : RoomDatabase() {
@ -39,7 +39,7 @@ abstract class DatabaseX : RoomDatabase() {
abstract val categoryDao: CategoryDao
abstract val categoryTempDao: CategoryTempDao
abstract val installedDao: InstalledDao
abstract val lockDao: LockDao
abstract val extrasDao: ExtrasDao
companion object {
@Volatile

View File

@ -3,12 +3,10 @@ package com.looker.droidify.ui.compose.utils
import android.net.Uri
import com.looker.droidify.database.entity.Release
import com.looker.droidify.entity.ActionState
import com.looker.droidify.entity.ProductPreference
import com.looker.droidify.entity.Screenshot
interface Callbacks {
fun onActionClick(action: ActionState?)
fun onPreferenceChanged(preference: ProductPreference)
fun onPermissionsClick(group: String?, permissions: List<String>)
fun onScreenshotClick(screenshot: Screenshot)
fun onReleaseClick(release: Release)

View File

@ -41,13 +41,12 @@ import com.looker.droidify.RELEASE_STATE_INSTALLED
import com.looker.droidify.RELEASE_STATE_NONE
import com.looker.droidify.RELEASE_STATE_SUGGESTED
import com.looker.droidify.content.Preferences
import com.looker.droidify.content.ProductPreferences
import com.looker.droidify.database.entity.Extras
import com.looker.droidify.database.entity.Release
import com.looker.droidify.entity.ActionState
import com.looker.droidify.entity.AntiFeature
import com.looker.droidify.entity.DonateType
import com.looker.droidify.entity.DownloadState
import com.looker.droidify.entity.ProductPreference
import com.looker.droidify.entity.Screenshot
import com.looker.droidify.installer.AppInstaller
import com.looker.droidify.network.CoilDownloader
@ -144,9 +143,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks {
override fun setupLayout() {
viewModel._productRepos.observe(this) {
lifecycleScope.launch {
updateButtons()
}
viewModel.updateActions()
}
}
@ -159,86 +156,6 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks {
downloadConnection.unbind(requireContext())
}
private suspend fun updateButtons() {
updateButtons(ProductPreferences[packageName])
}
// TODO rename to updateActions
private suspend fun updateButtons(preference: ProductPreference) =
withContext(Dispatchers.Default) {
val installed = viewModel.installedItem.value
val productRepos = viewModel.productRepos
val product = findSuggestedProduct(productRepos, installed) { it.first }?.first
val compatible = product != null && product.selectedReleases.firstOrNull()
.let { it != null && it.incompatibilities.isEmpty() }
val canInstall = product != null && installed == null && compatible
val canUpdate =
product != null && compatible && product.canUpdate(installed) &&
!preference.shouldIgnoreUpdate(product.versionCode)
val canUninstall = product != null && installed != null && !installed.isSystem
val canLaunch =
product != null && installed != null && installed.launcherActivities.isNotEmpty()
val canShare = product != null && productRepos[0].second.name == "F-Droid"
val actions = mutableSetOf<ActionState>()
launch {
if (canInstall) {
actions += ActionState.Install
}
}
launch {
if (canUpdate) {
actions += ActionState.Update
}
}
launch {
if (canLaunch) {
actions += ActionState.Launch
}
}
launch {
if (installed != null) {
actions += ActionState.Details
}
}
launch {
if (canUninstall) {
actions += ActionState.Uninstall
}
}
launch {
if (canShare) {
actions += ActionState.Share
}
}
// TODO prioritize actions set and choose the first for main others for extra actions
val primaryAction = when {
canUpdate -> ActionState.Update
canLaunch -> ActionState.Launch
canInstall -> ActionState.Install
canShare -> ActionState.Share
else -> null
}
val secondaryAction = when {
primaryAction != ActionState.Share && canShare -> ActionState.Share
primaryAction != ActionState.Launch && canLaunch -> ActionState.Launch
installed != null && canUninstall -> ActionState.Uninstall
else -> null
}
withContext(Dispatchers.Main) {
viewModel.actions.value = actions
if (viewModel.downloadState.value != null && viewModel.mainAction.value?.textId != viewModel.downloadState.value?.textId)
viewModel.downloadState.value?.let {
viewModel.mainAction.value = ActionState.Cancel(it.textId)
}
else if (viewModel.downloadState.value == null) // && viewModel.mainAction.value != primaryAction)
viewModel.mainAction.value = primaryAction
viewModel.secondaryAction.value = secondaryAction
}
}
private suspend fun updateDownloadState(downloadState: DownloadService.State?) {
val state = when (downloadState) {
is DownloadService.State.Pending -> DownloadState.Pending
@ -250,8 +167,8 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks {
else -> null
}
viewModel.downloadState.value = state
updateButtons()
if (downloadState is DownloadService.State.Success && isResumed && !rootInstallerEnabled) {
viewModel.updateActions()
withContext(Dispatchers.Default) {
AppInstaller.getInstance(context)?.defaultInstaller?.install(downloadState.release.cacheFileName)
}
@ -325,10 +242,6 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks {
startActivity(Intent.createChooser(shareIntent, "Where to Send?"))
}
override fun onPreferenceChanged(preference: ProductPreference) {
lifecycleScope.launch { updateButtons(preference) }
}
override fun onPermissionsClick(group: String?, permissions: List<String>) {
MessageDialog(MessageDialog.Message.Permissions(group, permissions)).show(
childFragmentManager
@ -422,6 +335,7 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks {
val mainAction by viewModel.mainAction.observeAsState(if (installed == null) ActionState.Install else ActionState.Launch)
val actions by viewModel.actions.observeAsState() // TODO add rest actions to UI
val secondaryAction by viewModel.secondaryAction.observeAsState()
val extras by viewModel.extras.observeAsState(Extras(packageName))
val productRepos = products?.mapNotNull { product ->
repos?.firstOrNull { it.id == product.repositoryId }
?.let { Pair(product, it) }
@ -515,27 +429,23 @@ class AppSheetX() : FullscreenBottomSheetDialogFragment(), Callbacks {
item {
AnimatedVisibility(visible = product.canUpdate(installed)) {
SwitchPreference(text = stringResource(id = R.string.ignore_this_update),
initSelected = ProductPreferences[product.packageName].ignoreVersionCode == product.versionCode,
initSelected = extras?.ignoredVersion == product.versionCode,
onCheckedChanged = {
ProductPreferences[product.packageName].let {
it.copy(
ignoreVersionCode =
if (it.ignoreVersionCode == product.versionCode) 0 else product.versionCode
)
}
viewModel.setIgnoredVersion(
product.packageName,
if (it) product.versionCode else 0
)
viewModel.updateActions()
})
}
}
item {
AnimatedVisibility(visible = installed != null) {
SwitchPreference(text = stringResource(id = R.string.ignore_all_updates),
initSelected = ProductPreferences[product.packageName].ignoreVersionCode == product.versionCode,
initSelected = extras?.ignoreUpdates == true,
onCheckedChanged = {
ProductPreferences[product.packageName].let {
it.copy(
ignoreUpdates = !it.ignoreUpdates
)
}
viewModel.setIgnoreUpdates(product.packageName, it)
viewModel.updateActions()
})
}
}

View File

@ -4,12 +4,18 @@ import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.looker.droidify.database.DatabaseX
import com.looker.droidify.database.entity.Extras
import com.looker.droidify.database.entity.Installed
import com.looker.droidify.database.entity.Product
import com.looker.droidify.database.entity.Repository
import com.looker.droidify.entity.ActionState
import com.looker.droidify.entity.DownloadState
import com.looker.droidify.utility.findSuggestedProduct
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class AppViewModelX(val db: DatabaseX, val packageName: String) : ViewModel() {
@ -26,11 +32,111 @@ class AppViewModelX(val db: DatabaseX, val packageName: String) : ViewModel() {
val mainAction = MutableLiveData<ActionState>()
val actions = MutableLiveData<Set<ActionState>>()
val secondaryAction = MutableLiveData<ActionState>()
val extras = MediatorLiveData<Extras>()
init {
products.addSource(db.productDao.getLive(packageName)) { products.setValue(it.filterNotNull()) }
repositories.addSource(db.repositoryDao.allLive, repositories::setValue)
installedItem.addSource(db.installedDao.getLive(packageName), installedItem::setValue)
extras.addSource(db.extrasDao.getLive(packageName), extras::setValue)
}
fun updateActions() {
viewModelScope.launch {
updateUI()
}
}
private suspend fun updateUI() {
withContext(Dispatchers.IO) {
val installed = installedItem.value
val productRepos = productRepos
val product = findSuggestedProduct(productRepos, installed) { it.first }?.first
val compatible = product != null && product.selectedReleases.firstOrNull()
.let { it != null && it.incompatibilities.isEmpty() }
val canInstall = product != null && installed == null && compatible
val canUpdate =
product != null && compatible && product.canUpdate(installed) &&
!shouldIgnore(product.versionCode)
val canUninstall = product != null && installed != null && !installed.isSystem
val canLaunch =
product != null && installed != null && installed.launcherActivities.isNotEmpty()
val canShare = product != null && productRepos[0].second.name == "F-Droid"
val bookmarked = extras.value?.favorite
val actions = mutableSetOf<ActionState>()
launch {
if (canInstall) actions += ActionState.Install
if (canUpdate) actions += ActionState.Update
if (canLaunch) actions += ActionState.Launch
if (installed != null) actions += ActionState.Details
if (canUninstall) actions += ActionState.Uninstall
if (canShare) actions += ActionState.Share
if (bookmarked == true) actions += ActionState.Bookmarked
else actions += ActionState.Bookmark
}
val primaryAction = when {
canUpdate -> ActionState.Update
canLaunch -> ActionState.Launch
canInstall -> ActionState.Install
canShare -> ActionState.Share
else -> null
}
val secondaryAction = when {
primaryAction != ActionState.Share && canShare -> ActionState.Share
primaryAction != ActionState.Launch && canLaunch -> ActionState.Launch
installed != null && canUninstall -> ActionState.Uninstall
else -> null
}
withContext(Dispatchers.Main) {
this@AppViewModelX.actions.value = actions
if (downloadState.value != null && mainAction.value?.textId != downloadState.value?.textId)
downloadState.value?.let {
mainAction.value = ActionState.Cancel(it.textId)
}
else if (downloadState.value == null)
mainAction.value = primaryAction
this@AppViewModelX.secondaryAction.value = secondaryAction
}
}
}
fun shouldIgnore(appVersionCode: Long): Boolean =
extras.value?.ignoredVersion == appVersionCode && extras.value?.ignoreUpdates != false
fun setIgnoredVersion(packageName: String, versionCode: Long) {
viewModelScope.launch {
saveIgnoredVersion(packageName, versionCode)
}
}
private suspend fun saveIgnoredVersion(packageName: String, versionCode: Long) {
withContext(Dispatchers.IO) {
val oldValue = db.extrasDao[packageName]
if (oldValue != null) db.extrasDao
.insertReplace(oldValue.copy(ignoredVersion = versionCode))
else db.extrasDao
.insertReplace(Extras(packageName, ignoredVersion = versionCode))
}
}
fun setIgnoreUpdates(packageName: String, setBoolean: Boolean) {
viewModelScope.launch {
saveIgnoreUpdates(packageName, setBoolean)
}
}
private suspend fun saveIgnoreUpdates(packageName: String, setBoolean: Boolean) {
withContext(Dispatchers.IO) {
val oldValue = db.extrasDao[packageName]
if (oldValue != null) db.extrasDao
.insertReplace(oldValue.copy(ignoreUpdates = setBoolean))
else db.extrasDao
.insertReplace(Extras(packageName, ignoreUpdates = setBoolean))
}
}
class Factory(val db: DatabaseX, val packageName: String) : ViewModelProvider.Factory {