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 package com.aurora.store.data.model
import android.content.Context
import android.content.pm.PackageInfo
import android.os.Parcelable import android.os.Parcelable
import androidx.core.content.pm.PackageInfoCompat
import com.aurora.gplayapi.data.models.App import com.aurora.gplayapi.data.models.App
import com.aurora.store.data.room.update.Update import com.aurora.store.data.room.update.Update
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@@ -21,5 +24,13 @@ data class MinimalApp(
fun fromUpdate(update: Update): MinimalApp { fun fromUpdate(update: Update): MinimalApp {
return MinimalApp(update.packageName, update.versionCode, update.displayName) 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.isTAndAbove
import com.aurora.extensions.isValidApp import com.aurora.extensions.isValidApp
import com.aurora.store.BuildConfig import com.aurora.store.BuildConfig
import java.util.Locale
object PackageUtil { object PackageUtil {
private const val TAG = "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 { fun isInstalled(context: Context, packageName: String): Boolean {
return try { return try {
getPackageInfo(context, packageName, PackageManager.GET_META_DATA) 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 package com.aurora.store.view.ui.all
import android.content.pm.PackageInfo
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.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.aurora.gplayapi.data.models.App
import com.aurora.store.AuroraApp import com.aurora.store.AuroraApp
import com.aurora.store.data.event.InstallerEvent import com.aurora.store.data.event.InstallerEvent
import com.aurora.store.data.model.MinimalApp import com.aurora.store.data.model.MinimalApp
import com.aurora.store.databinding.FragmentAppsBinding import com.aurora.store.databinding.FragmentAppsBinding
import com.aurora.store.view.epoxy.views.HeaderViewModel_ 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.epoxy.views.shimmer.AppListViewShimmerModel_
import com.aurora.store.view.ui.commons.BaseFragment import com.aurora.store.view.ui.commons.BaseFragment
import com.aurora.store.viewmodel.all.InstalledViewModel import com.aurora.store.viewmodel.all.InstalledViewModel
@@ -54,7 +54,7 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewModel.installedApps.collect { viewModel.packages.collect {
updateController(it) 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 { binding.recycler.withModels {
setFilterDuplicates(true) setFilterDuplicates(true)
if (appList == null) { if (packages == null) {
for (i in 1..10) { for (i in 1..10) {
add( add(
AppListViewShimmerModel_() AppListViewShimmerModel_()
@@ -89,16 +89,16 @@ class InstalledAppsFragment : BaseFragment<FragmentAppsBinding>() {
add( add(
HeaderViewModel_() HeaderViewModel_()
.id("header") .id("header")
.title("${appList.size} apps installed") .title("${packages.size} apps installed")
) )
appList.forEach { app -> packages.forEach { app ->
add( add(
AppListViewModel_() PackageInfoViewModel_()
.id(app.id) .id(app.packageName.hashCode())
.app(app) .packageInfo(app)
.click { _ -> openDetailsFragment(app.packageName, app) } .click { _ -> openDetailsFragment(app.packageName) }
.longClick { _ -> .longClick { _ ->
openAppMenuSheet(MinimalApp.fromApp(app)) openAppMenuSheet(MinimalApp.fromPackageInfo(requireContext(), app))
false false
} }
) )

View File

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

View File

@@ -20,12 +20,10 @@
package com.aurora.store.viewmodel.all package com.aurora.store.viewmodel.all
import android.content.Context import android.content.Context
import android.content.pm.PackageInfo
import android.util.Log import android.util.Log
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope 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 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
@@ -33,20 +31,17 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@HiltViewModel @HiltViewModel
class InstalledViewModel @Inject constructor( class InstalledViewModel @Inject constructor(
private val appDetailsHelper: AppDetailsHelper,
private val blacklistProvider: BlacklistProvider,
@ApplicationContext private val context: Context @ApplicationContext private val context: Context
) : ViewModel() { ) : ViewModel() {
private val TAG = InstalledViewModel::class.java.simpleName private val TAG = InstalledViewModel::class.java.simpleName
private val _installedApps = MutableStateFlow<List<App>?>(null) private val _packages = MutableStateFlow<List<PackageInfo>?>(null)
val installedApps = _installedApps.asStateFlow() val packages = _packages.asStateFlow()
init { init {
fetchApps() fetchApps()
@@ -55,16 +50,9 @@ class InstalledViewModel @Inject constructor(
fun fetchApps() { fun fetchApps() {
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
PackageUtil.getPackageInfoMap(context).keys.let { packages -> _packages.value = PackageUtil.getAllValidPackages(context)
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()) }
}
} catch (exception: Exception) { } catch (exception: Exception) {
Log.e(TAG, "Failed to get installed apps", exception) Log.e(TAG, "Failed to fetch apps", exception)
} }
} }
} }

View File

@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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/>.
~
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingStart="@dimen/padding_normal"
android:paddingTop="@dimen/padding_xsmall"
android:paddingEnd="@dimen/padding_small"
android:paddingBottom="@dimen/padding_xsmall">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/img_icon"
android:layout_width="@dimen/icon_size_medium"
android:layout_height="@dimen/icon_size_medium"
android:layout_centerVertical="true" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line1"
style="@style/AuroraTextStyle.Line1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:layout_toEndOf="@id/img_icon" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line2"
style="@style/AuroraTextStyle.Line2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_line1"
android:layout_alignStart="@id/txt_line1"
android:layout_alignEnd="@id/txt_line1"
android:layout_marginTop="@dimen/margin_xxsmall" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/txt_line3"
style="@style/AuroraTextStyle.Line3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/txt_line2"
android:layout_alignStart="@id/txt_line1"
android:layout_alignEnd="@id/txt_line1"
android:layout_marginTop="@dimen/margin_xxsmall" />
</RelativeLayout>