Improve icon display when selecting apps for restore

This commit is contained in:
Torsten Grote 2024-05-23 18:06:57 -03:00
parent 787b9346a8
commit eecfcdb285
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
4 changed files with 27 additions and 24 deletions

View file

@ -1,6 +1,6 @@
package com.stevesoltys.seedvault.restore package com.stevesoltys.seedvault.restore
import android.graphics.Bitmap import android.graphics.drawable.Drawable
import android.text.format.DateUtils import android.text.format.DateUtils
import android.text.format.Formatter import android.text.format.Formatter
import android.view.LayoutInflater import android.view.LayoutInflater
@ -39,7 +39,7 @@ internal data class SelectableAppItem(
internal class AppSelectionAdapter( internal class AppSelectionAdapter(
val scope: CoroutineScope, val scope: CoroutineScope,
val iconLoader: suspend (SelectableAppItem, (Bitmap) -> Unit) -> Unit, val iconLoader: suspend (SelectableAppItem, (Drawable) -> Unit) -> Unit,
val listener: (SelectableAppItem) -> Unit, val listener: (SelectableAppItem) -> Unit,
) : Adapter<RecyclerView.ViewHolder>() { ) : Adapter<RecyclerView.ViewHolder>() {
@ -156,16 +156,18 @@ internal class AppSelectionAdapter(
checkBox.visibility = if (item.hasIcon == null) INVISIBLE else VISIBLE checkBox.visibility = if (item.hasIcon == null) INVISIBLE else VISIBLE
progressBar.visibility = if (item.hasIcon == null) VISIBLE else INVISIBLE progressBar.visibility = if (item.hasIcon == null) VISIBLE else INVISIBLE
val isSpecial = item.metadata.isInternalSystem
appIcon.scaleType = FIT_CENTER
appIcon.setImageResource(R.drawable.ic_launcher_default) appIcon.setImageResource(R.drawable.ic_launcher_default)
if (item.hasIcon == null) { appIcon.scaleType = if (isSpecial) CENTER else FIT_CENTER
if (item.hasIcon == null && !isSpecial) {
appIcon.alpha = 0.5f appIcon.alpha = 0.5f
} else if (item.hasIcon) { } else if (item.hasIcon == true || isSpecial) {
appIcon.alpha = 0.5f appIcon.alpha = 0.5f
iconJob = scope.launch { iconJob = scope.launch {
iconLoader(item) { bitmap -> iconLoader(item) { bitmap ->
val isSpecial = item.metadata.system && !item.metadata.isLaunchableSystemApp
appIcon.scaleType = if (isSpecial) CENTER else FIT_CENTER appIcon.scaleType = if (isSpecial) CENTER else FIT_CENTER
appIcon.setImageBitmap(bitmap) appIcon.setImageDrawable(bitmap)
appIcon.alpha = 1f appIcon.alpha = 1f
} }
} }

View file

@ -1,6 +1,6 @@
package com.stevesoltys.seedvault.restore package com.stevesoltys.seedvault.restore
import android.graphics.Bitmap import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -73,7 +73,7 @@ class AppSelectionFragment : Fragment() {
} }
} }
private suspend fun loadIcon(item: SelectableAppItem, callback: (Bitmap) -> Unit) { private suspend fun loadIcon(item: SelectableAppItem, callback: (Drawable) -> Unit) {
viewModel.loadIcon(item, callback) viewModel.loadIcon(item, callback)
} }

View file

@ -13,14 +13,13 @@ import android.app.backup.IRestoreObserver
import android.app.backup.IRestoreSession import android.app.backup.IRestoreSession
import android.app.backup.RestoreSet import android.app.backup.RestoreSet
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap import android.graphics.drawable.Drawable
import android.os.RemoteException import android.os.RemoteException
import android.os.UserHandle import android.os.UserHandle
import android.util.Log import android.util.Log
import androidx.annotation.UiThread import androidx.annotation.UiThread
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.core.graphics.drawable.toBitmap
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
@ -199,7 +198,7 @@ internal class RestoreViewModel(
?: return@mapNotNull null ?: return@mapNotNull null
if (metadata.time == 0L && !metadata.hasApk()) return@mapNotNull null if (metadata.time == 0L && !metadata.hasApk()) return@mapNotNull null
val name = app.getString(data.nameRes) val name = app.getString(data.nameRes)
SelectableAppItem(packageName, metadata.copy(name = name), true, hasIcon = true) SelectableAppItem(packageName, metadata.copy(name = name), true)
} }
val systemItem = SelectableAppItem( val systemItem = SelectableAppItem(
packageName = PACKAGE_NAME_SYSTEM, packageName = PACKAGE_NAME_SYSTEM,
@ -227,10 +226,10 @@ internal class RestoreViewModel(
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "Error loading icons:", e) Log.e(TAG, "Error loading icons:", e)
emptySet() emptySet()
} } + systemData.keys + setOf(PACKAGE_NAME_SYSTEM)
// update state, so it knows that icons have loaded // update state, so it knows that icons have loaded
val updatedItems = items.map { item -> val updatedItems = items.map { item ->
item.copy(hasIcon = item.hasIcon ?: false || item.packageName in packagesWithIcons) item.copy(hasIcon = item.packageName in packagesWithIcons)
} }
val newState = val newState =
SelectedAppsState(updatedItems, allSelected = true, iconsLoaded = true) SelectedAppsState(updatedItems, allSelected = true, iconsLoaded = true)
@ -239,13 +238,13 @@ internal class RestoreViewModel(
mDisplayFragment.setEvent(SELECT_APPS) mDisplayFragment.setEvent(SELECT_APPS)
} }
suspend fun loadIcon(item: SelectableAppItem, callback: (Bitmap) -> Unit) { suspend fun loadIcon(item: SelectableAppItem, callback: (Drawable) -> Unit) {
if (item.packageName == PACKAGE_NAME_SYSTEM) { if (item.packageName == PACKAGE_NAME_SYSTEM) {
val bitmap = getDrawable(app, R.drawable.ic_app_settings)!!.toBitmap() val drawable = getDrawable(app, R.drawable.ic_app_settings)!!
callback(bitmap) callback(drawable)
} else if (item.metadata.isInternalSystem && item.packageName in systemData.keys) { } else if (item.metadata.isInternalSystem && item.packageName in systemData.keys) {
val bitmap = getDrawable(app, systemData[item.packageName]!!.iconRes)!!.toBitmap() val drawable = getDrawable(app, systemData[item.packageName]!!.iconRes)!!
callback(bitmap) callback(drawable)
} else { } else {
iconManager.loadIcon(item.packageName, callback) iconManager.loadIcon(item.packageName, callback)
} }

View file

@ -6,12 +6,13 @@
package com.stevesoltys.seedvault.worker package com.stevesoltys.seedvault.worker
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.Bitmap.CompressFormat.WEBP_LOSSY import android.graphics.Bitmap.CompressFormat.WEBP_LOSSY
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
import android.util.Log import android.util.Log
import androidx.appcompat.content.res.AppCompatResources.getDrawable import androidx.appcompat.content.res.AppCompatResources.getDrawable
import androidx.core.graphics.drawable.toBitmap import androidx.core.graphics.drawable.toBitmap
import androidx.core.graphics.drawable.toDrawable
import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.R
import com.stevesoltys.seedvault.transport.backup.PackageService import com.stevesoltys.seedvault.transport.backup.PackageService
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -92,23 +93,24 @@ internal class IconManager(
} }
private val defaultIcon by lazy { private val defaultIcon by lazy {
getDrawable(context, R.drawable.ic_launcher_default)!!.toBitmap() getDrawable(context, R.drawable.ic_launcher_default)!!
} }
/** /**
* Tries to load the icons for the given [packageName] * Tries to load the icons for the given [packageName]
* that was downloaded before with [downloadIcons]. * that was downloaded before with [downloadIcons].
* Calls [callback] on the UiThread with the loaded [Bitmap] or the default icon. * Calls [callback] on the UiThread with the loaded [Drawable] or the default icon.
*/ */
suspend fun loadIcon(packageName: String, callback: (Bitmap) -> Unit) { suspend fun loadIcon(packageName: String, callback: (Drawable) -> Unit) {
try { try {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val folder = File(context.cacheDir, CACHE_FOLDER) val folder = File(context.cacheDir, CACHE_FOLDER)
val file = File(folder, packageName) val file = File(folder, packageName)
file.inputStream().use { inputStream -> file.inputStream().use { inputStream ->
val bitmap = BitmapFactory.decodeStream(inputStream) val drawable =
BitmapFactory.decodeStream(inputStream).toDrawable(context.resources)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
callback(bitmap) callback(drawable)
} }
} }
} }