Themed icons

This commit is contained in:
MM20 2021-12-04 21:39:45 +01:00
parent be50fb2276
commit ccda48c559
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
18 changed files with 357 additions and 125 deletions

View File

@ -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 {

View File

@ -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"

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>

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>

View File

@ -10,24 +10,20 @@ 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, val packageName: String,
badgeNumber: Float = 0f, val drawableIds: IntArray,
val packageName: String, autoGenerateBackgroundMode: Int
val drawableIds: IntArray,
autoGenerateBackgroundMode: Int
) : DynamicLauncherIcon( ) : DynamicLauncherIcon(
foreground, foreground,
background, background,
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

View File

@ -7,37 +7,33 @@ 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, val hourLayer: Int,
badgeNumber: Float, val minuteLayer: Int,
val hourLayer: Int, val secondLayer: Int
val minuteLayer: Int,
val secondLayer: Int
) : DynamicLauncherIcon( ) : DynamicLauncherIcon(
foreground, foreground,
background, background,
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 {
it.setDrawable(secondLayer, ColorDrawable(0)) try {
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

View File

@ -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,

View File

@ -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"

View File

@ -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))

View File

@ -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()) }
} }

View File

@ -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
}
}

View File

@ -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,

View File

@ -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

View File

@ -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,

View File

@ -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)
} }
} }

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)