InstalledAppsFragment: Show all installed packages similar to blacklist

Signed-off-by: Aayush Gupta <aayushgupta219@gmail.com>
This commit is contained in:
Aayush Gupta
2024-11-11 11:20:01 +05:30
parent f30e79aeb1
commit 44d20d517d
7 changed files with 175 additions and 43 deletions

View File

@@ -1,6 +1,9 @@
package com.aurora.store.data.model
import android.content.Context
import android.content.pm.PackageInfo
import android.os.Parcelable
import androidx.core.content.pm.PackageInfoCompat
import com.aurora.gplayapi.data.models.App
import com.aurora.store.data.room.update.Update
import kotlinx.parcelize.Parcelize
@@ -21,5 +24,13 @@ data class MinimalApp(
fun fromUpdate(update: Update): MinimalApp {
return MinimalApp(update.packageName, update.versionCode, update.displayName)
}
fun fromPackageInfo(context: Context, packageInfo: PackageInfo): MinimalApp {
return MinimalApp(
packageInfo.packageName,
PackageInfoCompat.getLongVersionCode(packageInfo).toInt(),
packageInfo.applicationInfo!!.loadLabel(context.packageManager).toString()
)
}
}
}

View File

@@ -40,11 +40,21 @@ import com.aurora.extensions.isPAndAbove
import com.aurora.extensions.isTAndAbove
import com.aurora.extensions.isValidApp
import com.aurora.store.BuildConfig
import java.util.Locale
object PackageUtil {
private const val TAG = "PackageUtil"
fun getAllValidPackages(context: Context): List<PackageInfo> {
return context.packageManager.getInstalledPackages(PackageManager.GET_META_DATA)
.filter { it.isValidApp(context.packageManager) }
.sortedBy {
it.applicationInfo!!.loadLabel(context.packageManager).toString()
.lowercase(Locale.getDefault())
}
}
fun isInstalled(context: Context, packageName: String): Boolean {
return try {
getPackageInfo(context, packageName, PackageManager.GET_META_DATA)

View File

@@ -0,0 +1,69 @@
/*
* Aurora Store
* Copyright (C) 2021, Rahul Kumar Patel <whyorean@gmail.com>
*
* Aurora Store is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* Aurora Store is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Aurora Store. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.aurora.store.view.epoxy.views
import android.content.Context
import android.content.pm.PackageInfo
import android.util.AttributeSet
import androidx.core.content.pm.PackageInfoCompat
import coil3.load
import coil3.request.placeholder
import coil3.request.transformations
import coil3.transform.RoundedCornersTransformation
import com.airbnb.epoxy.CallbackProp
import com.airbnb.epoxy.ModelProp
import com.airbnb.epoxy.ModelView
import com.aurora.store.R
import com.aurora.store.databinding.ViewPackageBinding
import com.aurora.store.util.PackageUtil
@ModelView(
autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT,
baseModelClass = BaseModel::class
)
class PackageInfoView @JvmOverloads constructor(
context: Context?,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : BaseView<ViewPackageBinding>(context, attrs, defStyleAttr) {
@ModelProp(options = [ModelProp.Option.IgnoreRequireHashCode])
fun packageInfo(packageInfo: PackageInfo) {
val appInfo = packageInfo.applicationInfo!!
binding.imgIcon.load(PackageUtil.getIconForPackage(context, appInfo.packageName)) {
placeholder(R.drawable.bg_placeholder)
transformations(RoundedCornersTransformation(25F))
}
binding.txtLine1.text = appInfo.loadLabel(context.packageManager)
binding.txtLine2.text = appInfo.packageName
binding.txtLine3.text = ("${packageInfo.versionName}.${PackageInfoCompat.getLongVersionCode(packageInfo)}")
}
@CallbackProp
fun click(onClickListener: OnClickListener?) {
binding.root.setOnClickListener(onClickListener)
}
@CallbackProp
fun longClick(onClickListener: OnLongClickListener?) {
binding.root.setOnLongClickListener(onClickListener)
}
}

View File

@@ -19,18 +19,18 @@
package com.aurora.store.view.ui.all
import android.content.pm.PackageInfo
import android.os.Bundle
import android.util.Log
import android.view.View
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.AuroraApp
import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.model.MinimalApp
import com.aurora.store.databinding.FragmentAppsBinding
import com.aurora.store.view.epoxy.views.HeaderViewModel_
import com.aurora.store.view.epoxy.views.app.AppListViewModel_
import com.aurora.store.view.epoxy.views.PackageInfoViewModel_
import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.all.InstalledViewModel
@@ -54,7 +54,7 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
viewModel.installedApps.collect {
viewModel.packages.collect {
updateController(it)
}
}
@@ -75,10 +75,10 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
}
}
private fun updateController(appList: List<App>?) {
private fun updateController(packages: List<PackageInfo>?) {
binding.recycler.withModels {
setFilterDuplicates(true)
if (appList == null) {
if (packages == null) {
for (i in 1..10) {
add(
AppListViewShimmerModel_()
@@ -89,16 +89,16 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
add(
HeaderViewModel_()
.id("header")
.title("${appList.size} apps installed")
.title("${packages.size} apps installed")
)
appList.forEach { app ->
packages.forEach { app ->
add(
AppListViewModel_()
.id(app.id)
.app(app)
.click { _ -> openDetailsFragment(app.packageName, app) }
PackageInfoViewModel_()
.id(app.packageName.hashCode())
.packageInfo(app)
.click { _ -> openDetailsFragment(app.packageName) }
.longClick { _ ->
openAppMenuSheet(MinimalApp.fromApp(app))
openAppMenuSheet(MinimalApp.fromPackageInfo(requireContext(), app))
false
}
)

View File

@@ -24,16 +24,14 @@ import android.content.pm.PackageInfo
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.extensions.isValidApp
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
import javax.inject.Inject
@HiltViewModel
@@ -55,17 +53,10 @@ class BlacklistViewModel @Inject constructor(
private fun fetchApps() {
viewModelScope.launch(Dispatchers.IO) {
supervisorScope {
try {
_packages.value = context.packageManager.getInstalledPackages(0)
.filter { it.isValidApp(context.packageManager) }
.sortedBy {
it.applicationInfo!!.loadLabel(context.packageManager).toString()
.lowercase(Locale.getDefault())
}
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch apps", exception)
}
try {
_packages.value = PackageUtil.getAllValidPackages(context)
} catch (exception: Exception) {
Log.e(TAG, "Failed to fetch apps", exception)
}
}
}

View File

@@ -20,12 +20,10 @@
package com.aurora.store.viewmodel.all
import android.content.Context
import android.content.pm.PackageInfo
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.aurora.gplayapi.data.models.App
import com.aurora.gplayapi.helpers.AppDetailsHelper
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
@@ -33,20 +31,17 @@ 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(
private val appDetailsHelper: AppDetailsHelper,
private val blacklistProvider: BlacklistProvider,
@ApplicationContext private val context: Context
) : ViewModel() {
private val TAG = InstalledViewModel::class.java.simpleName
private val _installedApps = MutableStateFlow<List<App>?>(null)
val installedApps = _installedApps.asStateFlow()
private val _packages = MutableStateFlow<List<PackageInfo>?>(null)
val packages = _packages.asStateFlow()
init {
fetchApps()
@@ -55,16 +50,9 @@ class InstalledViewModel @Inject constructor(
fun fetchApps() {
viewModelScope.launch(Dispatchers.IO) {
try {
PackageUtil.getPackageInfoMap(context).keys.let { packages ->
val filtersPackages = packages.filter { !blacklistProvider.isBlacklisted(it) }
_installedApps.value = appDetailsHelper.getAppByPackageName(filtersPackages)
.filter { it.displayName.isNotEmpty() }
.map { it.isInstalled = true; it }
.sortedBy { it.displayName.lowercase(Locale.getDefault()) }
}
_packages.value = PackageUtil.getAllValidPackages(context)
} catch (exception: Exception) {
Log.e(TAG, "Failed to get installed apps", exception)
Log.e(TAG, "Failed to fetch apps", exception)
}
}
}