Improve - Installed Apps, Updates & Blaclists

This commit is contained in:
Rahul Patel
2024-06-29 07:02:57 +05:30
committed by Aayush Gupta
parent a43779ed79
commit a2fef6a897
8 changed files with 146 additions and 138 deletions

View File

@@ -4,7 +4,7 @@ import android.content.pm.PackageInfo
import android.os.Process
fun PackageInfo.isApp(): Boolean {
if (this.applicationInfo == null) return false
if (this.applicationInfo == null || this.packageName.isEmpty()) return false
return when {
isQAndAbove() -> {

View File

@@ -28,8 +28,8 @@ import coil.transform.RoundedCornersTransformation
import com.airbnb.epoxy.CallbackProp
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.aurora.gplayapi.data.models.App
import com.aurora.store.R
import com.aurora.store.data.model.Black
import com.aurora.store.databinding.ViewBlackBinding
@ModelView(
@@ -62,15 +62,15 @@ class BlackListView : RelativeLayout {
}
@ModelProp
fun black(black: Black) {
B.imgIcon.load(black.drawable) {
fun app(app: App) {
B.imgIcon.load(app.iconArtwork.url) {
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(25F))
}
B.txtLine1.text = black.displayName
B.txtLine2.text = black.packageName
B.txtLine3.text = ("${black.versionName}.${black.versionCode}")
B.txtLine1.text = app.displayName
B.txtLine2.text = app.packageName
B.txtLine3.text = ("${app.versionName}.${app.versionCode}")
}
@ModelProp

View File

@@ -22,7 +22,7 @@ package com.aurora.store.view.ui.all
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.fragment.app.viewModels
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.AuroraApp
@@ -46,7 +46,7 @@ class InstalledAppsFragment : BaseFragment(R.layout.fragment_apps) {
private val binding: FragmentAppsBinding
get() = _binding!!
private val viewModel: InstalledViewModel by viewModels()
private val viewModel: InstalledViewModel by activityViewModels()
companion object {
@JvmStatic
@@ -60,7 +60,7 @@ class InstalledAppsFragment : BaseFragment(R.layout.fragment_apps) {
is BusEvent.InstallEvent,
is BusEvent.UninstallEvent,
is BusEvent.Blacklisted -> {
viewModel.getInstalledApps(requireContext())
viewModel.fetchApps()
}
else -> {
@@ -73,14 +73,12 @@ class InstalledAppsFragment : BaseFragment(R.layout.fragment_apps) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentAppsBinding.bind(view)
viewModel.liveData.observe(viewLifecycleOwner) {
updateController(it)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.installedApps.collect {
updateController(it)
}
}
updateController(null)
viewModel.getInstalledApps(view.context)
viewLifecycleOwner.lifecycleScope.launch {
AuroraApp.flowEvent.busEvent.collect { onEvent(it) }
}

View File

@@ -22,16 +22,18 @@ package com.aurora.store.view.ui.commons
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import com.aurora.gplayapi.data.models.App
import com.aurora.store.R
import com.aurora.store.data.model.Black
import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.databinding.ActivityGenericRecyclerBinding
import com.aurora.store.view.epoxy.views.BlackListViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.viewmodel.all.BlacklistViewModel
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint
class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
@@ -40,7 +42,7 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
private val binding: ActivityGenericRecyclerBinding
get() = _binding!!
private val viewModel: BlacklistViewModel by viewModels()
private val viewModel: BlacklistViewModel by activityViewModels()
private lateinit var blacklistProvider: BlacklistProvider
@@ -48,12 +50,12 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
super.onViewCreated(view, savedInstanceState)
_binding = ActivityGenericRecyclerBinding.bind(view)
blacklistProvider = BlacklistProvider.with(view.context)
blacklistProvider = BlacklistProvider.with(requireContext())
viewModel.liveData.observe(viewLifecycleOwner) {
updateController(it.sortedByDescending { app ->
blacklistProvider.isBlacklisted(app.packageName)
})
viewLifecycleOwner.lifecycleScope.launch {
viewModel.blackListedApps.collect {
updateController(it)
}
}
// Toolbar
@@ -61,8 +63,6 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
binding.layoutToolbarAction.imgActionPrimary.setOnClickListener {
findNavController().navigateUp()
}
updateController(null)
}
override fun onPause() {
@@ -75,7 +75,7 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
_binding = null
}
private fun updateController(blackList: List<Black>?) {
private fun updateController(blackList: List<App>?) {
binding.recycler.withModels {
setFilterDuplicates(true)
if (blackList == null) {
@@ -86,22 +86,24 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
)
}
} else {
blackList.forEach {
add(
BlackListViewModel_()
.id(it.packageName.hashCode())
.black(it)
.markChecked(viewModel.selected.contains(it.packageName))
.checked { _, isChecked ->
if (isChecked)
viewModel.selected.add(it.packageName)
else
viewModel.selected.remove(it.packageName)
blackList
.sortedByDescending { app -> blacklistProvider.isBlacklisted(app.packageName) }
.forEach {
add(
BlackListViewModel_()
.id(it.packageName.hashCode())
.app(it)
.markChecked(viewModel.selected.contains(it.packageName))
.checked { _, isChecked ->
if (isChecked)
viewModel.selected.add(it.packageName)
else
viewModel.selected.remove(it.packageName)
requestModelBuild()
}
)
}
requestModelBuild()
}
)
}
}
}
}

View File

@@ -97,52 +97,53 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
true
}
viewModel.observe()
viewLifecycleOwner.lifecycleScope.launch {
viewModel.updates.combine(viewModel.downloadsList) { uList, dList ->
uList?.associateWith { a ->
dList.find { it.packageName == a.packageName && it.versionCode == a.versionCode }
}
}.collectLatest { map ->
updateController(map)
binding.swipeRefreshLayout.isRefreshing = false
viewModel.updateAllEnqueued = map?.values?.all { it?.isRunning == true } ?: false
if (!map.isNullOrEmpty()) {
binding.updateFab.apply {
visibility = View.VISIBLE
if (viewModel.updateAllEnqueued) {
setImageDrawable(
ContextCompat.getDrawable(
requireContext(),
R.drawable.ic_cancel
)
)
} else {
setImageDrawable(
ContextCompat.getDrawable(
requireContext(),
R.drawable.ic_installation
)
)
}
setOnClickListener {
if (viewModel.updateAllEnqueued) {
cancelAll()
} else {
map.keys.forEach { updateSingle(it, true) }
}
binding.recycler.requestModelBuild()
}
viewModel.updates
.combine(viewModel.downloadsList) { uList, dList ->
uList?.associateWith { a ->
dList.find { it.packageName == a.packageName && it.versionCode == a.versionCode }
}
}.collectLatest { map ->
updateController(map)
binding.swipeRefreshLayout.isRefreshing = false
viewModel.updateAllEnqueued =
map?.values?.all { it?.isRunning == true } ?: false
if (!map.isNullOrEmpty()) {
binding.updateFab.apply {
visibility = View.VISIBLE
if (viewModel.updateAllEnqueued) {
setImageDrawable(
ContextCompat.getDrawable(
requireContext(),
R.drawable.ic_cancel
)
)
} else {
setImageDrawable(
ContextCompat.getDrawable(
requireContext(),
R.drawable.ic_installation
)
)
}
setOnClickListener {
if (viewModel.updateAllEnqueued) {
cancelAll()
} else {
map.keys.forEach { updateSingle(it, true) }
}
binding.recycler.requestModelBuild()
}
}
} else {
binding.updateFab.visibility = View.GONE
}
} else {
binding.updateFab.visibility = View.GONE
}
}
}
binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.observe(true)
viewModel.fetchUpdates()
}
viewLifecycleOwner.lifecycleScope.launch {
@@ -158,7 +159,7 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
private fun onEvent(event: BusEvent) {
when (event) {
is BusEvent.InstallEvent, is BusEvent.UninstallEvent -> {
viewModel.observe(true)
viewModel.fetchUpdates()
}
else -> {}

View File

@@ -21,18 +21,19 @@ package com.aurora.store.viewmodel.all
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.pm.PackageInfoCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.extensions.isApp
import com.aurora.store.data.model.Black
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.AppDetailsHelper
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.util.PackageUtil
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
@@ -41,40 +42,36 @@ import javax.inject.Inject
@HiltViewModel
@SuppressLint("StaticFieldLeak") // false positive, see https://github.com/google/dagger/issues/3253
class BlacklistViewModel @Inject constructor(
@ApplicationContext private val context: Context
@ApplicationContext private val context: Context,
authProvider: AuthProvider
) : ViewModel() {
private val packageManager: PackageManager = context.packageManager
private val blacklistProvider: BlacklistProvider = BlacklistProvider.with(context)
private val appDetailsHelper = AppDetailsHelper(authProvider.authData)
.using(HttpClient.getPreferredClient(context))
private val _blacklistedApps = MutableStateFlow<List<App>?>(null)
val blackListedApps = _blacklistedApps.asStateFlow()
var blackList: MutableList<Black> = mutableListOf()
var selected: MutableSet<String> = mutableSetOf()
val liveData: MutableLiveData<List<Black>> = MutableLiveData()
init {
selected = blacklistProvider.getBlackList()
observe()
fetchApps()
}
fun observe() {
private fun fetchApps() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
val packageInfoMap = PackageUtil.getPackageInfoMap(context)
packageInfoMap.values
val packageNames = context.packageManager.getInstalledPackages(0)
.filter { it.isApp() }
.forEach {
val black = Black(it.packageName).apply {
displayName = packageManager.getApplicationLabel(it.applicationInfo)
.toString()
versionCode = PackageInfoCompat.getLongVersionCode(it)
versionName = it.versionName
drawable = packageManager.getApplicationIcon(packageName)
}
blackList.add(black)
}
liveData.postValue(blackList.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
.map { it.packageName }
val apps = appDetailsHelper
.getAppByPackageName(packageNames)
.filter { it.displayName.isNotBlank() }
_blacklistedApps.emit(apps.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
} catch (e: Exception) {
e.printStackTrace()
}

View File

@@ -28,26 +28,40 @@ 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
import kotlinx.coroutines.launch
import java.util.Locale
import javax.inject.Inject
@HiltViewModel
class InstalledViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val authProvider: AuthProvider
) : ViewModel() {
private val TAG = InstalledViewModel::class.java.simpleName
private var appList: MutableList<App> = mutableListOf()
private val _installedApps = MutableStateFlow<List<App>?>(null)
val installedApps = _installedApps.asStateFlow()
val liveData: MutableLiveData<List<App>> = MutableLiveData()
fun getInstalledApps(context: Context) {
init {
fetchApps()
}
fun fetchApps() {
viewModelScope.launch(Dispatchers.IO) {
try {
appList = AppUtil.getFilteredInstalledApps(context, authProvider.authData).toMutableList()
liveData.postValue(appList.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
val apps = AppUtil.getFilteredInstalledApps(
context,
authProvider.authData
)
_installedApps.emit(apps.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
} catch (exception: Exception) {
Log.e(TAG, "Failed to get installed apps", exception)
}

View File

@@ -33,11 +33,10 @@ 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.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope
import java.util.Locale
import javax.inject.Inject
@@ -56,34 +55,31 @@ class UpdatesViewModel @Inject constructor(
private val _updates = MutableStateFlow<List<App>?>(null)
val updates = _updates.asStateFlow()
val stash: MutableList<App> = mutableListOf()
val downloadsList get() = downloadWorkerUtil.downloadsList
fun observe(forceRefresh: Boolean = false) {
init {
fetchUpdates()
}
fun fetchUpdates() {
viewModelScope.launch(Dispatchers.IO) {
if (stash.isNotEmpty() && !forceRefresh) {
_updates.emit(stash)
}
supervisorScope {
try {
val isExtendedUpdateEnabled = Preferences.getBoolean(
context, Preferences.PREFERENCE_UPDATES_EXTENDED
)
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()) }
val updates = AppUtil.getUpdatableApps(
context,
authProvider.authData,
gson,
!isExtendedUpdateEnabled
).sortedBy { it.displayName.lowercase(Locale.getDefault()) }
stash.clear()
stash.addAll(updates)
_updates.emit(updates)
} catch (exception: Exception) {
Log.d(TAG, "Failed to get updates", exception)
_updates.emit(updates)
} catch (exception: Exception) {
Log.d(TAG, "Failed to get updates", exception)
}
}
}
}