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 import android.os.Process
fun PackageInfo.isApp(): Boolean { fun PackageInfo.isApp(): Boolean {
if (this.applicationInfo == null) return false if (this.applicationInfo == null || this.packageName.isEmpty()) return false
return when { return when {
isQAndAbove() -> { isQAndAbove() -> {

View File

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

View File

@@ -22,7 +22,7 @@ package com.aurora.store.view.ui.all
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import androidx.fragment.app.viewModels 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
@@ -46,7 +46,7 @@ class InstalledAppsFragment : BaseFragment(R.layout.fragment_apps) {
private val binding: FragmentAppsBinding private val binding: FragmentAppsBinding
get() = _binding!! get() = _binding!!
private val viewModel: InstalledViewModel by viewModels() private val viewModel: InstalledViewModel by activityViewModels()
companion object { companion object {
@JvmStatic @JvmStatic
@@ -60,7 +60,7 @@ class InstalledAppsFragment : BaseFragment(R.layout.fragment_apps) {
is BusEvent.InstallEvent, is BusEvent.InstallEvent,
is BusEvent.UninstallEvent, is BusEvent.UninstallEvent,
is BusEvent.Blacklisted -> { is BusEvent.Blacklisted -> {
viewModel.getInstalledApps(requireContext()) viewModel.fetchApps()
} }
else -> { else -> {
@@ -73,14 +73,12 @@ class InstalledAppsFragment : BaseFragment(R.layout.fragment_apps) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = FragmentAppsBinding.bind(view) _binding = FragmentAppsBinding.bind(view)
viewModel.liveData.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.launch {
updateController(it) viewModel.installedApps.collect {
updateController(it)
}
} }
updateController(null)
viewModel.getInstalledApps(view.context)
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
AuroraApp.flowEvent.busEvent.collect { onEvent(it) } 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.os.Bundle
import android.view.View import android.view.View
import androidx.fragment.app.Fragment 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 androidx.navigation.fragment.findNavController
import com.aurora.gplayapi.data.models.App
import com.aurora.store.R import com.aurora.store.R
import com.aurora.store.data.model.Black
import com.aurora.store.data.providers.BlacklistProvider import com.aurora.store.data.providers.BlacklistProvider
import com.aurora.store.databinding.ActivityGenericRecyclerBinding import com.aurora.store.databinding.ActivityGenericRecyclerBinding
import com.aurora.store.view.epoxy.views.BlackListViewModel_ import com.aurora.store.view.epoxy.views.BlackListViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.viewmodel.all.BlacklistViewModel import com.aurora.store.viewmodel.all.BlacklistViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
@AndroidEntryPoint @AndroidEntryPoint
class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) { class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
@@ -40,7 +42,7 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
private val binding: ActivityGenericRecyclerBinding private val binding: ActivityGenericRecyclerBinding
get() = _binding!! get() = _binding!!
private val viewModel: BlacklistViewModel by viewModels() private val viewModel: BlacklistViewModel by activityViewModels()
private lateinit var blacklistProvider: BlacklistProvider private lateinit var blacklistProvider: BlacklistProvider
@@ -48,12 +50,12 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
_binding = ActivityGenericRecyclerBinding.bind(view) _binding = ActivityGenericRecyclerBinding.bind(view)
blacklistProvider = BlacklistProvider.with(view.context) blacklistProvider = BlacklistProvider.with(requireContext())
viewModel.liveData.observe(viewLifecycleOwner) { viewLifecycleOwner.lifecycleScope.launch {
updateController(it.sortedByDescending { app -> viewModel.blackListedApps.collect {
blacklistProvider.isBlacklisted(app.packageName) updateController(it)
}) }
} }
// Toolbar // Toolbar
@@ -61,8 +63,6 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
binding.layoutToolbarAction.imgActionPrimary.setOnClickListener { binding.layoutToolbarAction.imgActionPrimary.setOnClickListener {
findNavController().navigateUp() findNavController().navigateUp()
} }
updateController(null)
} }
override fun onPause() { override fun onPause() {
@@ -75,7 +75,7 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
_binding = null _binding = null
} }
private fun updateController(blackList: List<Black>?) { private fun updateController(blackList: List<App>?) {
binding.recycler.withModels { binding.recycler.withModels {
setFilterDuplicates(true) setFilterDuplicates(true)
if (blackList == null) { if (blackList == null) {
@@ -86,22 +86,24 @@ class BlacklistFragment : Fragment(R.layout.activity_generic_recycler) {
) )
} }
} else { } else {
blackList.forEach { blackList
add( .sortedByDescending { app -> blacklistProvider.isBlacklisted(app.packageName) }
BlackListViewModel_() .forEach {
.id(it.packageName.hashCode()) add(
.black(it) BlackListViewModel_()
.markChecked(viewModel.selected.contains(it.packageName)) .id(it.packageName.hashCode())
.checked { _, isChecked -> .app(it)
if (isChecked) .markChecked(viewModel.selected.contains(it.packageName))
viewModel.selected.add(it.packageName) .checked { _, isChecked ->
else if (isChecked)
viewModel.selected.remove(it.packageName) 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 true
} }
viewModel.observe()
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewModel.updates.combine(viewModel.downloadsList) { uList, dList -> viewModel.updates
uList?.associateWith { a -> .combine(viewModel.downloadsList) { uList, dList ->
dList.find { it.packageName == a.packageName && it.versionCode == a.versionCode } uList?.associateWith { a ->
} dList.find { it.packageName == a.packageName && it.versionCode == a.versionCode }
}.collectLatest { map -> }
updateController(map) }.collectLatest { map ->
binding.swipeRefreshLayout.isRefreshing = false updateController(map)
viewModel.updateAllEnqueued = map?.values?.all { it?.isRunning == true } ?: false binding.swipeRefreshLayout.isRefreshing = false
viewModel.updateAllEnqueued =
if (!map.isNullOrEmpty()) { map?.values?.all { it?.isRunning == true } ?: false
binding.updateFab.apply {
visibility = View.VISIBLE if (!map.isNullOrEmpty()) {
if (viewModel.updateAllEnqueued) { binding.updateFab.apply {
setImageDrawable( visibility = View.VISIBLE
ContextCompat.getDrawable( if (viewModel.updateAllEnqueued) {
requireContext(), setImageDrawable(
R.drawable.ic_cancel ContextCompat.getDrawable(
) requireContext(),
) R.drawable.ic_cancel
} else { )
setImageDrawable( )
ContextCompat.getDrawable( } else {
requireContext(), setImageDrawable(
R.drawable.ic_installation ContextCompat.getDrawable(
) requireContext(),
) R.drawable.ic_installation
} )
setOnClickListener { )
if (viewModel.updateAllEnqueued) { }
cancelAll() setOnClickListener {
} else { if (viewModel.updateAllEnqueued) {
map.keys.forEach { updateSingle(it, true) } cancelAll()
} } else {
binding.recycler.requestModelBuild() map.keys.forEach { updateSingle(it, true) }
} }
binding.recycler.requestModelBuild()
}
}
} else {
binding.updateFab.visibility = View.GONE
} }
} else {
binding.updateFab.visibility = View.GONE
} }
}
} }
binding.swipeRefreshLayout.setOnRefreshListener { binding.swipeRefreshLayout.setOnRefreshListener {
viewModel.observe(true) viewModel.fetchUpdates()
} }
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
@@ -158,7 +159,7 @@ class UpdatesFragment : BaseFragment(R.layout.fragment_updates) {
private fun onEvent(event: BusEvent) { private fun onEvent(event: BusEvent) {
when (event) { when (event) {
is BusEvent.InstallEvent, is BusEvent.UninstallEvent -> { is BusEvent.InstallEvent, is BusEvent.UninstallEvent -> {
viewModel.observe(true) viewModel.fetchUpdates()
} }
else -> {} else -> {}

View File

@@ -21,18 +21,19 @@ package com.aurora.store.viewmodel.all
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context 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.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.aurora.extensions.isApp 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.data.providers.BlacklistProvider
import com.aurora.store.util.PackageUtil
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import java.util.Locale import java.util.Locale
@@ -41,40 +42,36 @@ 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 BlacklistViewModel @Inject constructor( class BlacklistViewModel @Inject constructor(
@ApplicationContext private val context: Context @ApplicationContext private val context: Context,
authProvider: AuthProvider
) : ViewModel() { ) : ViewModel() {
private val packageManager: PackageManager = context.packageManager
private val blacklistProvider: BlacklistProvider = BlacklistProvider.with(context) 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() var selected: MutableSet<String> = mutableSetOf()
val liveData: MutableLiveData<List<Black>> = MutableLiveData()
init { init {
selected = blacklistProvider.getBlackList() selected = blacklistProvider.getBlackList()
observe() fetchApps()
} }
fun observe() { private fun fetchApps() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
supervisorScope { supervisorScope {
try { try {
val packageInfoMap = PackageUtil.getPackageInfoMap(context) val packageNames = context.packageManager.getInstalledPackages(0)
packageInfoMap.values
.filter { it.isApp() } .filter { it.isApp() }
.forEach { .map { it.packageName }
val black = Black(it.packageName).apply {
displayName = packageManager.getApplicationLabel(it.applicationInfo) val apps = appDetailsHelper
.toString() .getAppByPackageName(packageNames)
versionCode = PackageInfoCompat.getLongVersionCode(it) .filter { it.displayName.isNotBlank() }
versionName = it.versionName
drawable = packageManager.getApplicationIcon(packageName) _blacklistedApps.emit(apps.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
}
blackList.add(black)
}
liveData.postValue(blackList.sortedBy { it.displayName.lowercase(Locale.getDefault()) })
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() 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.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.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class InstalledViewModel @Inject constructor( class InstalledViewModel @Inject constructor(
@ApplicationContext private val context: Context,
private val authProvider: AuthProvider private val authProvider: AuthProvider
) : ViewModel() { ) : ViewModel() {
private val TAG = InstalledViewModel::class.java.simpleName 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() val liveData: MutableLiveData<List<App>> = MutableLiveData()
fun getInstalledApps(context: Context) { init {
fetchApps()
}
fun fetchApps() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
appList = AppUtil.getFilteredInstalledApps(context, authProvider.authData).toMutableList() val apps = AppUtil.getFilteredInstalledApps(
liveData.postValue(appList.sortedBy { it.displayName.lowercase(Locale.getDefault()) }) context,
authProvider.authData
)
_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)
} }

View File

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