Improve - Installed Apps, Updates & Blaclists
This commit is contained in:
committed by
Aayush Gupta
parent
a43779ed79
commit
a2fef6a897
@@ -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() -> {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 -> {}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user