Refactor icons

This commit is contained in:
MM20
2022-06-18 18:51:41 +02:00
parent 08e736f5d1
commit 6de89f3f79
54 changed files with 894 additions and 1489 deletions
@@ -1,53 +0,0 @@
package de.mm20.launcher2.icons
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import de.mm20.launcher2.ktx.getDrawableOrNull
import java.util.*
import java.util.concurrent.Executors
class CalendarDynamicLauncherIcon(
foreground: Drawable,
background: Drawable?,
foregroundScale: Float,
backgroundScale: Float,
val packageName: String,
val drawableIds: IntArray,
) : DynamicLauncherIcon(
foreground,
background,
foregroundScale,
backgroundScale,
) {
var currentDay = 0
override fun update(context: Context) {
val calendar = Calendar.getInstance()
val day = calendar[Calendar.DAY_OF_MONTH]
if (day == currentDay || drawableIds.size < currentDay) return
val resources = try {
context.packageManager.getResourcesForApplication(packageName)
} catch (e: PackageManager.NameNotFoundException) {
return
}
Executors.newSingleThreadExecutor().execute {
val currentDayDrawable = resources.getDrawableOrNull(drawableIds[day - 1])
?: return@execute
if (currentDayDrawable is AdaptiveIconDrawable) {
foreground = currentDayDrawable.foreground
background = currentDayDrawable.background
foregroundScale = 1.5f
backgroundScale = 1.5f
} else {
foregroundScale = 1f
backgroundScale = 1f
background = null
foreground = currentDayDrawable
}
}
currentDay = day
}
}
@@ -1,53 +0,0 @@
package de.mm20.launcher2.icons
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RotateDrawable
import android.os.Build
import androidx.annotation.RequiresApi
import java.util.*
import kotlin.math.roundToInt
class ClockDynamicLauncherIcon(
foreground: LayerDrawable,
background: Drawable?,
foregroundScale: Float,
backgroundScale: Float,
isThemeable: Boolean = false,
val hourLayer: Int,
val minuteLayer: Int,
val secondLayer: Int
) : DynamicLauncherIcon(
foreground,
background,
foregroundScale,
backgroundScale,
isThemeable = isThemeable
) {
init {
foreground.also {
try {
it.setDrawable(secondLayer, ColorDrawable(0))
} catch (e: IndexOutOfBoundsException) {}
(it.getDrawable(hourLayer) as? RotateDrawable)?.fromDegrees = 0f
(it.getDrawable(hourLayer) as? RotateDrawable)?.toDegrees = 360f
(it.getDrawable(minuteLayer) as? RotateDrawable)?.fromDegrees = 0f
(it.getDrawable(minuteLayer) as? RotateDrawable)?.toDegrees = 360f
}
}
override fun update(context: Context) {
val calendar = Calendar.getInstance()
val hourDegrees = calendar[Calendar.HOUR] / 12f * 10000 + calendar[Calendar.MINUTE] / 60f * 10000f / 12
val minuteDegrees = calendar[Calendar.MINUTE] / 60f * 10000
(foreground as LayerDrawable).also {
(it.getDrawable(hourLayer) as? RotateDrawable)?.level = hourDegrees.roundToInt()
(it.getDrawable(minuteLayer) as? RotateDrawable)?.level = minuteDegrees.roundToInt()
}
notifyCallbacks()
}
}
@@ -0,0 +1,66 @@
package de.mm20.launcher2.icons
import android.content.res.Resources
import android.graphics.drawable.AdaptiveIconDrawable
import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
import java.time.Instant
import java.time.ZoneId
internal class DynamicCalendarIcon(
val resources: Resources,
val resourceIds: IntArray,
val isThemed: Boolean = false,
private val transformations: List<LauncherIconTransformation> = emptyList(),
) : DynamicLauncherIcon {
init {
if (resourceIds.size < 31) throw IllegalArgumentException("DynamicCalendarIcon resourceIds must at least have 31 items")
}
override suspend fun getIcon(time: Long): StaticLauncherIcon {
val day = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).dayOfMonth
val resId = resourceIds[day - 1]
val drawable = try {
ResourcesCompat.getDrawable(resources, resId, null)
} catch (e: Resources.NotFoundException) {
null
} ?: return StaticLauncherIcon(
foregroundLayer = TextLayer(day.toString()),
backgroundLayer = ColorLayer()
)
var icon = if (isThemed) {
StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = drawable,
scale = 0.5f,
),
backgroundLayer = ColorLayer()
)
} else if (drawable is AdaptiveIconDrawable) {
StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = drawable.foreground,
scale = 1.5f
),
backgroundLayer = StaticIconLayer(
icon = drawable.background,
scale = 1.5f
)
)
} else StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = drawable,
scale = 1f,
),
backgroundLayer = TransparentLayer
)
for (transformation in transformations) {
icon = transformation.transform(icon)
}
return icon
}
}
@@ -1,58 +0,0 @@
package de.mm20.launcher2.icons
import android.app.Activity
import android.app.Application
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.util.Log
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import java.lang.ref.WeakReference
class DynamicIconController(val context: Context): LifecycleObserver {
private var timeReceiver: BroadcastReceiver? = null
private val registeredIcons = mutableListOf<WeakReference<DynamicLauncherIcon>>()
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun resume() {
timeReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
updateAllIcons(context)
}
}
val filter = IntentFilter(Intent.ACTION_TIME_TICK).also {
it.addAction(Intent.ACTION_TIME_CHANGED)
it.addAction(Intent.ACTION_TIMEZONE_CHANGED)
}
context.registerReceiver(timeReceiver, filter)
updateAllIcons(context)
}
private fun updateAllIcons(context: Context) {
val iterator = registeredIcons.iterator()
while (iterator.hasNext()) {
val iconRef = iterator.next()
if (iconRef.get() != null) iconRef.get()?.update(context)
else iterator.remove()
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun pause() {
try {
context.unregisterReceiver(timeReceiver)
} catch (e: IllegalArgumentException) {
}
timeReceiver = null
}
fun registerIcon(icon: DynamicLauncherIcon) {
icon.update(context)
registeredIcons.add(WeakReference(icon))
}
}
@@ -1,21 +0,0 @@
package de.mm20.launcher2.icons
import android.content.Context
import android.graphics.drawable.Drawable
abstract class DynamicLauncherIcon(
foreground: Drawable,
background: Drawable?,
foregroundScale: Float,
backgroundScale: Float,
isThemeable: Boolean = false
) : LauncherIcon(
foreground,
background,
foregroundScale,
backgroundScale,
isThemeable = isThemeable
) {
abstract fun update(context: Context)
}
@@ -3,7 +3,7 @@ package de.mm20.launcher2.icons
import android.content.ComponentName
import de.mm20.launcher2.database.entities.IconEntity
data class Icon(
data class IconPackIcon(
val type: String,
val componentName: ComponentName?,
val drawable: String?,
@@ -124,7 +124,7 @@ class UpdateIconPacksWorker(val context: Context) {
parser.setInput(inStream)
}
val icons = mutableListOf<Icon>()
val icons = mutableListOf<IconPackIcon>()
val iconDao = AppDatabase.getInstance(context).iconDao()
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
@@ -143,7 +143,7 @@ class UpdateIconPacksWorker(val context: Context) {
)
)
?: continue@loop
val icon = Icon(
val icon = IconPackIcon(
componentName = componentName,
drawable = drawable,
iconPack = pkgName,
@@ -164,7 +164,7 @@ class UpdateIconPacksWorker(val context: Context) {
)
?: continue@loop
val icon = Icon(
val icon = IconPackIcon(
componentName = componentName,
drawable = drawable,
iconPack = pkgName,
@@ -176,7 +176,7 @@ class UpdateIconPacksWorker(val context: Context) {
for (i in 0 until parser.attributeCount) {
if (parser.getAttributeName(i).startsWith("img")) {
val drawable = parser.getAttributeValue(i)
val icon = Icon(
val icon = IconPackIcon(
componentName = null,
drawable = drawable,
iconPack = pkgName,
@@ -190,7 +190,7 @@ class UpdateIconPacksWorker(val context: Context) {
for (i in 0 until parser.attributeCount) {
if (parser.getAttributeName(i).startsWith("img")) {
val drawable = parser.getAttributeValue(i)
val icon = Icon(
val icon = IconPackIcon(
componentName = null,
drawable = drawable,
iconPack = pkgName,
@@ -204,7 +204,7 @@ class UpdateIconPacksWorker(val context: Context) {
for (i in 0 until parser.attributeCount) {
if (parser.getAttributeName(i).startsWith("img")) {
val drawable = parser.getAttributeValue(i)
val icon = Icon(
val icon = IconPackIcon(
componentName = null,
drawable = drawable,
iconPack = pkgName,
@@ -246,7 +246,7 @@ class UpdateIconPacksWorker(val context: Context) {
iconDao.deleteIcons(packageName)
return
}
val icons = mutableListOf<Icon>()
val icons = mutableListOf<IconPackIcon>()
val parser = resources.getXml(resId)
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.eventType != XmlPullParser.START_TAG) continue
@@ -256,7 +256,7 @@ class UpdateIconPacksWorker(val context: Context) {
parser.getAttributeResourceValue(null, "drawable", 0).toString()
val pkg = parser.getAttributeValue(null, "package")
val componentName = ComponentName(pkg, pkg)
val icon = Icon(
val icon = IconPackIcon(
drawable = drawable,
componentName = componentName,
iconPack = packageName,
@@ -6,6 +6,8 @@ import android.content.Intent
import android.content.IntentFilter
import android.util.LruCache
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.Searchable
import kotlinx.coroutines.CoroutineScope
@@ -17,7 +19,6 @@ import kotlinx.coroutines.launch
class IconRepository(
val context: Context,
private val iconPackManager: IconPackManager,
private val dynamicIconController: DynamicIconController,
private val dataStore: LauncherDataStore
) {
@@ -34,6 +35,11 @@ class IconRepository(
private var iconProviders: MutableStateFlow<List<IconProvider>> = MutableStateFlow(listOf())
private var placeholderProvider: IconProvider? = null
private var transformations: MutableStateFlow<List<LauncherIconTransformation>> =
MutableStateFlow(
listOf()
)
init {
requestIconPackListUpdate()
context.registerReceiver(appReceiver, IntentFilter().apply {
@@ -62,19 +68,23 @@ class IconRepository(
providers.add(
IconPackIconProvider(
context,
settings.iconPack,
settings.legacyIconBg
settings.iconPack
)
)
}
providers.add(GoogleClockIconProvider(context))
providers.add(CalendarIconProvider(context))
providers.add(SystemIconProvider(context, settings.legacyIconBg))
providers.add(SystemIconProvider(context))
providers.add(placeholderProvider)
cache.evictAll()
val transformations = mutableListOf<LauncherIconTransformation>()
if (settings.adaptify) transformations.add(LegacyToAdaptiveTransformation())
this@IconRepository.placeholderProvider = placeholderProvider
iconProviders.value = providers
this@IconRepository.transformations.value = transformations
}
}
}
@@ -82,28 +92,33 @@ class IconRepository(
fun getIcon(searchable: Searchable, size: Int): Flow<LauncherIcon> = channelFlow {
iconProviders.collectLatest { providers ->
var icon = cache.get(searchable.key)
if (icon != null) {
send(icon)
return@collectLatest
}
transformations.collectLatest { transformations ->
var icon = cache.get(searchable.key)
if (icon != null) {
send(icon)
return@collectLatest
}
val placeholder = placeholderProvider?.getIcon(searchable, size)
placeholder?.let { send(it) }
val placeholder = placeholderProvider?.getIcon(searchable, size)
placeholder?.let { send(it) }
for (provider in providers) {
val ic = provider.getIcon(searchable, size)
if (ic != null) {
if (ic is DynamicLauncherIcon) {
dynamicIconController.registerIcon(ic)
for (provider in providers) {
val ic = provider.getIcon(searchable, size)
if (ic != null) {
icon = ic
break
}
icon = ic
break
}
if (icon != null) {
if (icon is StaticLauncherIcon) {
for (transformation in transformations) {
icon = transformation.transform(icon as StaticLauncherIcon)
}
}
cache.put(searchable.key, icon)
send(icon)
}
}
if (icon != null) {
cache.put(searchable.key, icon)
send(icon)
}
}
}
@@ -4,7 +4,6 @@ import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
val iconsModule = module {
single { DynamicIconController(androidContext()) }
single { IconPackManager(androidContext()) }
single { IconRepository(androidContext(), get(), get(), get()) }
single { IconRepository(androidContext(), get(), get()) }
}
@@ -1,44 +0,0 @@
package de.mm20.launcher2.icons
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.Color
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.os.Build
import de.mm20.launcher2.ktx.getDrawableOrNull
import java.util.*
import java.util.concurrent.Executors
class ThemedCalendarDynamicLauncherIcon(
foregroundScale: Float,
val packageName: String,
val foregroundIds: IntArray,
background: Drawable,
) : DynamicLauncherIcon(
foreground = ColorDrawable(0),
background = background,
foregroundScale = foregroundScale,
backgroundScale = 1f,
isThemeable = true,
) {
var currentDay = 0
override fun update(context: Context) {
val calendar = Calendar.getInstance()
val day = calendar[Calendar.DAY_OF_MONTH]
if (day == currentDay || foregroundIds.size < currentDay) return
val resources = try {
context.packageManager.getResourcesForApplication(packageName)
} catch (e: PackageManager.NameNotFoundException) {
return
}
Executors.newSingleThreadExecutor().execute {
val currentDayDrawable = resources.getDrawableOrNull(foregroundIds[day - 1])
?: return@execute
foreground = currentDayDrawable
}
currentDay = day
}
}
@@ -4,7 +4,7 @@ import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.graphics.drawable.ColorDrawable
import de.mm20.launcher2.icons.CalendarDynamicLauncherIcon
import de.mm20.launcher2.icons.DynamicCalendarIcon
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
import de.mm20.launcher2.search.data.Application
@@ -35,13 +35,9 @@ class CalendarIconProvider(val context: Context): IconProvider {
drawableIds[i] = typedArray.getResourceId(i, 0)
}
typedArray.recycle()
return CalendarDynamicLauncherIcon(
foreground = ColorDrawable(0),
background = ColorDrawable(0),
foregroundScale = 1.5f,
backgroundScale = 1.5f,
packageName = component.packageName,
drawableIds = drawableIds
return DynamicCalendarIcon(
resources = resources,
resourceIds = drawableIds
)
}
}
@@ -5,12 +5,12 @@ import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.graphics.drawable.RotateDrawable
import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.icons.ClockDynamicLauncherIcon
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.data.Application
import de.mm20.launcher2.search.data.Searchable
import kotlin.math.roundToInt
class GoogleClockIconProvider(val context: Context) : IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
@@ -26,7 +26,7 @@ class GoogleClockIconProvider(val context: Context) : IconProvider {
return null
}
val drawable =
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND")
appInfo.metaData.getInt("com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND")
val resources = pm.getResourcesForApplication(appInfo)
val baseIcon = try {
ResourcesCompat.getDrawable(resources, drawable, null) as? AdaptiveIconDrawable
@@ -36,19 +36,53 @@ class GoogleClockIconProvider(val context: Context) : IconProvider {
}
val foreground = baseIcon.foreground as? LayerDrawable ?: return null
val hourLayer =
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX")
appInfo.metaData.getInt("com.android.launcher3.HOUR_LAYER_INDEX")
val minuteLayer =
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX")
appInfo.metaData.getInt("com.android.launcher3.MINUTE_LAYER_INDEX")
val secondLayer =
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX")
return ClockDynamicLauncherIcon(
foreground = foreground,
background = baseIcon.background,
foregroundScale = 1.5f,
backgroundScale = 1.5f,
hourLayer = hourLayer,
minuteLayer = minuteLayer,
secondLayer = secondLayer
appInfo.metaData.getInt("com.android.launcher3.SECOND_LAYER_INDEX")
val defaultHour =
appInfo.metaData.getInt("com.android.launcher3.DEFAULT_HOUR")
val defaultMinute =
appInfo.metaData.getInt("com.android.launcher3.DEFAULT_MINUTE")
val defaultSecond =
appInfo.metaData.getInt("com.android.launcher3.DEFAULT_SECOND")
return StaticLauncherIcon(
foregroundLayer = ClockLayer(
sublayers = (0 until foreground.numberOfLayers).map {
val drw = foreground.getDrawable(it)
if (drw is RotateDrawable) {
drw.level = when (it) {
hourLayer -> {
(12 - defaultHour) * 60
}
minuteLayer -> {
(60 - defaultMinute)
}
secondLayer -> {
(60 - defaultSecond) * 10
}
else -> 0
}
}
ClockSublayer(
drawable = drw,
role = when {
it == hourLayer -> ClockSublayerRole.Hour
it == minuteLayer -> ClockSublayerRole.Minute
it == secondLayer -> ClockSublayerRole.Second
else -> ClockSublayerRole.Static
}
)
},
scale = 1.5f,
),
backgroundLayer = StaticIconLayer(
icon = baseIcon.background,
scale = 1.5f,
)
)
}
}
@@ -9,13 +9,11 @@ import android.graphics.*
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.os.Build
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.CalendarDynamicLauncherIcon
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.randomElementOrNull
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.search.data.LauncherApp
@@ -24,9 +22,8 @@ import kotlin.math.roundToInt
class IconPackIconProvider(
private val context: Context,
private val iconPack: String,
private val legacyIconBackground: Settings.IconSettings.LegacyIconBackground
): IconProvider {
private val iconPack: String
): IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
val res = try {
@@ -49,25 +46,31 @@ class IconPackIconProvider(
val drawable = ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null
return when {
drawable is AdaptiveIconDrawable -> {
LauncherIcon(
foreground = drawable.foreground,
background = drawable.background,
foregroundScale = 1.5f,
backgroundScale = 1.5f
StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = drawable.foreground,
scale = 1.5f
),
backgroundLayer = StaticIconLayer(
icon = drawable.background,
scale = 1.5f
)
)
}
else -> {
LauncherIcon(
foreground = drawable,
foregroundScale = getScale(),
autoGenerateBackgroundMode = legacyIconBackground.number
StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = drawable,
scale = getScale()
),
backgroundLayer = TransparentLayer
)
}
}
}
private fun getScale(): Float {
return 0.7f
return 1f
}
private suspend fun generateIcon(
@@ -147,10 +150,12 @@ class IconPackIconProvider(
}
}
return LauncherIcon(
foreground = BitmapDrawable(context.resources, bitmap),
foregroundScale = getScale(),
autoGenerateBackgroundMode = legacyIconBackground.number
return StaticLauncherIcon(
foregroundLayer = StaticIconLayer(
icon = BitmapDrawable(context.resources, bitmap),
scale = getScale(),
),
backgroundLayer = TransparentLayer
)
}
@@ -180,7 +185,7 @@ class IconPackIconProvider(
private fun getIconPackCalendarIcon(
context: Context,
baseIconName: String
): CalendarDynamicLauncherIcon? {
): DynamicCalendarIcon? {
val resources = try {
context.packageManager.getResourcesForApplication(iconPack)
} catch (e: PackageManager.NameNotFoundException) {
@@ -192,13 +197,9 @@ class IconPackIconProvider(
if (id == 0) return null
id
}.toIntArray()
return CalendarDynamicLauncherIcon(
foreground = ColorDrawable(0),
background = ColorDrawable(0),
foregroundScale = 1.5f,
backgroundScale = 1.5f,
packageName = iconPack,
drawableIds = drawableIds,
return DynamicCalendarIcon(
resources = resources,
resourceIds = drawableIds
)
}
}
@@ -5,7 +5,7 @@ import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.data.Searchable
class PlaceholderIconProvider(val context: Context) : IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon {
return searchable.getPlaceholderIcon(context)
}
}
@@ -6,10 +6,9 @@ import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.search.data.Searchable
class SystemIconProvider(
private val context: Context,
private val legacyIconBackground: Settings.IconSettings.LegacyIconBackground
) : IconProvider {
private val context: Context
) : IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
return searchable.loadIcon(context, size, legacyIconBackground)
return searchable.loadIcon(context, size)
}
}
@@ -4,13 +4,13 @@ import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RotateDrawable
import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.ktx.getDrawableOrNull
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
import de.mm20.launcher2.search.data.Application
import de.mm20.launcher2.search.data.Searchable
@@ -37,21 +37,22 @@ internal class ThemedIconProvider(
}
private suspend fun getGreyscaleIcon(packageName: String): Icon? {
private suspend fun getGreyscaleIcon(packageName: String): IconPackIcon? {
val iconDao = AppDatabase.getInstance(context).iconDao()
return iconDao.getGreyscaleIcon(ComponentName(packageName, packageName).flattenToString())
?.let { Icon(it) }
?.let { IconPackIcon(it) }
}
private fun getStaticIcon(resources: Resources, resId: Int): LauncherIcon? {
try {
val fg = ResourcesCompat.getDrawable(resources, resId, null) ?: return null
return LauncherIcon(
foreground = fg,
foregroundScale = 0.5f,
background = ColorDrawable(Color.WHITE),
isThemeable = true
return StaticLauncherIcon(
foregroundLayer = TintedIconLayer(
icon = fg,
scale = 0.5f,
),
backgroundLayer = ColorLayer()
)
} catch (e: Resources.NotFoundException) {
return null
@@ -64,7 +65,9 @@ internal class ThemedIconProvider(
var i = 0
var drawable: LayerDrawable? = null
var minuteIndex: Int? = null
var defaultMinute = 0
var hourIndex: Int? = null
var defaultHour = 0
while (i < array.length()) {
when (array.getString(i)) {
"com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND" -> {
@@ -79,19 +82,46 @@ internal class ThemedIconProvider(
i++
minuteIndex = array.getInt(i, -1).takeIf { it != -1 }
}
"com.android.launcher3.DEFAULT_HOUR" -> {
i++
defaultHour = array.getInt(i, 0)
}
"com.android.launcher3.DEFAULT_MINUTE" -> {
i++
defaultMinute = array.getInt(i, 0)
}
}
i++
}
if (drawable != null && minuteIndex != null && hourIndex != null) {
return ClockDynamicLauncherIcon(
foreground = drawable,
background = ColorDrawable(Color.WHITE),
foregroundScale = 1.5f,
backgroundScale = 1f,
hourLayer = hourIndex,
minuteLayer = minuteIndex,
secondLayer = -1,
isThemeable = true,
return StaticLauncherIcon(
foregroundLayer = TintedClockLayer(
sublayers = (0 until drawable.numberOfLayers).map {
val drw = drawable.getDrawable(it)
if (drw is RotateDrawable) {
drw.level = when (it) {
hourIndex -> {
(12 - defaultHour) * 60
}
minuteIndex -> {
(60 - defaultMinute)
}
else -> 0
}
}
ClockSublayer(
drawable = drw,
role = when {
it == hourIndex -> ClockSublayerRole.Hour
it == minuteIndex -> ClockSublayerRole.Minute
else -> ClockSublayerRole.Static
}
)
},
scale = 1.5f,
),
backgroundLayer = ColorLayer()
)
}
} catch (e: Resources.NotFoundException) {
@@ -108,15 +138,13 @@ internal class ThemedIconProvider(
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
if (array.length() != 31) return null
return ThemedCalendarDynamicLauncherIcon(
foregroundScale = 0.5f,
packageName = iconProviderPackage,
foregroundIds = IntArray(31) {
return DynamicCalendarIcon(
resources = resources,
resourceIds = IntArray(31) {
array.getResourceId(it, 0).takeIf { it != 0 } ?: return null
},
background = ColorDrawable(Color.WHITE),
isThemed = true
)
} catch (e: Resources.NotFoundException) {
}
return null
@@ -1,7 +1,7 @@
package de.mm20.launcher2.icons.providers
import android.content.Context
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.icons.*
import de.mm20.launcher2.search.data.Searchable
internal class ThemedPlaceholderIconProvider(
@@ -11,13 +11,30 @@ internal class ThemedPlaceholderIconProvider(
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon {
val icon = searchable.getPlaceholderIcon(context)
return LauncherIcon(
foreground = icon.foreground,
foregroundScale = icon.foregroundScale,
background = icon.background,
backgroundScale = icon.backgroundScale,
isThemeable = true
return StaticLauncherIcon(
foregroundLayer = asThemed(icon.foregroundLayer),
backgroundLayer = asThemed(icon.backgroundLayer),
)
}
private fun asThemed(layer: LauncherIconLayer): LauncherIconLayer {
return when (layer) {
is ClockLayer -> TintedClockLayer(
scale = layer.scale,
color = 0,
sublayers = layer.sublayers,
)
is ColorLayer -> layer.copy(color = 0)
is StaticIconLayer -> TintedIconLayer(
icon = layer.icon,
color = 0,
scale = layer.scale,
)
is TextLayer -> layer.copy(color = 0)
is TintedIconLayer -> layer.copy(color = 0)
is TintedClockLayer -> return layer.copy(color = 0)
is TransparentLayer -> return layer
}
}
}
@@ -0,0 +1,7 @@
package de.mm20.launcher2.icons.transformations
import de.mm20.launcher2.icons.StaticLauncherIcon
internal interface LauncherIconTransformation {
suspend fun transform(icon: StaticLauncherIcon): StaticLauncherIcon
}
@@ -0,0 +1,50 @@
package de.mm20.launcher2.icons.transformations
import android.graphics.drawable.BitmapDrawable
import androidx.core.graphics.drawable.toBitmap
import androidx.palette.graphics.Palette
import de.mm20.launcher2.icons.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal class LegacyToAdaptiveTransformation: LauncherIconTransformation {
override suspend fun transform(icon: StaticLauncherIcon): StaticLauncherIcon {
if (icon.backgroundLayer !is TransparentLayer) return icon
val bgColor = extractColor(icon.foregroundLayer)
return StaticLauncherIcon(
foregroundLayer = scale(icon.foregroundLayer, 0.7f),
backgroundLayer = ColorLayer(bgColor)
)
}
private fun scale(layer: LauncherIconLayer, scale: Float): LauncherIconLayer {
return when(layer) {
is ClockLayer -> layer.copy(scale = scale)
is StaticIconLayer -> layer.copy(scale = scale)
is TintedClockLayer -> layer.copy(scale = scale)
is TintedIconLayer -> layer.copy(scale = scale)
else -> layer
}
}
private suspend fun extractColor(layer: LauncherIconLayer): Int {
if (layer is StaticIconLayer) {
val drawable = layer.icon
val bitmap = if (drawable is BitmapDrawable) {
drawable.bitmap
} else {
drawable.toBitmap(48, 48)
}
val palette = withContext(Dispatchers.Default) {
Palette.from(bitmap).generate()
}
return palette.getDominantColor(0)
} else if (layer is ColorLayer) {
return layer.color
}
return 0
}
}