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 package de.mm20.launcher2.customattrs
import android.graphics.Color
import android.util.Log import android.util.Log
import de.mm20.launcher2.database.entities.CustomAttributeEntity import de.mm20.launcher2.database.entities.CustomAttributeEntity
import de.mm20.launcher2.ktx.jsonObjectOf import de.mm20.launcher2.ktx.jsonObjectOf
@ -76,7 +75,7 @@ sealed class CustomIcon : CustomAttribute {
return when (type) { return when (type) {
"custom_icon_pack_icon" -> { "custom_icon_pack_icon" -> {
CustomIconPackIcon( CustomIconPackIcon(
iconName = payload.getString("icon"), iconComponentName = payload.getString("icon"),
iconPackPackage = payload.getString("icon_pack") iconPackPackage = payload.getString("icon_pack")
) )
} }
@ -86,6 +85,9 @@ sealed class CustomIcon : CustomAttribute {
iconPackPackage = payload.getString("icon_pack") iconPackPackage = payload.getString("icon_pack")
) )
} }
"default_icon" -> {
UnmodifiedSystemDefaultIcon
}
"adaptified_legacy_icon" -> { "adaptified_legacy_icon" -> {
AdaptifiedLegacyIcon( AdaptifiedLegacyIcon(
fgScale = payload.getDouble("fg_scale").toFloat(), fgScale = payload.getDouble("fg_scale").toFloat(),
@ -100,12 +102,12 @@ sealed class CustomIcon : CustomAttribute {
data class CustomIconPackIcon( data class CustomIconPackIcon(
val iconPackPackage: String, val iconPackPackage: String,
val iconName: String, val iconComponentName: String,
) : CustomIcon() { ) : CustomIcon() {
override fun toDatabaseValue(): String { override fun toDatabaseValue(): String {
return jsonObjectOf( return jsonObjectOf(
"type" to "custom_icon_pack_icon", "type" to "custom_icon_pack_icon",
"icon" to iconName, "icon" to iconComponentName,
"icon_pack" to iconPackPackage, "icon_pack" to iconPackPackage,
).toString() ).toString()
} }
@ -152,3 +154,15 @@ data class CustomThemedIcon(
).toString() ).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") @Query("SELECT drawable FROM Icons WHERE componentName = :componentName AND iconPack = :iconPack")
suspend fun getIconName(componentName: String, iconPack: String): String? 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? 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") @Query("DELETE FROM Icons WHERE iconPack = :iconPack")
fun deleteIcons(iconPack: String) 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 { val res = try {
context.packageManager.getResourcesForApplication(iconPack) context.packageManager.getResourcesForApplication(iconPack)
} catch (e: PackageManager.NameNotFoundException) { } catch (e: PackageManager.NameNotFoundException) {
Log.e("MM20", "Icon pack package $iconPack not found!") Log.e("MM20", "Icon pack package $iconPack not found!")
return null return null
} }
val iconDao = AppDatabase.getInstance(context).iconDao() val iconDao = appDatabase.iconDao()
val icon = iconDao.getIcon(componentName.flattenToString(), iconPack) val icon = iconDao.getIcon(componentName.flattenToString(), iconPack)
?: return null ?: 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? { private suspend fun getIconBack(iconPack: String): String? {
val iconDao = AppDatabase.getInstance(context).iconDao() val iconDao = appDatabase.iconDao()
val iconbacks = iconDao.getIconBacks(iconPack) val iconbacks = iconDao.getIconBacks(iconPack)
return iconbacks.randomElementOrNull() return iconbacks.randomElementOrNull()
} }
private suspend fun getIconUpon(iconPack: String): String? { private suspend fun getIconUpon(iconPack: String): String? {
val iconDao = AppDatabase.getInstance(context).iconDao() val iconDao = appDatabase.iconDao()
val iconupons = iconDao.getIconUpons(iconPack) val iconupons = iconDao.getIconUpons(iconPack)
return iconupons.randomElementOrNull() return iconupons.randomElementOrNull()
} }
private suspend fun getIconMask(iconPack: String): String? { private suspend fun getIconMask(iconPack: String): String? {
val iconDao = AppDatabase.getInstance(context).iconDao() val iconDao = appDatabase.iconDao()
val iconmasks = iconDao.getIconMasks(iconPack) val iconmasks = iconDao.getIconMasks(iconPack)
return iconmasks.randomElementOrNull() return iconmasks.randomElementOrNull()
} }
private suspend fun getPackScale(iconPack: String): Float { private suspend fun getPackScale(iconPack: String): Float {
val iconDao = AppDatabase.getInstance(context).iconDao() val iconDao = appDatabase.iconDao()
return iconDao.getScale(iconPack) ?: 1f return iconDao.getScale(iconPack) ?: 1f
} }

View File

@ -6,13 +6,12 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.graphics.Color import android.graphics.Color
import android.util.LruCache import android.util.LruCache
import de.mm20.launcher2.customattrs.AdaptifiedLegacyIcon import de.mm20.launcher2.customattrs.*
import de.mm20.launcher2.customattrs.CustomAttributesRepository
import de.mm20.launcher2.customattrs.CustomIcon
import de.mm20.launcher2.icons.providers.* import de.mm20.launcher2.icons.providers.*
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -101,6 +100,7 @@ class IconRepository(
transformations.collectLatest { transformations -> transformations.collectLatest { transformations ->
customAttributesRepository.getCustomIcon(searchable).collectLatest { customIcon -> customAttributesRepository.getCustomIcon(searchable).collectLatest { customIcon ->
val provs = getProviders(customIcon) + providers
val transforms = getTransformations(customIcon) ?: transformations val transforms = getTransformations(customIcon) ?: transformations
var icon = cache.get(searchable.key + customIcon.hashCode()) var icon = cache.get(searchable.key + customIcon.hashCode())
@ -112,13 +112,8 @@ class IconRepository(
val placeholder = placeholderProvider?.getIcon(searchable, size) val placeholder = placeholderProvider?.getIcon(searchable, size)
placeholder?.let { send(it) } placeholder?.let { send(it) }
for (provider in providers) { icon = getFirstIcon(searchable, size, provs)
val ic = provider.getIcon(searchable, size)
if (ic != null) {
icon = ic
break
}
}
if (icon != null) { if (icon != null) {
icon = applyTransformations(icon, transforms) 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>? { private fun getTransformations(customIcon: CustomIcon?): List<LauncherIconTransformation>? {
customIcon ?: return null customIcon ?: return null
if (customIcon is AdaptifiedLegacyIcon) { if (customIcon is AdaptifiedLegacyIcon) {
@ -140,6 +152,9 @@ class IconRepository(
) )
) )
} }
if (customIcon is UnmodifiedSystemDefaultIcon) {
return emptyList()
}
return null return null
} }
@ -160,13 +175,7 @@ class IconRepository(
): List<CustomIconSuggestion> { ): List<CustomIconSuggestion> {
val suggestions = mutableListOf<CustomIconSuggestion>() val suggestions = mutableListOf<CustomIconSuggestion>()
var rawIcon = iconProviders.first().firstNotNullOfOrNull { var rawIcon = getFirstIcon(searchable, size, iconProviders.first())
it.getIcon(searchable, size)
}
if (rawIcon == null) {
rawIcon = placeholderProvider?.getIcon(searchable, size)
}
if (rawIcon == null) { if (rawIcon == null) {
return emptyList() return emptyList()
@ -183,56 +192,114 @@ class IconRepository(
) )
) )
val customIcons = mutableListOf<CustomIcon>(UnmodifiedSystemDefaultIcon)
if (rawIcon is StaticLauncherIcon && rawIcon.backgroundLayer is TransparentLayer) { 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( AdaptifiedLegacyIcon(
fgScale = 1f, fgScale = 1f,
bgColor = 1 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( AdaptifiedLegacyIcon(
fgScale = 48f / 38f, fgScale = 48f / 38f,
bgColor = 1 bgColor = 1
), )
)
customIcons.add(
AdaptifiedLegacyIcon( AdaptifiedLegacyIcon(
fgScale = 0.7f, fgScale = 0.7f,
bgColor = 0 bgColor = 0
), )
)
customIcons.add(
AdaptifiedLegacyIcon( AdaptifiedLegacyIcon(
fgScale = 0.7f, fgScale = 0.7f,
bgColor = Color.WHITE, bgColor = Color.WHITE,
) )
) )
suggestions.addAll( }
adaptifyOptions.mapNotNull { suggestions.addAll(
val transformation = customIcons.map {
getTransformations(it)?.firstOrNull() ?: return@mapNotNull null val transformations = getTransformations(it) ?: defaultTransformations
CustomIconSuggestion( val providers = getProviders(it)
icon = transformation.transform(rawIcon),
data = 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 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( private suspend fun applyTransformations(
icon: LauncherIcon, icon: LauncherIcon,
transformations: List<LauncherIconTransformation> transformations: List<LauncherIconTransformation>
): LauncherIcon { ): LauncherIcon {
var icon = icon var transformedIcon = icon
if (icon is StaticLauncherIcon) { if (transformedIcon is StaticLauncherIcon) {
for (transformation in transformations) { 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?) { 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.ComponentName
import android.content.Context 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.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.LauncherApp
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.math.roundToInt
class IconPackIconProvider( class IconPackIconProvider(
private val context: Context, private val context: Context,
@ -31,7 +17,7 @@ class IconPackIconProvider(
if (searchable !is LauncherApp) return null if (searchable !is LauncherApp) return null
val component = ComponentName(searchable.`package`, searchable.activity) val component = ComponentName(searchable.`package`, searchable.activity)
return iconPackManager.getIcon(iconPack, component, size) return iconPackManager.getIcon(iconPack, component)
?: iconPackManager.generateIcon( ?: iconPackManager.generateIcon(
context, context,
iconPack, iconPack,