Rework icon repository
This commit is contained in:
parent
2a271a41ec
commit
dbd7fda7fb
@ -46,7 +46,6 @@ dependencies {
|
||||
|
||||
implementation(project(":search"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":icons"))
|
||||
implementation(project(":preferences"))
|
||||
implementation(project(":ktx"))
|
||||
implementation(project(":badges"))
|
||||
|
||||
@ -40,6 +40,7 @@ dependencies {
|
||||
implementation(libs.androidx.core)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.palette)
|
||||
implementation(libs.materialcomponents)
|
||||
|
||||
implementation(libs.bundles.androidx.lifecycle)
|
||||
|
||||
@ -50,6 +51,7 @@ dependencies {
|
||||
implementation(project(":ktx"))
|
||||
implementation(project(":base"))
|
||||
implementation(project(":search"))
|
||||
implementation(project(":applications"))
|
||||
implementation(project(":crashreporter"))
|
||||
|
||||
}
|
||||
@ -54,203 +54,6 @@ class IconPackManager(
|
||||
selectedIconPack = iconPack
|
||||
}
|
||||
|
||||
fun getIcon(context: Context, activity: LauncherActivityInfo, size: Int): LauncherIcon? {
|
||||
if (selectedIconPack.isEmpty()) return getDefaultIcon(context, activity)
|
||||
return getFromPack(context, activity, size) ?: generateIcon(context, activity, size)
|
||||
}
|
||||
|
||||
private fun getFromPack(
|
||||
context: Context,
|
||||
activity: LauncherActivityInfo,
|
||||
size: Int
|
||||
): LauncherIcon? {
|
||||
val res = try {
|
||||
context.packageManager.getResourcesForApplication(selectedIconPack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.e("MM20", "Icon pack package $selectedIconPack not found!")
|
||||
return getDefaultIcon(context, activity)
|
||||
}
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
val component = ComponentName(activity.applicationInfo.packageName, activity.name)
|
||||
val icon = iconDao.getIcon(component.flattenToString(), selectedIconPack)
|
||||
?: return generateIcon(context, activity, size)
|
||||
|
||||
if (icon.type == "calendar") {
|
||||
return getIconPackCalendarIcon(context, icon.iconPack, icon.drawable ?: return null)?.also {
|
||||
dynamicIconController.registerIcon(it)
|
||||
}
|
||||
}
|
||||
val drawableName = icon.drawable
|
||||
val resId = res.getIdentifier(drawableName, "drawable", selectedIconPack).takeIf { it != 0 }
|
||||
?: return generateIcon(context, activity, size)
|
||||
val drawable = ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null
|
||||
return when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable -> {
|
||||
LauncherIcon(
|
||||
foreground = drawable.foreground,
|
||||
background = drawable.background,
|
||||
foregroundScale = 1.5f,
|
||||
backgroundScale = 1.5f
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
LauncherIcon(
|
||||
foreground = drawable,
|
||||
foregroundScale = getScale(),
|
||||
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun generateIcon(
|
||||
context: Context,
|
||||
activity: LauncherActivityInfo,
|
||||
size: Int
|
||||
): LauncherIcon? {
|
||||
val back = getIconBack()
|
||||
val upon = getIconUpon()
|
||||
val mask = getIconMask()
|
||||
val scale = getPackScale()
|
||||
|
||||
if (back == null && upon == null && mask == null) {
|
||||
return getDefaultIcon(context, activity)
|
||||
}
|
||||
|
||||
val drawable = activity.getIcon(context.resources.displayMetrics.densityDpi)
|
||||
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
|
||||
val canvas = Canvas(bitmap)
|
||||
val paint = Paint()
|
||||
paint.isAntiAlias = true
|
||||
paint.isFilterBitmap = true
|
||||
paint.isDither = true
|
||||
|
||||
|
||||
var inBounds: Rect
|
||||
var outBounds: Rect
|
||||
|
||||
val icon = drawable.toBitmap(width = size, height = size)
|
||||
|
||||
inBounds = Rect(0, 0, icon.width, icon.height)
|
||||
outBounds = Rect(
|
||||
(bitmap.width * (1 - scale) * 0.5).roundToInt(),
|
||||
(bitmap.height * (1 - scale) * 0.5).roundToInt(),
|
||||
(bitmap.width - bitmap.width * (1 - scale) * 0.5).roundToInt(),
|
||||
(bitmap.height - bitmap.height * (1 - scale) * 0.5).roundToInt()
|
||||
)
|
||||
canvas.drawBitmap(icon, inBounds, outBounds, paint)
|
||||
|
||||
val pack = selectedIconPack
|
||||
val pm = context.packageManager
|
||||
val res = try {
|
||||
pm.getResourcesForApplication(pack)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return getDefaultIcon(context, activity)
|
||||
}
|
||||
|
||||
if (mask != null) {
|
||||
res.getIdentifier(mask, "drawable", pack).takeIf { it != 0 }?.let {
|
||||
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
|
||||
val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null
|
||||
val maskBmp = maskDrawable.toBitmap(size, size)
|
||||
inBounds = Rect(0, 0, maskBmp.width, maskBmp.height)
|
||||
outBounds = Rect(0, 0, bitmap.width, bitmap.height)
|
||||
canvas.drawBitmap(maskBmp, inBounds, outBounds, paint)
|
||||
}
|
||||
}
|
||||
if (upon != null) {
|
||||
res.getIdentifier(upon, "drawable", pack).takeIf { it != 0 }?.let {
|
||||
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
|
||||
val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null
|
||||
val maskBmp = maskDrawable.toBitmap(size, size)
|
||||
inBounds = Rect(0, 0, maskBmp.width, maskBmp.height)
|
||||
outBounds = Rect(0, 0, bitmap.width, bitmap.height)
|
||||
canvas.drawBitmap(maskBmp, inBounds, outBounds, paint)
|
||||
}
|
||||
}
|
||||
if (back != null) {
|
||||
res.getIdentifier(back, "drawable", pack).takeIf { it != 0 }?.let {
|
||||
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER);
|
||||
val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null
|
||||
val maskBmp = maskDrawable.toBitmap(size, size)
|
||||
inBounds = Rect(0, 0, maskBmp.width, maskBmp.height)
|
||||
outBounds = Rect(0, 0, bitmap.width, bitmap.height)
|
||||
canvas.drawBitmap(maskBmp, inBounds, outBounds, paint)
|
||||
}
|
||||
}
|
||||
|
||||
return LauncherIcon(
|
||||
foreground = BitmapDrawable(context.resources, bitmap),
|
||||
foregroundScale = getScale(),
|
||||
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getDefaultIcon(context: Context, activity: LauncherActivityInfo): LauncherIcon? {
|
||||
if (activity.applicationInfo.packageName == GOOGLE_DESK_CLOCK_PACKAGE_NAME) {
|
||||
getGoogleDeskClockIcon(context)?.let {
|
||||
dynamicIconController.registerIcon(it)
|
||||
return it
|
||||
}
|
||||
}
|
||||
getCalendarIcon(context, activity)?.let {
|
||||
dynamicIconController.registerIcon(it)
|
||||
return it
|
||||
}
|
||||
try {
|
||||
val icon = activity.getIcon(context.resources.displayMetrics.densityDpi) ?: return null
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> {
|
||||
return LauncherIcon(
|
||||
foreground = icon.foreground ?: return null,
|
||||
background = icon.background,
|
||||
foregroundScale = 1.5f,
|
||||
backgroundScale = 1.5f
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
return LauncherIcon(
|
||||
foreground = icon,
|
||||
foregroundScale = getScale(),
|
||||
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconPackCalendarIcon(
|
||||
context: Context,
|
||||
iconPack: String,
|
||||
baseIconName: String
|
||||
): CalendarDynamicLauncherIcon? {
|
||||
val resources = try {
|
||||
context.packageManager.getResourcesForApplication(iconPack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return null
|
||||
}
|
||||
val drawableIds = (1..31).map {
|
||||
val drawableName = baseIconName + it
|
||||
val id = resources.getIdentifier(drawableName, "drawable", iconPack)
|
||||
if (id == 0) return null
|
||||
id
|
||||
}.toIntArray()
|
||||
return CalendarDynamicLauncherIcon(
|
||||
context = context,
|
||||
background = ColorDrawable(0),
|
||||
foreground = ColorDrawable(0),
|
||||
foregroundScale = 1.5f,
|
||||
backgroundScale = 1.5f,
|
||||
packageName = iconPack,
|
||||
drawableIds = drawableIds,
|
||||
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getCalendarIcon(
|
||||
context: Context,
|
||||
activity: LauncherActivityInfo
|
||||
@ -289,41 +92,6 @@ class IconPackManager(
|
||||
)
|
||||
}
|
||||
|
||||
private fun getGoogleDeskClockIcon(context: Context): ClockDynamicLauncherIcon? {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null
|
||||
val pm = context.packageManager
|
||||
val appInfo =
|
||||
pm.getApplicationInfo(GOOGLE_DESK_CLOCK_PACKAGE_NAME, PackageManager.GET_META_DATA)
|
||||
?: return null
|
||||
val drawable =
|
||||
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND")
|
||||
val resources = pm.getResourcesForApplication(appInfo)
|
||||
val baseIcon = try {
|
||||
ResourcesCompat.getDrawable(resources, drawable, null) as? AdaptiveIconDrawable
|
||||
?: return null
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
val foreground = baseIcon.foreground as? LayerDrawable ?: return null
|
||||
val hourLayer =
|
||||
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX")
|
||||
val minuteLayer =
|
||||
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX")
|
||||
val secondLayer =
|
||||
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX")
|
||||
return ClockDynamicLauncherIcon(
|
||||
context = context,
|
||||
background = baseIcon.background,
|
||||
backgroundScale = 1.5f,
|
||||
foreground = foreground,
|
||||
foregroundScale = 1.5f,
|
||||
badgeNumber = 0f,
|
||||
hourLayer = hourLayer,
|
||||
minuteLayer = minuteLayer,
|
||||
secondLayer = secondLayer
|
||||
)
|
||||
}
|
||||
|
||||
private fun getScale(): Float {
|
||||
return when (LauncherPreferences.instance.iconShape) {
|
||||
IconShape.CIRCLE, IconShape.PLATFORM_DEFAULT -> 0.7f
|
||||
@ -332,29 +100,6 @@ class IconPackManager(
|
||||
}
|
||||
}
|
||||
|
||||
private fun getIconBack(): String? {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
val iconbacks = iconDao.getIconBacks(selectedIconPack)
|
||||
return iconbacks.randomElementOrNull()
|
||||
}
|
||||
|
||||
private fun getIconUpon(): String? {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
val iconupons = iconDao.getIconUpons(selectedIconPack)
|
||||
return iconupons.randomElementOrNull()
|
||||
}
|
||||
|
||||
private fun getIconMask(): String? {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
val iconmasks = iconDao.getIconMasks(selectedIconPack)
|
||||
return iconmasks.randomElementOrNull()
|
||||
}
|
||||
|
||||
private fun getPackScale(): Float {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
return iconDao.getScale(selectedIconPack) ?: 1f
|
||||
}
|
||||
|
||||
suspend fun getInstalledIconPacks(): List<IconPack> {
|
||||
return withContext(Dispatchers.IO) {
|
||||
AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map {
|
||||
|
||||
@ -5,6 +5,8 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.util.LruCache
|
||||
import de.mm20.launcher2.icons.providers.*
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
@ -12,7 +14,8 @@ import kotlinx.coroutines.flow.flow
|
||||
|
||||
class IconRepository(
|
||||
val context: Context,
|
||||
val iconPackManager: IconPackManager
|
||||
private val iconPackManager: IconPackManager,
|
||||
private val dynamicIconController: DynamicIconController,
|
||||
) {
|
||||
|
||||
private val appReceiver = object : BroadcastReceiver() {
|
||||
@ -23,6 +26,10 @@ class IconRepository(
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
|
||||
private val cache = LruCache<String, LauncherIcon>(200)
|
||||
|
||||
private var iconProviders: List<IconProvider> = listOf()
|
||||
private lateinit var placeholderProvider: IconProvider
|
||||
|
||||
init {
|
||||
requestIconPackListUpdate()
|
||||
@ -34,9 +41,9 @@ class IconRepository(
|
||||
addAction(Intent.ACTION_PACKAGE_CHANGED)
|
||||
addDataScheme("package")
|
||||
})
|
||||
recreate()
|
||||
}
|
||||
|
||||
private val cache = LruCache<String, LauncherIcon>(200)
|
||||
|
||||
fun getIcon(searchable: Searchable, size: Int): Flow<LauncherIcon> = flow {
|
||||
var icon = cache.get(searchable.key)
|
||||
@ -44,12 +51,19 @@ class IconRepository(
|
||||
emit(icon)
|
||||
return@flow
|
||||
}
|
||||
val placeholderIcon = withContext(Dispatchers.IO) {
|
||||
searchable.getPlaceholderIcon(context)
|
||||
}
|
||||
emit(placeholderIcon)
|
||||
icon = withContext(Dispatchers.IO) {
|
||||
searchable.loadIconAsync(context, size)
|
||||
|
||||
icon = placeholderProvider.getIcon(searchable, size)
|
||||
emit(icon)
|
||||
|
||||
for (provider in iconProviders) {
|
||||
val ic = provider.getIcon(searchable, size)
|
||||
if (ic != null) {
|
||||
icon = ic
|
||||
if (icon is DynamicLauncherIcon) {
|
||||
dynamicIconController.registerIcon(icon)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (icon != null) {
|
||||
cache.put(searchable.key, icon)
|
||||
@ -71,11 +85,20 @@ class IconRepository(
|
||||
}
|
||||
}
|
||||
|
||||
fun removeIconFromCache(searchable: Searchable) {
|
||||
cache.remove(searchable.key)
|
||||
}
|
||||
fun recreate() {
|
||||
placeholderProvider = PlaceholderIconProvider(context)
|
||||
val providers = mutableListOf<IconProvider>()
|
||||
|
||||
fun clearCache() {
|
||||
if (iconPackManager.selectedIconPack.isNotBlank()) {
|
||||
providers.add(IconPackIconProvider(context, iconPackManager.selectedIconPack))
|
||||
}
|
||||
|
||||
providers.add(GoogleClockIconProvider(context))
|
||||
providers.add(CalendarIconProvider(context))
|
||||
providers.add(SystemIconProvider(context))
|
||||
providers.add(placeholderProvider)
|
||||
cache.evictAll()
|
||||
|
||||
iconProviders = providers
|
||||
}
|
||||
}
|
||||
@ -6,5 +6,5 @@ import org.koin.dsl.module
|
||||
val iconsModule = module {
|
||||
single { DynamicIconController(androidContext()) }
|
||||
single { IconPackManager(androidContext(), get()) }
|
||||
single { IconRepository(androidContext(), get()) }
|
||||
single { IconRepository(androidContext(), get(), get()) }
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
package de.mm20.launcher2.icons.providers
|
||||
|
||||
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.LauncherIcon
|
||||
import de.mm20.launcher2.ktx.obtainTypedArrayOrNull
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.search.data.Application
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
|
||||
class CalendarIconProvider(val context: Context): IconProvider {
|
||||
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
|
||||
if(searchable !is Application) return null
|
||||
val component = ComponentName(searchable.`package`, searchable.activity)
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package de.mm20.launcher2.icons.providers
|
||||
|
||||
import android.content.Context
|
||||
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 androidx.core.content.res.ResourcesCompat
|
||||
import de.mm20.launcher2.icons.ClockDynamicLauncherIcon
|
||||
import de.mm20.launcher2.icons.IconPackManager
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.search.data.Application
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
|
||||
class GoogleClockIconProvider(val context: Context) : IconProvider {
|
||||
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null
|
||||
if (searchable !is Application) return null
|
||||
if (searchable.`package` != "com.google.android.deskclock") return null
|
||||
val pm = context.packageManager
|
||||
val appInfo = try {
|
||||
pm.getApplicationInfo(
|
||||
IconPackManager.GOOGLE_DESK_CLOCK_PACKAGE_NAME,
|
||||
PackageManager.GET_META_DATA
|
||||
)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return null
|
||||
}
|
||||
val drawable =
|
||||
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND")
|
||||
val resources = pm.getResourcesForApplication(appInfo)
|
||||
val baseIcon = try {
|
||||
ResourcesCompat.getDrawable(resources, drawable, null) as? AdaptiveIconDrawable
|
||||
?: return null
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
val foreground = baseIcon.foreground as? LayerDrawable ?: return null
|
||||
val hourLayer =
|
||||
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX")
|
||||
val minuteLayer =
|
||||
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX")
|
||||
val secondLayer =
|
||||
appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX")
|
||||
return ClockDynamicLauncherIcon(
|
||||
context = context,
|
||||
background = baseIcon.background,
|
||||
backgroundScale = 1.5f,
|
||||
foreground = foreground,
|
||||
foregroundScale = 1.5f,
|
||||
badgeNumber = 0f,
|
||||
hourLayer = hourLayer,
|
||||
minuteLayer = minuteLayer,
|
||||
secondLayer = secondLayer
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,207 @@
|
||||
package de.mm20.launcher2.icons.providers
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.pm.LauncherActivityInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.Resources
|
||||
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.ktx.randomElementOrNull
|
||||
import de.mm20.launcher2.preferences.IconShape
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.search.data.LauncherApp
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class IconPackIconProvider(val context: Context, val iconPack: String): IconProvider {
|
||||
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
|
||||
if (searchable !is LauncherApp) return null
|
||||
val res = try {
|
||||
context.packageManager.getResourcesForApplication(iconPack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
Log.e("MM20", "Icon pack package $iconPack not found!")
|
||||
return null
|
||||
}
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
val component = ComponentName(searchable.`package`, searchable.activity)
|
||||
val icon = iconDao.getIcon(component.flattenToString(), iconPack)
|
||||
?: return generateIcon(context, searchable.launcherActivityInfo, size)
|
||||
|
||||
if (icon.type == "calendar") {
|
||||
return getIconPackCalendarIcon(context, icon.drawable ?: return null)
|
||||
}
|
||||
val drawableName = icon.drawable
|
||||
val resId = res.getIdentifier(drawableName, "drawable", iconPack).takeIf { it != 0 }
|
||||
?: return generateIcon(context, searchable.launcherActivityInfo, size)
|
||||
val drawable = ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null
|
||||
return when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable -> {
|
||||
LauncherIcon(
|
||||
foreground = drawable.foreground,
|
||||
background = drawable.background,
|
||||
foregroundScale = 1.5f,
|
||||
backgroundScale = 1.5f
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
LauncherIcon(
|
||||
foreground = drawable,
|
||||
foregroundScale = getScale(),
|
||||
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getScale(): Float {
|
||||
return when (LauncherPreferences.instance.iconShape) {
|
||||
IconShape.CIRCLE, IconShape.PLATFORM_DEFAULT -> 0.7f
|
||||
else -> 0.8f
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun generateIcon(
|
||||
context: Context,
|
||||
activity: LauncherActivityInfo,
|
||||
size: Int
|
||||
): LauncherIcon? {
|
||||
val back = getIconBack()
|
||||
val upon = getIconUpon()
|
||||
val mask = getIconMask()
|
||||
val scale = getPackScale()
|
||||
|
||||
if (back == null && upon == null && mask == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
val drawable = activity.getIcon(context.resources.displayMetrics.densityDpi)
|
||||
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
|
||||
|
||||
val canvas = Canvas(bitmap)
|
||||
val paint = Paint()
|
||||
paint.isAntiAlias = true
|
||||
paint.isFilterBitmap = true
|
||||
paint.isDither = true
|
||||
|
||||
|
||||
var inBounds: Rect
|
||||
var outBounds: Rect
|
||||
|
||||
val icon = drawable.toBitmap(width = size, height = size)
|
||||
|
||||
inBounds = Rect(0, 0, icon.width, icon.height)
|
||||
outBounds = Rect(
|
||||
(bitmap.width * (1 - scale) * 0.5).roundToInt(),
|
||||
(bitmap.height * (1 - scale) * 0.5).roundToInt(),
|
||||
(bitmap.width - bitmap.width * (1 - scale) * 0.5).roundToInt(),
|
||||
(bitmap.height - bitmap.height * (1 - scale) * 0.5).roundToInt()
|
||||
)
|
||||
canvas.drawBitmap(icon, inBounds, outBounds, paint)
|
||||
|
||||
val pack = iconPack
|
||||
val pm = context.packageManager
|
||||
val res = try {
|
||||
pm.getResourcesForApplication(pack)
|
||||
} catch (e: Resources.NotFoundException) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (mask != null) {
|
||||
res.getIdentifier(mask, "drawable", pack).takeIf { it != 0 }?.let {
|
||||
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OUT)
|
||||
val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null
|
||||
val maskBmp = maskDrawable.toBitmap(size, size)
|
||||
inBounds = Rect(0, 0, maskBmp.width, maskBmp.height)
|
||||
outBounds = Rect(0, 0, bitmap.width, bitmap.height)
|
||||
canvas.drawBitmap(maskBmp, inBounds, outBounds, paint)
|
||||
}
|
||||
}
|
||||
if (upon != null) {
|
||||
res.getIdentifier(upon, "drawable", pack).takeIf { it != 0 }?.let {
|
||||
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER);
|
||||
val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null
|
||||
val maskBmp = maskDrawable.toBitmap(size, size)
|
||||
inBounds = Rect(0, 0, maskBmp.width, maskBmp.height)
|
||||
outBounds = Rect(0, 0, bitmap.width, bitmap.height)
|
||||
canvas.drawBitmap(maskBmp, inBounds, outBounds, paint)
|
||||
}
|
||||
}
|
||||
if (back != null) {
|
||||
res.getIdentifier(back, "drawable", pack).takeIf { it != 0 }?.let {
|
||||
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER);
|
||||
val maskDrawable = ResourcesCompat.getDrawable(res, it, null) ?: return null
|
||||
val maskBmp = maskDrawable.toBitmap(size, size)
|
||||
inBounds = Rect(0, 0, maskBmp.width, maskBmp.height)
|
||||
outBounds = Rect(0, 0, bitmap.width, bitmap.height)
|
||||
canvas.drawBitmap(maskBmp, inBounds, outBounds, paint)
|
||||
}
|
||||
}
|
||||
|
||||
return LauncherIcon(
|
||||
foreground = BitmapDrawable(context.resources, bitmap),
|
||||
foregroundScale = getScale(),
|
||||
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getIconBack(): String? {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
val iconbacks = iconDao.getIconBacks(iconPack)
|
||||
return iconbacks.randomElementOrNull()
|
||||
}
|
||||
|
||||
private suspend fun getIconUpon(): String? {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
val iconupons = iconDao.getIconUpons(iconPack)
|
||||
return iconupons.randomElementOrNull()
|
||||
}
|
||||
|
||||
private suspend fun getIconMask(): String? {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
val iconmasks = iconDao.getIconMasks(iconPack)
|
||||
return iconmasks.randomElementOrNull()
|
||||
}
|
||||
|
||||
private suspend fun getPackScale(): Float {
|
||||
val iconDao = AppDatabase.getInstance(context).iconDao()
|
||||
return iconDao.getScale(iconPack) ?: 1f
|
||||
}
|
||||
|
||||
private fun getIconPackCalendarIcon(
|
||||
context: Context,
|
||||
baseIconName: String
|
||||
): CalendarDynamicLauncherIcon? {
|
||||
val resources = try {
|
||||
context.packageManager.getResourcesForApplication(iconPack)
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
return null
|
||||
}
|
||||
val drawableIds = (1..31).map {
|
||||
val drawableName = baseIconName + it
|
||||
val id = resources.getIdentifier(drawableName, "drawable", iconPack)
|
||||
if (id == 0) return null
|
||||
id
|
||||
}.toIntArray()
|
||||
return CalendarDynamicLauncherIcon(
|
||||
context = context,
|
||||
background = ColorDrawable(0),
|
||||
foreground = ColorDrawable(0),
|
||||
foregroundScale = 1.5f,
|
||||
backgroundScale = 1.5f,
|
||||
packageName = iconPack,
|
||||
drawableIds = drawableIds,
|
||||
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package de.mm20.launcher2.icons.providers
|
||||
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
|
||||
interface IconProvider {
|
||||
suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon?
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package de.mm20.launcher2.icons.providers
|
||||
|
||||
import android.content.Context
|
||||
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? {
|
||||
return searchable.getPlaceholderIcon(context)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package de.mm20.launcher2.icons.providers
|
||||
|
||||
import android.content.Context
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
|
||||
class SystemIconProvider(val context: Context) : IconProvider {
|
||||
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
|
||||
return searchable.loadIconAsync(context, size)
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user