Add option to pick icons from any icon pack

This commit is contained in:
MM20 2022-07-27 20:08:45 +02:00
parent 6cb74e68f6
commit 2e3add0d94
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
6 changed files with 158 additions and 62 deletions

View File

@ -1,6 +1,5 @@
package de.mm20.launcher2.customattrs
import android.graphics.Color
import android.util.Log
import de.mm20.launcher2.database.entities.CustomAttributeEntity
import de.mm20.launcher2.ktx.jsonObjectOf
@ -76,7 +75,7 @@ sealed class CustomIcon : CustomAttribute {
return when (type) {
"custom_icon_pack_icon" -> {
CustomIconPackIcon(
iconName = payload.getString("icon"),
iconComponentName = payload.getString("icon"),
iconPackPackage = payload.getString("icon_pack")
)
}
@ -86,6 +85,9 @@ sealed class CustomIcon : CustomAttribute {
iconPackPackage = payload.getString("icon_pack")
)
}
"default_icon" -> {
UnmodifiedSystemDefaultIcon
}
"adaptified_legacy_icon" -> {
AdaptifiedLegacyIcon(
fgScale = payload.getDouble("fg_scale").toFloat(),
@ -100,12 +102,12 @@ sealed class CustomIcon : CustomAttribute {
data class CustomIconPackIcon(
val iconPackPackage: String,
val iconName: String,
val iconComponentName: String,
) : CustomIcon() {
override fun toDatabaseValue(): String {
return jsonObjectOf(
"type" to "custom_icon_pack_icon",
"icon" to iconName,
"icon" to iconComponentName,
"icon_pack" to iconPackPackage,
).toString()
}
@ -152,3 +154,15 @@ data class CustomThemedIcon(
).toString()
}
}
/**
* Use default icon, ignore any icon pack, themed icon or force adaptive settings.
*/
object UnmodifiedSystemDefaultIcon: CustomIcon() {
override fun toDatabaseValue(): String {
return jsonObjectOf(
"type" to "default_icon"
).toString()
}
}

View File

@ -13,9 +13,12 @@ interface IconDao {
@Query("SELECT drawable FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack")
suspend fun getIconName(componentName: String, iconPack: String): String?
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack")
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack AND (type = 'app' OR type = 'calendar') LIMIT 1")
suspend fun getIcon(componentName: String, iconPack: String): IconEntity?
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND (type = 'app' OR type = 'calendar')")
suspend fun getIconsFromAllPacks(componentName: String): List<IconEntity>
@Query("DELETE FROM Icons WHERE iconPack = :iconPack")
fun deleteIcons(iconPack: String)

View File

@ -51,14 +51,14 @@ class IconPackManager(
}
}
suspend fun getIcon(iconPack: String, componentName: ComponentName, size: Int): LauncherIcon? {
suspend fun getIcon(iconPack: String, componentName: ComponentName): LauncherIcon? {
val res = try {
context.packageManager.getResourcesForApplication(iconPack)
} catch (e: PackageManager.NameNotFoundException) {
Log.e("MM20", "Icon pack package $iconPack not found!")
return null
}
val iconDao = AppDatabase.getInstance(context).iconDao()
val iconDao = appDatabase.iconDao()
val icon = iconDao.getIcon(componentName.flattenToString(), iconPack)
?: return null
@ -185,26 +185,32 @@ class IconPackManager(
)
}
suspend fun getIcons(componentName: ComponentName): List<IconPackIcon> {
val iconDao = appDatabase.iconDao()
return iconDao.getIconsFromAllPacks(componentName.flattenToString())
.map { IconPackIcon(it) }
}
private suspend fun getIconBack(iconPack: String): String? {
val iconDao = AppDatabase.getInstance(context).iconDao()
val iconDao = appDatabase.iconDao()
val iconbacks = iconDao.getIconBacks(iconPack)
return iconbacks.randomElementOrNull()
}
private suspend fun getIconUpon(iconPack: String): String? {
val iconDao = AppDatabase.getInstance(context).iconDao()
val iconDao = appDatabase.iconDao()
val iconupons = iconDao.getIconUpons(iconPack)
return iconupons.randomElementOrNull()
}
private suspend fun getIconMask(iconPack: String): String? {
val iconDao = AppDatabase.getInstance(context).iconDao()
val iconDao = appDatabase.iconDao()
val iconmasks = iconDao.getIconMasks(iconPack)
return iconmasks.randomElementOrNull()
}
private suspend fun getPackScale(iconPack: String): Float {
val iconDao = AppDatabase.getInstance(context).iconDao()
val iconDao = appDatabase.iconDao()
return iconDao.getScale(iconPack) ?: 1f
}

View File

@ -6,13 +6,12 @@ import android.content.Intent
import android.content.IntentFilter
import android.graphics.Color
import android.util.LruCache
import de.mm20.launcher2.customattrs.AdaptifiedLegacyIcon
import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.customattrs.CustomIcon
import de.mm20.launcher2.customattrs.*
import de.mm20.launcher2.icons.providers.*
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@ -101,6 +100,7 @@ class IconRepository(
transformations.collectLatest { transformations ->
customAttributesRepository.getCustomIcon(searchable).collectLatest { customIcon ->
val provs = getProviders(customIcon) + providers
val transforms = getTransformations(customIcon) ?: transformations
var icon = cache.get(searchable.key + customIcon.hashCode())
@ -112,13 +112,8 @@ class IconRepository(
val placeholder = placeholderProvider?.getIcon(searchable, size)
placeholder?.let { send(it) }
for (provider in providers) {
val ic = provider.getIcon(searchable, size)
if (ic != null) {
icon = ic
break
}
}
icon = getFirstIcon(searchable, size, provs)
if (icon != null) {
icon = applyTransformations(icon, transforms)
@ -130,6 +125,23 @@ class IconRepository(
}
}
private fun getProviders(customIcon: CustomIcon?): List<IconProvider> {
if (customIcon is UnmodifiedSystemDefaultIcon) {
return listOf(
SystemIconProvider(context)
)
}
if (customIcon is CustomIconPackIcon) {
return listOf(
CustomIconPackIconProvider(
customIcon,
iconPackManager
)
)
}
return emptyList()
}
private fun getTransformations(customIcon: CustomIcon?): List<LauncherIconTransformation>? {
customIcon ?: return null
if (customIcon is AdaptifiedLegacyIcon) {
@ -140,6 +152,9 @@ class IconRepository(
)
)
}
if (customIcon is UnmodifiedSystemDefaultIcon) {
return emptyList()
}
return null
}
@ -160,13 +175,7 @@ class IconRepository(
): List<CustomIconSuggestion> {
val suggestions = mutableListOf<CustomIconSuggestion>()
var rawIcon = iconProviders.first().firstNotNullOfOrNull {
it.getIcon(searchable, size)
}
if (rawIcon == null) {
rawIcon = placeholderProvider?.getIcon(searchable, size)
}
var rawIcon = getFirstIcon(searchable, size, iconProviders.first())
if (rawIcon == null) {
return emptyList()
@ -183,56 +192,114 @@ class IconRepository(
)
)
val customIcons = mutableListOf<CustomIcon>(UnmodifiedSystemDefaultIcon)
if (rawIcon is StaticLauncherIcon && rawIcon.backgroundLayer is TransparentLayer) {
val adaptifyOptions = listOf(
// Legacy icons that simply fill the entire canvas
// Legacy icons that simply fill the entire canvas
customIcons.add(
AdaptifiedLegacyIcon(
fgScale = 1f,
bgColor = 1
),
// 48x48 with 5px padding used to be the default icon size for icons generated by
// the Android Studio asset generator. Upscale these icons to remove that padding.
)
)
// 48x48 with 5px padding used to be the default icon size for icons generated by
// the Android Studio asset generator. Upscale these icons to remove that padding.
customIcons.add(
AdaptifiedLegacyIcon(
fgScale = 48f / 38f,
bgColor = 1
),
)
)
customIcons.add(
AdaptifiedLegacyIcon(
fgScale = 0.7f,
bgColor = 0
),
)
)
customIcons.add(
AdaptifiedLegacyIcon(
fgScale = 0.7f,
bgColor = Color.WHITE,
)
)
suggestions.addAll(
adaptifyOptions.mapNotNull {
val transformation =
getTransformations(it)?.firstOrNull() ?: return@mapNotNull null
CustomIconSuggestion(
icon = transformation.transform(rawIcon),
data = it,
)
}
suggestions.addAll(
customIcons.map {
val transformations = getTransformations(it) ?: defaultTransformations
val providers = getProviders(it)
val icon = getFirstIcon(searchable, size, providers) ?: rawIcon
CustomIconSuggestion(
icon = applyTransformations(icon, transformations),
data = it,
)
}
)
val providerOptions = mutableListOf<CustomIcon>()
if (searchable is LauncherApp) {
val iconPackIcons = iconPackManager.getIcons(
searchable.launcherActivityInfo.componentName
)
providerOptions.addAll(
iconPackIcons.mapNotNull {
CustomIconPackIcon(
iconPackPackage = it.iconPack,
iconComponentName = it.componentName?.flattenToString()
?: return@mapNotNull null
)
}
)
}
suggestions.addAll(
providerOptions.mapNotNull {
val providers = getProviders(it)
val icon = getFirstIcon(searchable, size, providers) ?: return@mapNotNull null
CustomIconSuggestion(
icon = applyTransformations(icon, defaultTransformations),
data = it,
)
}
)
return suggestions
}
private suspend fun getFirstIcon(
searchable: Searchable,
size: Int,
providers: List<IconProvider>
): LauncherIcon? {
for (provider in providers) {
val icon = provider.getIcon(searchable, size)
if (icon != null) {
return icon
}
}
return null
}
private suspend fun applyTransformations(
icon: LauncherIcon,
transformations: List<LauncherIconTransformation>
): LauncherIcon {
var icon = icon
if (icon is StaticLauncherIcon) {
var transformedIcon = icon
if (transformedIcon is StaticLauncherIcon) {
for (transformation in transformations) {
icon = transformation.transform(icon as StaticLauncherIcon)
transformedIcon = transformation.transform(transformedIcon as StaticLauncherIcon)
}
}
return icon
return transformedIcon
}
fun setCustomIcon(searchable: Searchable, icon: CustomIcon?) {

View File

@ -0,0 +1,20 @@
package de.mm20.launcher2.icons.providers
import de.mm20.launcher2.customattrs.CustomIconPackIcon
import de.mm20.launcher2.icons.IconPackManager
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.Searchable
class CustomIconPackIconProvider(
private val customIcon: CustomIconPackIcon,
private val iconPackManager: IconPackManager,
) : IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
return iconPackManager.getIcon(
customIcon.iconPackPackage,
searchable.launcherActivityInfo.componentName
)
}
}

View File

@ -2,25 +2,11 @@ package de.mm20.launcher2.icons.providers
import android.content.ComponentName
import android.content.Context
import android.content.pm.LauncherActivityInfo
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.*
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.util.Log
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.randomElementOrNull
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.math.roundToInt
class IconPackIconProvider(
private val context: Context,
@ -31,7 +17,7 @@ class IconPackIconProvider(
if (searchable !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity)
return iconPackManager.getIcon(iconPack, component, size)
return iconPackManager.getIcon(iconPack, component)
?: iconPackManager.generateIcon(
context,
iconPack,