Themed icons
This commit is contained in:
parent
be50fb2276
commit
ccda48c559
@ -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"
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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,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
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user