Refactor icons
This commit is contained in:
@@ -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)
|
||||
}
|
||||
+1
-1
@@ -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
|
||||
|
||||
+24
-7
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -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
|
||||
}
|
||||
+50
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user