Themed icons

This commit is contained in:
MM20
2021-12-04 21:39:45 +01:00
parent be50fb2276
commit ccda48c559
18 changed files with 357 additions and 125 deletions
@@ -95,6 +95,11 @@ class PreferencesAppearanceFragment : PreferenceFragmentCompat() {
true true
} }
findPreference<Preference>("themed_icons")?.setOnPreferenceChangeListener { _, _ ->
iconRepository.recreate()
true
}
val shapePreference = findPreference<Preference>("icon_shape")!! val shapePreference = findPreference<Preference>("icon_shape")!!
shapePreference.summary = getShapeName() shapePreference.summary = getShapeName()
shapePreference.setOnPreferenceClickListener { shapePreference.setOnPreferenceClickListener {
@@ -22,6 +22,12 @@
app:summary="@string/preference_cards_summary" app:summary="@string/preference_cards_summary"
app:title="@string/preference_cards" /> app:title="@string/preference_cards" />
<PreferenceCategory app:title="@string/preference_category_icons"> <PreferenceCategory app:title="@string/preference_category_icons">
<SwitchPreference
app:key="themed_icons"
android:defaultValue="false"
app:title="@string/preference_themed_icons"
app:summary="@string/preference_themed_icons_summary"
/>
<ListPreference <ListPreference
app:key="icon_pack" app:key="icon_pack"
app:persistent="false" app:persistent="false"
+2
View File
@@ -413,4 +413,6 @@
<item quantity="other">+%1$d laufende Termine aus vergangenen Tagen</item> <item quantity="other">+%1$d laufende Termine aus vergangenen Tagen</item>
</plurals> </plurals>
<string name="weather_wind">Wind:</string> <string name="weather_wind">Wind:</string>
<string name="preference_themed_icons">Eingefärbte Symbole</string>
<string name="preference_themed_icons_summary">Symbole an das Farbschema der App anpassen</string>
</resources> </resources>
+2
View File
@@ -203,6 +203,8 @@
<string name="preference_automatic_location_disabled_summary">Not supported by this provider</string> <string name="preference_automatic_location_disabled_summary">Not supported by this provider</string>
<string name="date_format_long_without_year">EE, MMMM d</string> <string name="date_format_long_without_year">EE, MMMM d</string>
<string name="weather_wind_direction_speed">%1$s • %2$s</string> <string name="weather_wind_direction_speed">%1$s • %2$s</string>
<string name="preference_themed_icons">Themed icons</string>
<string name="preference_themed_icons_summary">Color icons with the application\'s color scheme</string>
<string name="preference_icon_pack">Icon pack</string> <string name="preference_icon_pack">Icon pack</string>
<string name="preference_icon_pack_summary_empty">No icon packs installed</string> <string name="preference_icon_pack_summary_empty">No icon packs installed</string>
<string name="preference_dynamic_icon_bg">Dynamic background</string> <string name="preference_dynamic_icon_bg">Dynamic background</string>
@@ -10,12 +10,10 @@ import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
class CalendarDynamicLauncherIcon( class CalendarDynamicLauncherIcon(
context: Context,
foreground: Drawable, foreground: Drawable,
background: Drawable?, background: Drawable?,
foregroundScale: Float, foregroundScale: Float,
backgroundScale: Float, backgroundScale: Float,
badgeNumber: Float = 0f,
val packageName: String, val packageName: String,
val drawableIds: IntArray, val drawableIds: IntArray,
autoGenerateBackgroundMode: Int autoGenerateBackgroundMode: Int
@@ -25,9 +23,7 @@ class CalendarDynamicLauncherIcon(
foregroundScale, foregroundScale,
backgroundScale, backgroundScale,
/** Not needed, we already have a background **/ /** Not needed, we already have a background **/
autoGenerateBackgroundMode, autoGenerateBackgroundMode
badgeNumber,
null
) { ) {
var currentDay = 0 var currentDay = 0
@@ -7,19 +7,15 @@ import android.graphics.drawable.LayerDrawable
import android.graphics.drawable.RotateDrawable import android.graphics.drawable.RotateDrawable
import android.os.Build import android.os.Build
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
import java.util.* import java.util.*
import kotlin.math.roundToInt import kotlin.math.roundToInt
@RequiresApi(Build.VERSION_CODES.O) @RequiresApi(Build.VERSION_CODES.O)
class ClockDynamicLauncherIcon( class ClockDynamicLauncherIcon(
context: Context,
foreground: LayerDrawable, foreground: LayerDrawable,
background: Drawable?, background: Drawable?,
foregroundScale: Float, foregroundScale: Float,
backgroundScale: Float, backgroundScale: Float,
badgeNumber: Float,
val hourLayer: Int, val hourLayer: Int,
val minuteLayer: Int, val minuteLayer: Int,
val secondLayer: Int val secondLayer: Int
@@ -29,15 +25,15 @@ class ClockDynamicLauncherIcon(
foregroundScale, foregroundScale,
backgroundScale, backgroundScale,
/** Not needed, we already have a background **/ /** Not needed, we already have a background **/
LauncherIcon.BACKGROUND_WHITE, LauncherIcon.BACKGROUND_WHITE
badgeNumber,
null
) { ) {
init { init {
foreground.also { foreground.also {
try {
it.setDrawable(secondLayer, ColorDrawable(0)) it.setDrawable(secondLayer, ColorDrawable(0))
} catch (e: IndexOutOfBoundsException) {}
(it.getDrawable(hourLayer) as? RotateDrawable)?.fromDegrees = 0f (it.getDrawable(hourLayer) as? RotateDrawable)?.fromDegrees = 0f
(it.getDrawable(hourLayer) as? RotateDrawable)?.toDegrees = 360f (it.getDrawable(hourLayer) as? RotateDrawable)?.toDegrees = 360f
(it.getDrawable(minuteLayer) as? RotateDrawable)?.fromDegrees = 0f (it.getDrawable(minuteLayer) as? RotateDrawable)?.fromDegrees = 0f
@@ -2,17 +2,14 @@ package de.mm20.launcher2.icons
import android.content.Context import android.content.Context
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
abstract class DynamicLauncherIcon( abstract class DynamicLauncherIcon(
foreground: Drawable, foreground: Drawable,
background: Drawable?, background: Drawable?,
foregroundScale: Float, foregroundScale: Float,
backgroundScale: Float, backgroundScale: Float,
autoGenerateBackgroundMode: Int, autoGenerateBackgroundMode: Int
badgeNumber: Float, )
badgeDrawable: Drawable?)
: LauncherIcon( : LauncherIcon(
foreground, foreground,
background, background,
@@ -3,38 +3,30 @@ package de.mm20.launcher2.icons
import android.content.ComponentName import android.content.ComponentName
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.pm.LauncherActivityInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.ResolveInfo import android.content.pm.ResolveInfo
import android.content.res.Resources
import android.content.res.XmlResourceParser import android.content.res.XmlResourceParser
import android.graphics.* import android.graphics.*
import android.graphics.drawable.AdaptiveIconDrawable
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.util.Log import android.util.Log
import androidx.core.content.res.ResourcesCompat
import androidx.core.graphics.drawable.toBitmap
import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.crashreporter.CrashReporter
import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
import de.mm20.launcher2.ktx.randomElementOrNull
import de.mm20.launcher2.preferences.IconShape
import de.mm20.launcher2.preferences.LauncherPreferences
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParser
import org.xmlpull.v1.XmlPullParserException import org.xmlpull.v1.XmlPullParserException
import org.xmlpull.v1.XmlPullParserFactory import org.xmlpull.v1.XmlPullParserFactory
import java.io.InputStreamReader import java.io.InputStreamReader
import kotlin.math.roundToInt
private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf(
"com.google.android.apps.nexuslauncher", // Pixel Launcher
"app.lawnchair.lawnicons", // Lawnicons
"app.lawnchair", // Lawnchair
"de.mm20.launcher2.themedicons",
)
class IconPackManager( class IconPackManager(
val context: Context, val context: Context
val dynamicIconController: DynamicIconController
) { ) {
var selectedIconPack: String var selectedIconPack: String
get() { get() {
@@ -54,52 +46,6 @@ class IconPackManager(
selectedIconPack = iconPack selectedIconPack = iconPack
} }
private fun getCalendarIcon(
context: Context,
activity: LauncherActivityInfo
): CalendarDynamicLauncherIcon? {
val component = ComponentName(activity.applicationInfo.packageName, activity.name)
val pm = context.packageManager
val ai = try {
pm.getActivityInfo(component, PackageManager.GET_META_DATA)
} catch (e: PackageManager.NameNotFoundException) {
return null
}
val resources = pm.getResourcesForActivity(component)
var arrayId = ai.metaData?.getInt("com.teslacoilsw.launcher.calendarIconArray") ?: 0
if (arrayId == 0) arrayId = ai.metaData?.getInt("com.google.android.calendar.dynamic_icons")
?: return null
if (arrayId == 0) return null
val typedArray = resources.obtainTypedArrayOrNull(arrayId) ?: return null
if (typedArray.length() != 31) {
typedArray.recycle()
return null
}
val drawableIds = IntArray(31)
for (i in 0 until 31) {
drawableIds[i] = typedArray.getResourceId(i, 0)
}
typedArray.recycle()
return CalendarDynamicLauncherIcon(
context = context,
background = ColorDrawable(0),
foreground = ColorDrawable(0),
foregroundScale = 1.5f,
backgroundScale = 1.5f,
packageName = component.packageName,
drawableIds = drawableIds,
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
)
}
private fun getScale(): Float {
return when (LauncherPreferences.instance.iconShape) {
IconShape.CIRCLE, IconShape.PLATFORM_DEFAULT -> 0.7f
else -> 0.8f
}
}
suspend fun getInstalledIconPacks(): List<IconPack> { suspend fun getInstalledIconPacks(): List<IconPack> {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map { AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map {
@@ -108,11 +54,6 @@ class IconPackManager(
} }
} }
companion object {
const val GOOGLE_DESK_CLOCK_PACKAGE_NAME = "com.google.android.deskclock"
const val GOOGLE_CALENDAR_PACKAGE_NAME = "com.google.android.calendar"
}
@Synchronized @Synchronized
suspend fun updateIconPacks() { suspend fun updateIconPacks() {
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
@@ -143,6 +84,9 @@ class UpdateIconPacksWorker(val context: Context) {
continue continue
} }
} }
val supportedGrayscaleMapPackages = SUPPORTED_GRAYSCALE_MAP_PROVIDERS
supportedGrayscaleMapPackages.forEach { installGrayscaleIconMap(it) }
} }
private fun loadInstalledPacks(context: Context): List<ResolveInfo> { private fun loadInstalledPacks(context: Context): List<ResolveInfo> {
@@ -296,6 +240,46 @@ class UpdateIconPacksWorker(val context: Context) {
CrashReporter.logException(e) CrashReporter.logException(e)
} }
} }
private fun installGrayscaleIconMap(packageName: String) {
val iconDao = AppDatabase.getInstance(context).iconDao()
try {
val resources = context.packageManager.getResourcesForApplication(packageName)
val resId = resources.getIdentifier("grayscale_icon_map", "xml", packageName)
if (resId == 0) {
iconDao.deleteIcons(packageName)
return
}
val icons = mutableListOf<Icon>()
val parser = resources.getXml(resId)
loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) {
if (parser.eventType != XmlPullParser.START_TAG) continue
when (parser.name) {
"icon" -> {
val drawable =
parser.getAttributeResourceValue(null, "drawable", 0).toString()
val pkg = parser.getAttributeValue(null, "package")
for (i in 0 until parser.attributeCount) {
Log.d("MM20", "${parser.getAttributeName(0)}")
}
val componentName = ComponentName(pkg, pkg)
val icon = Icon(
drawable = drawable,
componentName = componentName,
iconPack = packageName,
type = "greyscale_icon"
)
icons.add(icon)
Log.d("MM20", "Installed icon ${icon.toString()}")
}
}
}
iconDao.installGrayscaleIconMap(packageName, icons.map { it.toDatabaseEntity() })
} catch (e: PackageManager.NameNotFoundException) {
iconDao.deleteIcons(packageName)
return
}
}
} }
private const val PREFERENCE_NAME = "icon_pack" private const val PREFERENCE_NAME = "icon_pack"
@@ -86,13 +86,20 @@ class IconRepository(
} }
fun recreate() { fun recreate() {
placeholderProvider = PlaceholderIconProvider(context) placeholderProvider = if (LauncherPreferences.instance.themedIcons) {
ThemedPlaceholderIconProvider(context)
} else {
PlaceholderIconProvider(context)
}
val providers = mutableListOf<IconProvider>() val providers = mutableListOf<IconProvider>()
if (LauncherPreferences.instance.themedIcons) {
providers.add(ThemedIconProvider(context))
}
if (iconPackManager.selectedIconPack.isNotBlank()) { if (iconPackManager.selectedIconPack.isNotBlank()) {
providers.add(IconPackIconProvider(context, iconPackManager.selectedIconPack)) providers.add(IconPackIconProvider(context, iconPackManager.selectedIconPack))
} }
providers.add(GoogleClockIconProvider(context)) providers.add(GoogleClockIconProvider(context))
providers.add(CalendarIconProvider(context)) providers.add(CalendarIconProvider(context))
providers.add(SystemIconProvider(context)) providers.add(SystemIconProvider(context))
@@ -5,6 +5,6 @@ import org.koin.dsl.module
val iconsModule = module { val iconsModule = module {
single { DynamicIconController(androidContext()) } single { DynamicIconController(androidContext()) }
single { IconPackManager(androidContext(), get()) } single { IconPackManager(androidContext()) }
single { IconRepository(androidContext(), get(), get()) } single { IconRepository(androidContext(), get(), get()) }
} }
@@ -0,0 +1,46 @@
package de.mm20.launcher2.icons
import android.content.Context
import android.content.pm.PackageManager
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,
val foregroundTint: Int,
background: Drawable,
) : DynamicLauncherIcon(
foreground = ColorDrawable(0),
background = background,
foregroundScale = foregroundScale,
backgroundScale = 1f,
/** Not needed, we already have a background **/
BACKGROUND_WHITE
) {
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
currentDayDrawable.setTint(foregroundTint)
foreground = currentDayDrawable
}
currentDay = day
}
}
@@ -37,9 +37,8 @@ class CalendarIconProvider(val context: Context): IconProvider {
} }
typedArray.recycle() typedArray.recycle()
return CalendarDynamicLauncherIcon( return CalendarDynamicLauncherIcon(
context = context,
background = ColorDrawable(0),
foreground = ColorDrawable(0), foreground = ColorDrawable(0),
background = ColorDrawable(0),
foregroundScale = 1.5f, foregroundScale = 1.5f,
backgroundScale = 1.5f, backgroundScale = 1.5f,
packageName = component.packageName, packageName = component.packageName,
@@ -8,7 +8,6 @@ import android.graphics.drawable.LayerDrawable
import android.os.Build import android.os.Build
import androidx.core.content.res.ResourcesCompat import androidx.core.content.res.ResourcesCompat
import de.mm20.launcher2.icons.ClockDynamicLauncherIcon import de.mm20.launcher2.icons.ClockDynamicLauncherIcon
import de.mm20.launcher2.icons.IconPackManager
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.data.Application import de.mm20.launcher2.search.data.Application
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
@@ -21,7 +20,7 @@ class GoogleClockIconProvider(val context: Context) : IconProvider {
val pm = context.packageManager val pm = context.packageManager
val appInfo = try { val appInfo = try {
pm.getApplicationInfo( pm.getApplicationInfo(
IconPackManager.GOOGLE_DESK_CLOCK_PACKAGE_NAME, "com.google.android.deskclock",
PackageManager.GET_META_DATA PackageManager.GET_META_DATA
) )
} catch (e: PackageManager.NameNotFoundException) { } catch (e: PackageManager.NameNotFoundException) {
@@ -44,12 +43,10 @@ class GoogleClockIconProvider(val context: Context) : IconProvider {
val secondLayer = val secondLayer =
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX") appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX")
return ClockDynamicLauncherIcon( return ClockDynamicLauncherIcon(
context = context,
background = baseIcon.background,
backgroundScale = 1.5f,
foreground = foreground, foreground = foreground,
background = baseIcon.background,
foregroundScale = 1.5f, foregroundScale = 1.5f,
badgeNumber = 0f, backgroundScale = 1.5f,
hourLayer = hourLayer, hourLayer = hourLayer,
minuteLayer = minuteLayer, minuteLayer = minuteLayer,
secondLayer = secondLayer secondLayer = secondLayer
@@ -194,9 +194,8 @@ class IconPackIconProvider(val context: Context, val iconPack: String): IconProv
id id
}.toIntArray() }.toIntArray()
return CalendarDynamicLauncherIcon( return CalendarDynamicLauncherIcon(
context = context,
background = ColorDrawable(0),
foreground = ColorDrawable(0), foreground = ColorDrawable(0),
background = ColorDrawable(0),
foregroundScale = 1.5f, foregroundScale = 1.5f,
backgroundScale = 1.5f, backgroundScale = 1.5f,
packageName = iconPack, packageName = iconPack,
@@ -6,6 +6,6 @@ import de.mm20.launcher2.search.data.Searchable
class SystemIconProvider(val context: Context) : IconProvider { class SystemIconProvider(val context: Context) : IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
return searchable.loadIconAsync(context, size) return searchable.loadIcon(context, size)
} }
} }
@@ -0,0 +1,150 @@
package de.mm20.launcher2.icons.providers
import android.content.ComponentName
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.LayerDrawable
import android.os.Build
import android.util.TypedValue
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.obtainTypedArrayOrNull
import de.mm20.launcher2.search.data.Application
import de.mm20.launcher2.search.data.Searchable
class ThemedIconProvider(
private val context: Context
) : IconProvider {
private val fgColor: Int
private val bgColor: Int
init {
val theme = context.resources.newTheme()
theme.applyStyle(R.style.DefaultColors, true)
val typedValue = TypedValue()
val isDarkMode =
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES != 0
val bgAttr =
if (isDarkMode) R.attr.colorOnPrimaryContainer else R.attr.colorPrimaryContainer
val fgAttr = if (isDarkMode) R.attr.colorOnSurfaceInverse else R.attr.colorOnSurfaceVariant
bgColor = theme.resolveAttribute(bgAttr, typedValue, true).let {
typedValue.data
}
fgColor = theme.resolveAttribute(fgAttr, typedValue, true).let {
typedValue.data
}
}
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
if (searchable !is Application) return null
val icon = getGreyscaleIcon(searchable.`package`) ?: return null
val resId = icon.drawable?.toIntOrNull() ?: return null
try {
val resources = context.packageManager.getResourcesForApplication(icon.iconPack)
return getClockIcon(resources, resId) ?: getCalendarIcon(
resources,
resId,
iconProviderPackage = icon.iconPack
) ?: getStaticIcon(resources, resId)
} catch (e: PackageManager.NameNotFoundException) {
CrashReporter.logException(e)
}
return null
}
private suspend fun getGreyscaleIcon(packageName: String): Icon? {
val iconDao = AppDatabase.getInstance(context).iconDao()
return iconDao.getGreyscaleIcon(ComponentName(packageName, packageName).flattenToString())
?.let { Icon(it) }
}
private fun getStaticIcon(resources: Resources, resId: Int): LauncherIcon? {
try {
val fg = ResourcesCompat.getDrawable(resources, resId, null) ?: return null
fg.setTint(fgColor)
return LauncherIcon(
foreground = fg,
foregroundScale = 0.5f,
background = ColorDrawable(bgColor)
)
} catch (e: Resources.NotFoundException) {
return null
}
}
private fun getClockIcon(resources: Resources, resId: Int): LauncherIcon? {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
return null
}
try {
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
var i = 0
var drawable: LayerDrawable? = null
var minuteIndex: Int? = null
var hourIndex: Int? = null
while (i < array.length()) {
when (array.getString(i)) {
"com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND" -> {
i++
drawable = array.getDrawable(i) as? LayerDrawable
}
"com.android.launcher3.HOUR_LAYER_INDEX" -> {
i++
hourIndex = array.getInt(i, -1).takeIf { it != -1 }
}
"com.android.launcher3.MINUTE_LAYER_INDEX" -> {
i++
minuteIndex = array.getInt(i, -1).takeIf { it != -1 }
}
}
i++
}
if (drawable != null && minuteIndex != null && hourIndex != null) {
drawable.setTint(fgColor)
return ClockDynamicLauncherIcon(
foreground = drawable,
background = ColorDrawable(bgColor),
foregroundScale = 1.5f,
backgroundScale = 1f,
hourLayer = hourIndex,
minuteLayer = minuteIndex,
secondLayer = -1,
)
}
} catch (e: Resources.NotFoundException) {
}
return null
}
private fun getCalendarIcon(
resources: Resources,
resId: Int,
iconProviderPackage: String
): LauncherIcon? {
try {
val array = resources.obtainTypedArrayOrNull(resId) ?: return null
if (array.length() != 31) return null
return ThemedCalendarDynamicLauncherIcon(
background = ColorDrawable(bgColor),
packageName = iconProviderPackage,
foregroundIds = IntArray(31) {
array.getResourceId(it, 0).takeIf { it != 0 } ?: return null
},
foregroundTint = fgColor,
foregroundScale = 0.5f,
)
} catch (e: Resources.NotFoundException) {
}
return null
}
}
@@ -0,0 +1,45 @@
package de.mm20.launcher2.icons.providers
import android.content.Context
import android.content.res.Configuration
import android.util.TypedValue
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.icons.R
import de.mm20.launcher2.search.data.Searchable
class ThemedPlaceholderIconProvider(
val context: Context
) : IconProvider {
private val fgColor: Int
private val bgColor: Int
init {
val theme = context.resources.newTheme()
theme.applyStyle(R.style.DefaultColors, true)
val typedValue = TypedValue()
val isDarkMode =
context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_YES != 0
val bgAttr =
if (isDarkMode) R.attr.colorOnPrimaryContainer else R.attr.colorPrimaryContainer
val fgAttr = if (isDarkMode) R.attr.colorOnSurfaceInverse else R.attr.colorOnSurfaceVariant
bgColor = theme.resolveAttribute(bgAttr, typedValue, true).let {
typedValue.data
}
fgColor = theme.resolveAttribute(fgAttr, typedValue, true).let {
typedValue.data
}
}
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon {
val icon = searchable.getPlaceholderIcon(context)
icon.foreground.setTint(fgColor)
icon.background?.setTint(bgColor)
return icon
}
}
@@ -80,6 +80,7 @@ class LauncherPreferences(val context: Application, version: Int = 3) {
var calendarMaxEvents by StringPreference("calendar_max_events", default = "10") var calendarMaxEvents by StringPreference("calendar_max_events", default = "10")
var themedIcons by BooleanPreference("themed_icons", default = false)
var legacyIconBg by StringPreference("legacy_icon_bg", default = "1") var legacyIconBg by StringPreference("legacy_icon_bg", default = "1")
var blurCards by BooleanPreference("blur_cards", default = false) var blurCards by BooleanPreference("blur_cards", default = false)
var searchStyle by EnumPreference("search_style", default = SearchStyles.NO_BG) var searchStyle by EnumPreference("search_style", default = SearchStyles.NO_BG)