diff --git a/app/src/main/java/com/aurora/extensions/PackageInfo.kt b/app/src/main/java/com/aurora/extensions/PackageInfo.kt index 5abd85346..41b83296d 100644 --- a/app/src/main/java/com/aurora/extensions/PackageInfo.kt +++ b/app/src/main/java/com/aurora/extensions/PackageInfo.kt @@ -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() -> { diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/BlackListView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/BlackListView.kt index 7d0527b02..69d6d46d2 100644 --- a/app/src/main/java/com/aurora/store/view/epoxy/views/BlackListView.kt +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/BlackListView.kt @@ -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 diff --git a/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt index 388d0e651..e6ba2b7db 100644 --- a/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt @@ -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) } } diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistFragment.kt index 5895ff6e8..89aff6fbc 100644 --- a/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistFragment.kt @@ -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?) { + private fun updateController(blackList: List?) { 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() + } + ) + } } } } diff --git a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt index e9466b741..e6255ed59 100644 --- a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt +++ b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt @@ -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 -> {} diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt index 7c3678ba1..edb9f7185 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt @@ -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?>(null) + val blackListedApps = _blacklistedApps.asStateFlow() - var blackList: MutableList = mutableListOf() var selected: MutableSet = mutableSetOf() - val liveData: MutableLiveData> = 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() } diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt index 264417706..d583f3081 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt @@ -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 = mutableListOf() + private val _installedApps = MutableStateFlow?>(null) + val installedApps = _installedApps.asStateFlow() + val liveData: MutableLiveData> = 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) } diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt index 86ad97359..daab3e8a6 100644 --- a/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt +++ b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt @@ -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?>(null) val updates = _updates.asStateFlow() - val stash: MutableList = 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) + } } } }