diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/AppInstallation.kt b/applications/src/main/java/de/mm20/launcher2/search/data/AppInstallation.kt index c014aac5..ecc2dabe 100644 --- a/applications/src/main/java/de/mm20/launcher2/search/data/AppInstallation.kt +++ b/applications/src/main/java/de/mm20/launcher2/search/data/AppInstallation.kt @@ -3,24 +3,22 @@ package de.mm20.launcher2.search.data import android.content.Context import android.content.Intent import android.content.pm.PackageInstaller +import android.graphics.Color import android.graphics.ColorMatrix import android.graphics.ColorMatrixColorFilter import android.graphics.drawable.BitmapDrawable -import android.graphics.drawable.ColorDrawable import androidx.core.content.ContextCompat import de.mm20.launcher2.applications.R -import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.preferences.Settings -import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground +import de.mm20.launcher2.icons.* class AppInstallation( - val session: PackageInstaller.SessionInfo + val session: PackageInstaller.SessionInfo ) : Application( - label = session.appLabel?.toString() ?: "", - `package` = session.appPackageName ?: "", - activity = "", - flags = 0, - version = null + label = session.appLabel?.toString() ?: "", + `package` = session.appPackageName ?: "", + activity = "", + flags = 0, + version = null ) { override val key: String @@ -30,22 +28,31 @@ class AppInstallation( return session.createDetailsIntent() } - override fun getPlaceholderIcon(context: Context): LauncherIcon { - return LauncherIcon( - foreground = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, - background = ColorDrawable(ContextCompat.getColor(context, R.color.grey)), - foregroundScale = 0.5f) + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { + return StaticLauncherIcon( + foregroundLayer = TintedIconLayer( + icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, + scale = 0.5f, + color = Color.WHITE + ), + backgroundLayer = ColorLayer(ContextCompat.getColor(context, R.color.grey)) + ) } - override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? { + override suspend fun loadIcon( + context: Context, + size: Int, + ): LauncherIcon { val icon = session.appIcon ?: return getPlaceholderIcon(context) val foreground = BitmapDrawable(context.resources, icon) foreground.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { setSaturation(0f) }) - return LauncherIcon( - foreground = foreground, - background = ColorDrawable(ContextCompat.getColor(context, R.color.grey)) + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = foreground, + ), + backgroundLayer = ColorLayer(ContextCompat.getColor(context, R.color.grey)) ) } diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/Application.kt b/applications/src/main/java/de/mm20/launcher2/search/data/Application.kt index 2a09ebac..81af746c 100644 --- a/applications/src/main/java/de/mm20/launcher2/search/data/Application.kt +++ b/applications/src/main/java/de/mm20/launcher2/search/data/Application.kt @@ -4,12 +4,13 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.pm.PackageManager -import android.graphics.drawable.ColorDrawable -import android.util.Log +import android.graphics.Color import androidx.core.content.ContextCompat import de.mm20.launcher2.applications.R import de.mm20.launcher2.compat.PackageManagerCompat -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.ColorLayer +import de.mm20.launcher2.icons.StaticLauncherIcon +import de.mm20.launcher2.icons.TintedIconLayer import org.json.JSONObject abstract class Application( @@ -34,11 +35,14 @@ abstract class Application( return intent } - override fun getPlaceholderIcon(context: Context): LauncherIcon { - return LauncherIcon( - foreground = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, - background = ColorDrawable(ContextCompat.getColor(context, R.color.lightgreen)), - foregroundScale = 0.5f + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { + return StaticLauncherIcon( + foregroundLayer = TintedIconLayer( + icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, + scale = 0.5f, + color = Color.WHITE, + ), + backgroundLayer = ColorLayer(ContextCompat.getColor(context, R.color.android_green)) ) } @@ -56,7 +60,10 @@ abstract class Application( get() = "app://$`package`:$activity" companion object { - internal fun getStoreLinkForInstaller(installerPackage: String?, packageName: String?): StoreLink? { + internal fun getStoreLinkForInstaller( + installerPackage: String?, + packageName: String? + ): StoreLink? { if (packageName == null) return null return when (installerPackage) { "de.amazon.mShop.android", "com.amazon.venezia" -> { diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt b/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt index bc157cd1..f030206b 100644 --- a/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt +++ b/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt @@ -11,9 +11,8 @@ import android.os.Bundle import android.os.Process import android.os.UserHandle import androidx.core.content.getSystemService -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.* import de.mm20.launcher2.ktx.getSerialNumber -import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent @@ -45,7 +44,6 @@ class LauncherApp( override suspend fun loadIcon( context: Context, size: Int, - legacyIconBackground: LegacyIconBackground ): LauncherIcon? { try { val icon = @@ -54,17 +52,23 @@ class LauncherApp( } ?: return null if (icon is AdaptiveIconDrawable) { - return LauncherIcon( - foreground = icon.foreground ?: return null, - background = icon.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = icon.foreground, + scale = 1.5f, + ), + backgroundLayer = StaticIconLayer( + icon = icon.background, + scale = 1.5f, + ) ) } else { - return LauncherIcon( - foreground = icon, - foregroundScale = 0.7f, - autoGenerateBackgroundMode = legacyIconBackground.number + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = icon, + scale = 1f, + ), + backgroundLayer = TransparentLayer ) } } catch (e: PackageManager.NameNotFoundException) { diff --git a/appshortcuts/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt b/appshortcuts/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt index 56701e5d..3cee36ca 100644 --- a/appshortcuts/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt +++ b/appshortcuts/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt @@ -4,6 +4,7 @@ import android.content.Context import android.content.Intent import android.content.pm.LauncherApps import android.content.pm.ShortcutInfo +import android.graphics.Color import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.ColorDrawable import android.os.Bundle @@ -11,9 +12,8 @@ import android.os.Process import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import de.mm20.launcher2.appshortcuts.R -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.* import de.mm20.launcher2.ktx.getSerialNumber -import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext @@ -51,18 +51,20 @@ class AppShortcut( return true } - override fun getPlaceholderIcon(context: Context): LauncherIcon { - return LauncherIcon( - foreground = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, - background = ColorDrawable(ContextCompat.getColor(context, R.color.green)), - foregroundScale = 0.5f + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { + return StaticLauncherIcon( + foregroundLayer = TintedIconLayer( + color = Color.WHITE, + icon = ContextCompat.getDrawable(context, R.drawable.ic_file_android)!!, + scale = 0.5f, + ), + backgroundLayer = ColorLayer(ContextCompat.getColor(context, R.color.green)), ) } override suspend fun loadIcon( context: Context, size: Int, - legacyIconBackground: LegacyIconBackground ): LauncherIcon? { val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps val icon = withContext(Dispatchers.IO) { @@ -72,17 +74,23 @@ class AppShortcut( ) } ?: return null if (icon is AdaptiveIconDrawable) { - return LauncherIcon( - foreground = icon.foreground, - background = icon.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = icon.foreground, + scale = 1.5f, + ), + backgroundLayer = StaticIconLayer( + icon = icon.background, + scale = 1.5f, + ) ) } - return LauncherIcon( - foreground = icon, - foregroundScale = 1f, - autoGenerateBackgroundMode = legacyIconBackground.number + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = icon, + scale = 1f + ), + backgroundLayer = TransparentLayer ) } } \ No newline at end of file diff --git a/base/src/main/java/de/mm20/launcher2/icons/LauncherIcon.kt b/base/src/main/java/de/mm20/launcher2/icons/LauncherIcon.kt index 80ca2858..dde2bd04 100644 --- a/base/src/main/java/de/mm20/launcher2/icons/LauncherIcon.kt +++ b/base/src/main/java/de/mm20/launcher2/icons/LauncherIcon.kt @@ -1,85 +1,14 @@ package de.mm20.launcher2.icons -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.Drawable -import androidx.core.graphics.drawable.toBitmap -import androidx.palette.graphics.Palette -import java.lang.ref.WeakReference +import android.content.res.Resources -open class LauncherIcon( - foreground: Drawable, - background: Drawable? = null, - foregroundScale: Float = 1f, - backgroundScale: Float = 1f, - var autoGenerateBackgroundMode: Int = BACKGROUND_WHITE, - val isThemeable: Boolean = false, -) { +sealed interface LauncherIcon - var foreground = foreground - set(value) { - field = value - updateBackgroundColor() - notifyCallbacks() - } +data class StaticLauncherIcon( + val foregroundLayer: LauncherIconLayer, + val backgroundLayer: LauncherIconLayer, +): LauncherIcon - private fun updateBackgroundColor() { - if (background == null) { - when (autoGenerateBackgroundMode) { - BACKGROUND_DYNAMIC -> { - val palette = Palette - .from(foreground.toBitmap()) - .generate() - this.background = ColorDrawable(palette.getDominantColor(Color.WHITE)) - badgeColor = palette.getLightVibrantColor(0xFFF0F0F0.toInt()) - } - BACKGROUND_WHITE -> this.background = ColorDrawable(Color.WHITE) - else -> this.foregroundScale = 1f - } - } - } - - var background = background - set(value) { - field = value - notifyCallbacks() - } - - var foregroundScale = foregroundScale - set(value) { - field = value - notifyCallbacks() - } - - var backgroundScale = backgroundScale - set(value) { - field = value - notifyCallbacks() - } - - private val callbacks = mutableListOf Unit>>() - - fun registerCallback(callback: (LauncherIcon) -> Unit) { - callbacks.add(WeakReference(callback)) - } - - protected fun notifyCallbacks() { - val iterator = callbacks.iterator() - while(iterator.hasNext()) { - val callback = iterator.next() - callback.get()?.invoke(this) ?: iterator.remove() - } - } - - var badgeColor: Int = 0xFFF0F0F0.toInt() - - init { - updateBackgroundColor() - } - - companion object { - const val BACKGROUND_NONE = 1 - const val BACKGROUND_DYNAMIC = 0 - const val BACKGROUND_WHITE = 2 - } +interface DynamicLauncherIcon: LauncherIcon { + suspend fun getIcon(time: Long): StaticLauncherIcon } \ No newline at end of file diff --git a/base/src/main/java/de/mm20/launcher2/icons/LauncherIconLayer.kt b/base/src/main/java/de/mm20/launcher2/icons/LauncherIconLayer.kt new file mode 100644 index 00000000..5ba76449 --- /dev/null +++ b/base/src/main/java/de/mm20/launcher2/icons/LauncherIconLayer.kt @@ -0,0 +1,50 @@ +package de.mm20.launcher2.icons + +import android.graphics.drawable.Drawable + +sealed interface LauncherIconLayer + +data class StaticIconLayer( + val icon: Drawable, + val scale: Float = 1f, +) : LauncherIconLayer + +data class ColorLayer( + val color: Int = 0, +) : LauncherIconLayer + +data class ClockLayer( + val sublayers: List, + val scale: Float, +) : LauncherIconLayer + +data class ClockSublayer( + val drawable: Drawable, + val role: ClockSublayerRole +) + +enum class ClockSublayerRole { + Hour, + Minute, + Second, + Static, +} + +data class TintedIconLayer( + val icon: Drawable, + val scale: Float = 0.5f, + val color: Int = 0 +) : LauncherIconLayer + +data class TintedClockLayer( + val sublayers: List, + val scale: Float, + val color: Int = 0, +) : LauncherIconLayer + +data class TextLayer( + val text: String, + val color: Int = 0, +) : LauncherIconLayer + +object TransparentLayer: LauncherIconLayer \ No newline at end of file diff --git a/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt b/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt index de2d1b06..cfce81f3 100644 --- a/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt +++ b/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt @@ -6,23 +6,14 @@ import android.content.Intent import android.graphics.Color import android.graphics.Typeface import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.LayerDrawable import android.provider.CalendarContract -import android.text.format.DateFormat -import androidx.core.content.ContextCompat -import androidx.core.graphics.ColorUtils -import androidx.core.graphics.blue -import androidx.core.graphics.green -import androidx.core.graphics.red -import de.mm20.launcher2.calendar.R import de.mm20.launcher2.graphics.TextDrawable -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.ColorLayer +import de.mm20.launcher2.icons.StaticLauncherIcon +import de.mm20.launcher2.icons.TextLayer import de.mm20.launcher2.ktx.dp -import hct.Hct import palettes.TonalPalette -import scheme.Scheme import java.text.SimpleDateFormat -import java.util.* class CalendarEvent( override val label: String, @@ -41,21 +32,14 @@ class CalendarEvent( get() = "calendar://$id" - override fun getPlaceholderIcon(context: Context): LauncherIcon { + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { val df = SimpleDateFormat("dd") - val s = (48 * context.dp).toInt() - val foreground = TextDrawable( - df.format(startTime), - color = Color.WHITE, - fontSize = 24 * context.dp, - typeface = Typeface.DEFAULT_BOLD, - height = s - ) - val background = ColorDrawable(getDisplayColor()) - return LauncherIcon( - foreground = foreground, - background = background, - foregroundScale = 0.74f + return StaticLauncherIcon( + foregroundLayer = TextLayer( + text = df.format(startTime), + color = Color.WHITE + ), + backgroundLayer = ColorLayer(getDisplayColor()) ) } diff --git a/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt b/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt index f6b82dac..e5a1ac7b 100644 --- a/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt +++ b/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt @@ -4,20 +4,14 @@ import android.content.ContentUris import android.content.Context import android.content.Intent import android.graphics.Color -import android.graphics.Typeface -import android.graphics.drawable.ColorDrawable import android.net.Uri import android.provider.ContactsContract import androidx.core.content.ContextCompat import androidx.core.database.getStringOrNull import androidx.core.graphics.drawable.toDrawable import de.mm20.launcher2.contacts.R -import de.mm20.launcher2.graphics.TextDrawable -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.* import de.mm20.launcher2.ktx.asBitmap -import de.mm20.launcher2.ktx.dp -import de.mm20.launcher2.ktx.sp -import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.net.URLEncoder @@ -44,25 +38,19 @@ class Contact( return phones.union(emails).joinToString(separator = ", ") { it.label } } - override fun getPlaceholderIcon(context: Context): LauncherIcon { + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { val iconText = if (firstName.isNotEmpty()) firstName[0].toString() else "" + if (lastName.isNotEmpty()) lastName[0].toString() else "" - return LauncherIcon( - foreground = TextDrawable( - iconText, - Color.WHITE, - fontSize = 20 * context.sp, - height = (48 * context.dp).toInt(), - typeface = Typeface.DEFAULT_BOLD - ), - background = ColorDrawable(ContextCompat.getColor(context, R.color.blue)) + + return StaticLauncherIcon( + foregroundLayer = TextLayer(text = iconText, color = Color.WHITE), + backgroundLayer = ColorLayer(ContextCompat.getColor(context, R.color.blue)) ) } override suspend fun loadIcon( context: Context, size: Int, - legacyIconBackground: LegacyIconBackground ): LauncherIcon? { val contentResolver = context.contentResolver val bmp = withContext(Dispatchers.IO) { @@ -71,10 +59,12 @@ class Contact( ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, uri, false) ?.asBitmap() } ?: return null - return LauncherIcon( - foreground = bmp.toDrawable(context.resources), - background = null, - autoGenerateBackgroundMode = legacyIconBackground.number + + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = bmp.toDrawable(context.resources), + ), + backgroundLayer = ColorLayer() ) } @@ -160,7 +150,10 @@ class Contact( val data3 = dataCursor.getStringOrNull(data3Column) ?: continue@loop telegram.add( - ContactInfo(data3.substringAfterLast(" "), "tg:openmessage?user_id=$data1") + ContactInfo( + data3.substringAfterLast(" "), + "tg:openmessage?user_id=$data1" + ) ) } "vnd.android.cursor.item/vnd.com.whatsapp.profile" -> { diff --git a/files/src/main/java/de/mm20/launcher2/search/data/File.kt b/files/src/main/java/de/mm20/launcher2/search/data/File.kt index eae054a9..29ee8784 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/File.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/File.kt @@ -1,10 +1,12 @@ package de.mm20.launcher2.search.data import android.content.Context -import android.graphics.drawable.ColorDrawable +import android.graphics.Color import androidx.core.content.ContextCompat import de.mm20.launcher2.files.R -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.ColorLayer +import de.mm20.launcher2.icons.StaticLauncherIcon +import de.mm20.launcher2.icons.TintedIconLayer import java.util.* abstract class File( @@ -19,7 +21,7 @@ abstract class File( open val providerIconRes: Int? = null - override fun getPlaceholderIcon(context: Context): LauncherIcon { + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { val (resId, bgColor) = when { isDirectory -> R.drawable.ic_file_folder to R.color.lightblue mimeType.startsWith("image/") -> R.drawable.ic_file_picture to R.color.teal @@ -47,17 +49,23 @@ abstract class File( else -> R.drawable.ic_file_generic to R.color.bluegrey } } - return LauncherIcon( - foreground = context.getDrawable(resId)!!, - background = ColorDrawable(ContextCompat.getColor(context, bgColor)), - foregroundScale = 0.5f + return StaticLauncherIcon( + foregroundLayer = TintedIconLayer( + icon = ContextCompat.getDrawable(context, resId)!!, + scale = 0.5f, + color = Color.WHITE + ), + backgroundLayer = ColorLayer(ContextCompat.getColor(context, bgColor)) ) } fun getFileType(context: Context): String { if (isDirectory) return context.getString(R.string.file_type_directory) if (mimeType == "application/vendor.de.mm20.launcher2.backup") { - return context.getString(R.string.file_type_launcherbackup, context.getString(R.string.app_name)) + return context.getString( + R.string.file_type_launcherbackup, + context.getString(R.string.app_name) + ) } val resource = when (mimeType) { "application/zip", @@ -107,7 +115,10 @@ abstract class File( } if (resource == R.string.file_type_none && label.matches(Regex(".+\\..+"))) { val extension = label.substringAfterLast(".").toUpperCase(Locale.getDefault()) - if (extension == "kvaesitso") return context.getString(R.string.file_type_launcherbackup, context.getString(R.string.app_name)) + if (extension == "kvaesitso") return context.getString( + R.string.file_type_launcherbackup, + context.getString(R.string.app_name) + ) return context.getString(R.string.file_type_generic, extension) } return context.getString(resource) diff --git a/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt index 3aab3da7..be19685f 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/LocalFile.kt @@ -15,10 +15,12 @@ import android.util.Size import androidx.core.content.FileProvider import androidx.exifinterface.media.ExifInterface import de.mm20.launcher2.files.R +import de.mm20.launcher2.icons.ColorLayer import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.StaticIconLayer +import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.ktx.formatToString import de.mm20.launcher2.media.ThumbnailUtilsCompat -import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent @@ -43,7 +45,6 @@ open class LocalFile( override suspend fun loadIcon( context: Context, size: Int, - legacyIconBackground: LegacyIconBackground ): LauncherIcon? { if (!JavaIOFile(path).exists()) return null when { @@ -55,9 +56,12 @@ open class LocalFile( ) } ?: return null - return LauncherIcon( - foreground = BitmapDrawable(context.resources, thumbnail), - autoGenerateBackgroundMode = legacyIconBackground.number + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = BitmapDrawable(context.resources, thumbnail), + scale = 1f, + ), + backgroundLayer = ColorLayer() ) } mimeType.startsWith("video/") -> { @@ -67,9 +71,13 @@ open class LocalFile( Size(size, size) ) } ?: return null - return LauncherIcon( - foreground = BitmapDrawable(context.resources, thumbnail), - autoGenerateBackgroundMode = legacyIconBackground.number + + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = BitmapDrawable(context.resources, thumbnail), + scale = 1f, + ), + backgroundLayer = ColorLayer() ) } mimeType.startsWith("audio/") -> { @@ -94,9 +102,13 @@ open class LocalFile( } thumbnail ?: return null - return LauncherIcon( - foreground = BitmapDrawable(context.resources, thumbnail), - autoGenerateBackgroundMode = legacyIconBackground.number + + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = BitmapDrawable(context.resources, thumbnail), + scale = 1f, + ), + backgroundLayer = ColorLayer() ) } @@ -107,18 +119,24 @@ open class LocalFile( } ?: return null when { icon is AdaptiveIconDrawable -> { - return LauncherIcon( - foreground = icon.foreground, - background = icon.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = icon.foreground, + scale = 1.5f, + ), + backgroundLayer = StaticIconLayer( + icon = icon.background, + scale = 1.5f, + ) ) } else -> { - return LauncherIcon( - foreground = icon, - foregroundScale = 0.7f, - autoGenerateBackgroundMode = legacyIconBackground.number + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = icon, + scale = 0.7f, + ), + backgroundLayer = ColorLayer() ) } } diff --git a/i18n/src/main/res/values-de/strings.xml b/i18n/src/main/res/values-de/strings.xml index d444fc2b..a5d9f9d7 100644 --- a/i18n/src/main/res/values-de/strings.xml +++ b/i18n/src/main/res/values-de/strings.xml @@ -188,8 +188,6 @@ Starkregen und Gewitter Wind Unbekannt - Symbol-Hintergrund - Stil von Legacy-Icons Hier gibt es keine Easter Eggs, es sei denn Ihr hättet sie mitgebracht. Bitte, hör auf, du verschwendest deine Zeit Ich werde es nicht noch einmal sagen: hier sind definitiv keine Easter Eggs versteckt diff --git a/i18n/src/main/res/values-fr/strings.xml b/i18n/src/main/res/values-fr/strings.xml index f543f0f0..2b703fd7 100644 --- a/i18n/src/main/res/values-fr/strings.xml +++ b/i18n/src/main/res/values-fr/strings.xml @@ -173,8 +173,6 @@ Par %1$s Installation en cours… (%1$s) Impossible d\'ouvrir %1$s - Arrière-plan de l\'icône - Style d\'icône legacy Il n\'y a pas d\'easter egg ici, à moins que vous ne l\'ayez amené avec vous. Par pitié, arrêtez, vous perdez votre temps diff --git a/i18n/src/main/res/values-it/strings.xml b/i18n/src/main/res/values-it/strings.xml index 8c6f91de..9eff2cab 100644 --- a/i18n/src/main/res/values-it/strings.xml +++ b/i18n/src/main/res/values-it/strings.xml @@ -291,7 +291,6 @@ Accedi per cercare sul tuo server Owncloud Gruppo Telegram Repository F-Droid - Sfondo icona Licenza Griglia Numero di colonne @@ -303,7 +302,6 @@ Musica Orologio Verticale - Stile icone legacy Concesso in licenza sotto la GNU General Public License 3.0 Stile Seleziona un orologio diff --git a/i18n/src/main/res/values-pl/strings.xml b/i18n/src/main/res/values-pl/strings.xml index 76c3c059..3d7be514 100644 --- a/i18n/src/main/res/values-pl/strings.xml +++ b/i18n/src/main/res/values-pl/strings.xml @@ -196,7 +196,6 @@ Zaloguj się do Owncloud Grupa Telegram Repozytorium F-Droid - Tło ikon Zaloguj się, aby móc przeszukiwać twój serwer Owncloud Ta aplikacja jest wolnym oprogramowaniem. Siatka @@ -212,7 +211,6 @@ Układ Pionowy Poziomy - Przestarzały styl ikon Wybierz typ zegara Data Pokaż aktualną datę diff --git a/i18n/src/main/res/values-pt-rBR/strings.xml b/i18n/src/main/res/values-pt-rBR/strings.xml index d54d1ea3..ee5c53ca 100644 --- a/i18n/src/main/res/values-pt-rBR/strings.xml +++ b/i18n/src/main/res/values-pt-rBR/strings.xml @@ -294,7 +294,6 @@ Owncloud Autenticar para pesquisar no seu servidor Owncloud Repositório F-Droid - Fundo do ícone Licença Este aplicativo é um software livre. Licenciado sob a GNU General Public License 3.0 diff --git a/i18n/src/main/res/values-ro/strings.xml b/i18n/src/main/res/values-ro/strings.xml index 0a7af77e..afed9142 100644 --- a/i18n/src/main/res/values-ro/strings.xml +++ b/i18n/src/main/res/values-ro/strings.xml @@ -278,8 +278,6 @@ Conectează-te pentru a căuta în serverul tău Owncloud Grup Telegram Repository F-Droid - Fundal pictogramă - Stilul pictogramei clasice Licență Această aplicație este un software gratuit. Grilă diff --git a/i18n/src/main/res/values-sv/strings.xml b/i18n/src/main/res/values-sv/strings.xml index c29a0171..f2fcfb3f 100644 --- a/i18n/src/main/res/values-sv/strings.xml +++ b/i18n/src/main/res/values-sv/strings.xml @@ -260,8 +260,6 @@ Logga in på Owncloud Telegram-grupp F-Droid-kodförråd - Ikonbakgrund - Äldre ikonstil Licens Denna app är fri programvara. Licensierad under GNU General Public License 3.0 diff --git a/i18n/src/main/res/values-zh-rCN/strings.xml b/i18n/src/main/res/values-zh-rCN/strings.xml index aefd261a..74afd553 100644 --- a/i18n/src/main/res/values-zh-rCN/strings.xml +++ b/i18n/src/main/res/values-zh-rCN/strings.xml @@ -294,8 +294,6 @@ 登陆以查找你的Owncloud服务 Telegram群组 F-Droid repository - 图标背景 - 经典图标风格 许可证 根据 GNU 通用公共许可证 3.0 授权 网格 diff --git a/i18n/src/main/res/values/strings.xml b/i18n/src/main/res/values/strings.xml index 428579f7..9430665b 100644 --- a/i18n/src/main/res/values/strings.xml +++ b/i18n/src/main/res/values/strings.xml @@ -491,8 +491,8 @@ Sign in to search your Owncloud server Telegram group F-Droid repository - Icon background - Legacy icon style + Enforce shape + Apply shape to all icons including those that would normally not support it License This app is free software. Licensed under the GNU General Public License 3.0 diff --git a/icons/src/main/java/de/mm20/launcher2/icons/CalendarDynamicLauncherIcon.kt b/icons/src/main/java/de/mm20/launcher2/icons/CalendarDynamicLauncherIcon.kt deleted file mode 100644 index 462e05c0..00000000 --- a/icons/src/main/java/de/mm20/launcher2/icons/CalendarDynamicLauncherIcon.kt +++ /dev/null @@ -1,53 +0,0 @@ -package de.mm20.launcher2.icons - -import android.content.Context -import android.content.pm.PackageManager -import android.graphics.drawable.AdaptiveIconDrawable -import android.graphics.drawable.Drawable -import android.os.Build -import de.mm20.launcher2.ktx.getDrawableOrNull -import java.util.* -import java.util.concurrent.Executors - -class CalendarDynamicLauncherIcon( - foreground: Drawable, - background: Drawable?, - foregroundScale: Float, - backgroundScale: Float, - val packageName: String, - val drawableIds: IntArray, -) : DynamicLauncherIcon( - foreground, - background, - foregroundScale, - backgroundScale, -) { - - var currentDay = 0 - override fun update(context: Context) { - val calendar = Calendar.getInstance() - val day = calendar[Calendar.DAY_OF_MONTH] - if (day == currentDay || drawableIds.size < currentDay) return - val resources = try { - context.packageManager.getResourcesForApplication(packageName) - } catch (e: PackageManager.NameNotFoundException) { - return - } - Executors.newSingleThreadExecutor().execute { - val currentDayDrawable = resources.getDrawableOrNull(drawableIds[day - 1]) - ?: return@execute - if (currentDayDrawable is AdaptiveIconDrawable) { - foreground = currentDayDrawable.foreground - background = currentDayDrawable.background - foregroundScale = 1.5f - backgroundScale = 1.5f - } else { - foregroundScale = 1f - backgroundScale = 1f - background = null - foreground = currentDayDrawable - } - } - currentDay = day - } -} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/ClockDynamicLauncherIcon.kt b/icons/src/main/java/de/mm20/launcher2/icons/ClockDynamicLauncherIcon.kt deleted file mode 100644 index 6c48c52d..00000000 --- a/icons/src/main/java/de/mm20/launcher2/icons/ClockDynamicLauncherIcon.kt +++ /dev/null @@ -1,53 +0,0 @@ -package de.mm20.launcher2.icons - -import android.content.Context -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.Drawable -import android.graphics.drawable.LayerDrawable -import android.graphics.drawable.RotateDrawable -import android.os.Build -import androidx.annotation.RequiresApi -import java.util.* -import kotlin.math.roundToInt - -class ClockDynamicLauncherIcon( - foreground: LayerDrawable, - background: Drawable?, - foregroundScale: Float, - backgroundScale: Float, - isThemeable: Boolean = false, - val hourLayer: Int, - val minuteLayer: Int, - val secondLayer: Int -) : DynamicLauncherIcon( - foreground, - background, - foregroundScale, - backgroundScale, - isThemeable = isThemeable -) { - - - init { - foreground.also { - try { - it.setDrawable(secondLayer, ColorDrawable(0)) - } catch (e: IndexOutOfBoundsException) {} - (it.getDrawable(hourLayer) as? RotateDrawable)?.fromDegrees = 0f - (it.getDrawable(hourLayer) as? RotateDrawable)?.toDegrees = 360f - (it.getDrawable(minuteLayer) as? RotateDrawable)?.fromDegrees = 0f - (it.getDrawable(minuteLayer) as? RotateDrawable)?.toDegrees = 360f - } - } - - override fun update(context: Context) { - val calendar = Calendar.getInstance() - val hourDegrees = calendar[Calendar.HOUR] / 12f * 10000 + calendar[Calendar.MINUTE] / 60f * 10000f / 12 - val minuteDegrees = calendar[Calendar.MINUTE] / 60f * 10000 - (foreground as LayerDrawable).also { - (it.getDrawable(hourLayer) as? RotateDrawable)?.level = hourDegrees.roundToInt() - (it.getDrawable(minuteLayer) as? RotateDrawable)?.level = minuteDegrees.roundToInt() - } - notifyCallbacks() - } -} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/DynamicCalendarIcon.kt b/icons/src/main/java/de/mm20/launcher2/icons/DynamicCalendarIcon.kt new file mode 100644 index 00000000..d7f19806 --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/DynamicCalendarIcon.kt @@ -0,0 +1,66 @@ +package de.mm20.launcher2.icons + +import android.content.res.Resources +import android.graphics.drawable.AdaptiveIconDrawable +import androidx.core.content.res.ResourcesCompat +import de.mm20.launcher2.icons.transformations.LauncherIconTransformation +import java.time.Instant +import java.time.ZoneId + +internal class DynamicCalendarIcon( + val resources: Resources, + val resourceIds: IntArray, + val isThemed: Boolean = false, + private val transformations: List = emptyList(), +) : DynamicLauncherIcon { + + init { + if (resourceIds.size < 31) throw IllegalArgumentException("DynamicCalendarIcon resourceIds must at least have 31 items") + } + + override suspend fun getIcon(time: Long): StaticLauncherIcon { + val day = Instant.ofEpochMilli(time).atZone(ZoneId.systemDefault()).dayOfMonth + val resId = resourceIds[day - 1] + + val drawable = try { + ResourcesCompat.getDrawable(resources, resId, null) + } catch (e: Resources.NotFoundException) { + null + } ?: return StaticLauncherIcon( + foregroundLayer = TextLayer(day.toString()), + backgroundLayer = ColorLayer() + ) + + var icon = if (isThemed) { + StaticLauncherIcon( + foregroundLayer = TintedIconLayer( + icon = drawable, + scale = 0.5f, + ), + backgroundLayer = ColorLayer() + ) + } else if (drawable is AdaptiveIconDrawable) { + StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = drawable.foreground, + scale = 1.5f + ), + backgroundLayer = StaticIconLayer( + icon = drawable.background, + scale = 1.5f + ) + ) + } else StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = drawable, + scale = 1f, + ), + backgroundLayer = TransparentLayer + ) + + for (transformation in transformations) { + icon = transformation.transform(icon) + } + return icon + } +} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/DynamicIconController.kt b/icons/src/main/java/de/mm20/launcher2/icons/DynamicIconController.kt deleted file mode 100644 index 9f8b791b..00000000 --- a/icons/src/main/java/de/mm20/launcher2/icons/DynamicIconController.kt +++ /dev/null @@ -1,58 +0,0 @@ -package de.mm20.launcher2.icons - -import android.app.Activity -import android.app.Application -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import android.util.Log -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleObserver -import androidx.lifecycle.OnLifecycleEvent -import java.lang.ref.WeakReference - -class DynamicIconController(val context: Context): LifecycleObserver { - - private var timeReceiver: BroadcastReceiver? = null - private val registeredIcons = mutableListOf>() - - @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) - fun resume() { - timeReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent?) { - updateAllIcons(context) - } - } - val filter = IntentFilter(Intent.ACTION_TIME_TICK).also { - it.addAction(Intent.ACTION_TIME_CHANGED) - it.addAction(Intent.ACTION_TIMEZONE_CHANGED) - } - context.registerReceiver(timeReceiver, filter) - updateAllIcons(context) - } - - private fun updateAllIcons(context: Context) { - val iterator = registeredIcons.iterator() - while (iterator.hasNext()) { - val iconRef = iterator.next() - if (iconRef.get() != null) iconRef.get()?.update(context) - else iterator.remove() - } - } - - @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) - fun pause() { - try { - context.unregisterReceiver(timeReceiver) - } catch (e: IllegalArgumentException) { - - } - timeReceiver = null - } - - fun registerIcon(icon: DynamicLauncherIcon) { - icon.update(context) - registeredIcons.add(WeakReference(icon)) - } -} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/DynamicLauncherIcon.kt b/icons/src/main/java/de/mm20/launcher2/icons/DynamicLauncherIcon.kt deleted file mode 100644 index 297da8d6..00000000 --- a/icons/src/main/java/de/mm20/launcher2/icons/DynamicLauncherIcon.kt +++ /dev/null @@ -1,21 +0,0 @@ -package de.mm20.launcher2.icons - -import android.content.Context -import android.graphics.drawable.Drawable - -abstract class DynamicLauncherIcon( - foreground: Drawable, - background: Drawable?, - foregroundScale: Float, - backgroundScale: Float, - isThemeable: Boolean = false -) : LauncherIcon( - foreground, - background, - foregroundScale, - backgroundScale, - isThemeable = isThemeable -) { - - abstract fun update(context: Context) -} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/Icon.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconPackIcon.kt similarity index 96% rename from icons/src/main/java/de/mm20/launcher2/icons/Icon.kt rename to icons/src/main/java/de/mm20/launcher2/icons/IconPackIcon.kt index b0e226c2..ebe56018 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/Icon.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconPackIcon.kt @@ -3,7 +3,7 @@ package de.mm20.launcher2.icons import android.content.ComponentName import de.mm20.launcher2.database.entities.IconEntity -data class Icon( +data class IconPackIcon( val type: String, val componentName: ComponentName?, val drawable: String?, diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt index 126c3cef..814f4c08 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt @@ -124,7 +124,7 @@ class UpdateIconPacksWorker(val context: Context) { parser.setInput(inStream) } - val icons = mutableListOf() + val icons = mutableListOf() val iconDao = AppDatabase.getInstance(context).iconDao() loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) { @@ -143,7 +143,7 @@ class UpdateIconPacksWorker(val context: Context) { ) ) ?: continue@loop - val icon = Icon( + val icon = IconPackIcon( componentName = componentName, drawable = drawable, iconPack = pkgName, @@ -164,7 +164,7 @@ class UpdateIconPacksWorker(val context: Context) { ) ?: continue@loop - val icon = Icon( + val icon = IconPackIcon( componentName = componentName, drawable = drawable, iconPack = pkgName, @@ -176,7 +176,7 @@ class UpdateIconPacksWorker(val context: Context) { for (i in 0 until parser.attributeCount) { if (parser.getAttributeName(i).startsWith("img")) { val drawable = parser.getAttributeValue(i) - val icon = Icon( + val icon = IconPackIcon( componentName = null, drawable = drawable, iconPack = pkgName, @@ -190,7 +190,7 @@ class UpdateIconPacksWorker(val context: Context) { for (i in 0 until parser.attributeCount) { if (parser.getAttributeName(i).startsWith("img")) { val drawable = parser.getAttributeValue(i) - val icon = Icon( + val icon = IconPackIcon( componentName = null, drawable = drawable, iconPack = pkgName, @@ -204,7 +204,7 @@ class UpdateIconPacksWorker(val context: Context) { for (i in 0 until parser.attributeCount) { if (parser.getAttributeName(i).startsWith("img")) { val drawable = parser.getAttributeValue(i) - val icon = Icon( + val icon = IconPackIcon( componentName = null, drawable = drawable, iconPack = pkgName, @@ -246,7 +246,7 @@ class UpdateIconPacksWorker(val context: Context) { iconDao.deleteIcons(packageName) return } - val icons = mutableListOf() + val icons = mutableListOf() val parser = resources.getXml(resId) loop@ while (parser.next() != XmlPullParser.END_DOCUMENT) { if (parser.eventType != XmlPullParser.START_TAG) continue @@ -256,7 +256,7 @@ class UpdateIconPacksWorker(val context: Context) { parser.getAttributeResourceValue(null, "drawable", 0).toString() val pkg = parser.getAttributeValue(null, "package") val componentName = ComponentName(pkg, pkg) - val icon = Icon( + val icon = IconPackIcon( drawable = drawable, componentName = componentName, iconPack = packageName, diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt index 91436382..6a9cad25 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt @@ -6,6 +6,8 @@ import android.content.Intent import android.content.IntentFilter import android.util.LruCache import de.mm20.launcher2.icons.providers.* +import de.mm20.launcher2.icons.transformations.LauncherIconTransformation +import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.search.data.Searchable import kotlinx.coroutines.CoroutineScope @@ -17,7 +19,6 @@ import kotlinx.coroutines.launch class IconRepository( val context: Context, private val iconPackManager: IconPackManager, - private val dynamicIconController: DynamicIconController, private val dataStore: LauncherDataStore ) { @@ -34,6 +35,11 @@ class IconRepository( private var iconProviders: MutableStateFlow> = MutableStateFlow(listOf()) private var placeholderProvider: IconProvider? = null + private var transformations: MutableStateFlow> = + MutableStateFlow( + listOf() + ) + init { requestIconPackListUpdate() context.registerReceiver(appReceiver, IntentFilter().apply { @@ -62,19 +68,23 @@ class IconRepository( providers.add( IconPackIconProvider( context, - settings.iconPack, - settings.legacyIconBg + settings.iconPack ) ) } providers.add(GoogleClockIconProvider(context)) providers.add(CalendarIconProvider(context)) - providers.add(SystemIconProvider(context, settings.legacyIconBg)) + providers.add(SystemIconProvider(context)) providers.add(placeholderProvider) cache.evictAll() + val transformations = mutableListOf() + + if (settings.adaptify) transformations.add(LegacyToAdaptiveTransformation()) + this@IconRepository.placeholderProvider = placeholderProvider iconProviders.value = providers + this@IconRepository.transformations.value = transformations } } } @@ -82,28 +92,33 @@ class IconRepository( fun getIcon(searchable: Searchable, size: Int): Flow = channelFlow { iconProviders.collectLatest { providers -> - var icon = cache.get(searchable.key) - if (icon != null) { - send(icon) - return@collectLatest - } - - val placeholder = placeholderProvider?.getIcon(searchable, size) - placeholder?.let { send(it) } - - for (provider in providers) { - val ic = provider.getIcon(searchable, size) - if (ic != null) { - if (ic is DynamicLauncherIcon) { - dynamicIconController.registerIcon(ic) - } - icon = ic - break + transformations.collectLatest { transformations -> + var icon = cache.get(searchable.key) + if (icon != null) { + send(icon) + return@collectLatest + } + + val placeholder = placeholderProvider?.getIcon(searchable, size) + placeholder?.let { send(it) } + + for (provider in providers) { + val ic = provider.getIcon(searchable, size) + if (ic != null) { + icon = ic + break + } + } + if (icon != null) { + if (icon is StaticLauncherIcon) { + for (transformation in transformations) { + icon = transformation.transform(icon as StaticLauncherIcon) + } + } + + cache.put(searchable.key, icon) + send(icon) } - } - if (icon != null) { - cache.put(searchable.key, icon) - send(icon) } } } diff --git a/icons/src/main/java/de/mm20/launcher2/icons/Module.kt b/icons/src/main/java/de/mm20/launcher2/icons/Module.kt index 4a38ba86..507cd96f 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/Module.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/Module.kt @@ -4,7 +4,6 @@ import org.koin.android.ext.koin.androidContext import org.koin.dsl.module val iconsModule = module { - single { DynamicIconController(androidContext()) } single { IconPackManager(androidContext()) } - single { IconRepository(androidContext(), get(), get(), get()) } + single { IconRepository(androidContext(), get(), get()) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/ThemedCalendarDynamicLauncherIcon.kt b/icons/src/main/java/de/mm20/launcher2/icons/ThemedCalendarDynamicLauncherIcon.kt deleted file mode 100644 index fa5acca0..00000000 --- a/icons/src/main/java/de/mm20/launcher2/icons/ThemedCalendarDynamicLauncherIcon.kt +++ /dev/null @@ -1,44 +0,0 @@ -package de.mm20.launcher2.icons - -import android.content.Context -import android.content.pm.PackageManager -import android.graphics.Color -import android.graphics.drawable.AdaptiveIconDrawable -import android.graphics.drawable.ColorDrawable -import android.graphics.drawable.Drawable -import android.os.Build -import de.mm20.launcher2.ktx.getDrawableOrNull -import java.util.* -import java.util.concurrent.Executors - -class ThemedCalendarDynamicLauncherIcon( - foregroundScale: Float, - val packageName: String, - val foregroundIds: IntArray, - background: Drawable, -) : DynamicLauncherIcon( - foreground = ColorDrawable(0), - background = background, - foregroundScale = foregroundScale, - backgroundScale = 1f, - isThemeable = true, -) { - - var currentDay = 0 - override fun update(context: Context) { - val calendar = Calendar.getInstance() - val day = calendar[Calendar.DAY_OF_MONTH] - if (day == currentDay || foregroundIds.size < currentDay) return - val resources = try { - context.packageManager.getResourcesForApplication(packageName) - } catch (e: PackageManager.NameNotFoundException) { - return - } - Executors.newSingleThreadExecutor().execute { - val currentDayDrawable = resources.getDrawableOrNull(foregroundIds[day - 1]) - ?: return@execute - foreground = currentDayDrawable - } - currentDay = day - } -} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt index 253cc856..8c77aefe 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/CalendarIconProvider.kt @@ -4,7 +4,7 @@ import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager import android.graphics.drawable.ColorDrawable -import de.mm20.launcher2.icons.CalendarDynamicLauncherIcon +import de.mm20.launcher2.icons.DynamicCalendarIcon import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.ktx.obtainTypedArrayOrNull import de.mm20.launcher2.search.data.Application @@ -35,13 +35,9 @@ class CalendarIconProvider(val context: Context): IconProvider { drawableIds[i] = typedArray.getResourceId(i, 0) } typedArray.recycle() - return CalendarDynamicLauncherIcon( - foreground = ColorDrawable(0), - background = ColorDrawable(0), - foregroundScale = 1.5f, - backgroundScale = 1.5f, - packageName = component.packageName, - drawableIds = drawableIds + return DynamicCalendarIcon( + resources = resources, + resourceIds = drawableIds ) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt index 51885791..2001f258 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/GoogleClockIconProvider.kt @@ -5,12 +5,12 @@ import android.content.pm.PackageManager import android.content.res.Resources import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.LayerDrawable -import android.os.Build +import android.graphics.drawable.RotateDrawable import androidx.core.content.res.ResourcesCompat -import de.mm20.launcher2.icons.ClockDynamicLauncherIcon -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.* import de.mm20.launcher2.search.data.Application import de.mm20.launcher2.search.data.Searchable +import kotlin.math.roundToInt class GoogleClockIconProvider(val context: Context) : IconProvider { override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { @@ -26,7 +26,7 @@ class GoogleClockIconProvider(val context: Context) : IconProvider { return null } val drawable = - appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND") + appInfo.metaData.getInt("com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND") val resources = pm.getResourcesForApplication(appInfo) val baseIcon = try { ResourcesCompat.getDrawable(resources, drawable, null) as? AdaptiveIconDrawable @@ -36,19 +36,53 @@ class GoogleClockIconProvider(val context: Context) : IconProvider { } val foreground = baseIcon.foreground as? LayerDrawable ?: return null val hourLayer = - appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX") + appInfo.metaData.getInt("com.android.launcher3.HOUR_LAYER_INDEX") val minuteLayer = - appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX") + appInfo.metaData.getInt("com.android.launcher3.MINUTE_LAYER_INDEX") val secondLayer = - appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX") - return ClockDynamicLauncherIcon( - foreground = foreground, - background = baseIcon.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f, - hourLayer = hourLayer, - minuteLayer = minuteLayer, - secondLayer = secondLayer + appInfo.metaData.getInt("com.android.launcher3.SECOND_LAYER_INDEX") + + val defaultHour = + appInfo.metaData.getInt("com.android.launcher3.DEFAULT_HOUR") + val defaultMinute = + appInfo.metaData.getInt("com.android.launcher3.DEFAULT_MINUTE") + val defaultSecond = + appInfo.metaData.getInt("com.android.launcher3.DEFAULT_SECOND") + + return StaticLauncherIcon( + foregroundLayer = ClockLayer( + sublayers = (0 until foreground.numberOfLayers).map { + val drw = foreground.getDrawable(it) + if (drw is RotateDrawable) { + drw.level = when (it) { + hourLayer -> { + (12 - defaultHour) * 60 + } + minuteLayer -> { + (60 - defaultMinute) + } + secondLayer -> { + (60 - defaultSecond) * 10 + } + else -> 0 + } + } + ClockSublayer( + drawable = drw, + role = when { + it == hourLayer -> ClockSublayerRole.Hour + it == minuteLayer -> ClockSublayerRole.Minute + it == secondLayer -> ClockSublayerRole.Second + else -> ClockSublayerRole.Static + } + ) + }, + scale = 1.5f, + ), + backgroundLayer = StaticIconLayer( + icon = baseIcon.background, + scale = 1.5f, + ) ) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt index 5da6cac6..35b3b713 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconPackIconProvider.kt @@ -9,13 +9,11 @@ import android.graphics.* import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable -import android.os.Build import android.util.Log import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.toBitmap import de.mm20.launcher2.database.AppDatabase -import de.mm20.launcher2.icons.CalendarDynamicLauncherIcon -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.* import de.mm20.launcher2.ktx.randomElementOrNull import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.search.data.LauncherApp @@ -24,9 +22,8 @@ import kotlin.math.roundToInt class IconPackIconProvider( private val context: Context, - private val iconPack: String, - private val legacyIconBackground: Settings.IconSettings.LegacyIconBackground - ): IconProvider { + private val iconPack: String +): IconProvider { override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { if (searchable !is LauncherApp) return null val res = try { @@ -49,25 +46,31 @@ class IconPackIconProvider( val drawable = ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null return when { drawable is AdaptiveIconDrawable -> { - LauncherIcon( - foreground = drawable.foreground, - background = drawable.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f + StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = drawable.foreground, + scale = 1.5f + ), + backgroundLayer = StaticIconLayer( + icon = drawable.background, + scale = 1.5f + ) ) } else -> { - LauncherIcon( - foreground = drawable, - foregroundScale = getScale(), - autoGenerateBackgroundMode = legacyIconBackground.number + StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = drawable, + scale = getScale() + ), + backgroundLayer = TransparentLayer ) } } } private fun getScale(): Float { - return 0.7f + return 1f } private suspend fun generateIcon( @@ -147,10 +150,12 @@ class IconPackIconProvider( } } - return LauncherIcon( - foreground = BitmapDrawable(context.resources, bitmap), - foregroundScale = getScale(), - autoGenerateBackgroundMode = legacyIconBackground.number + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = BitmapDrawable(context.resources, bitmap), + scale = getScale(), + ), + backgroundLayer = TransparentLayer ) } @@ -180,7 +185,7 @@ class IconPackIconProvider( private fun getIconPackCalendarIcon( context: Context, baseIconName: String - ): CalendarDynamicLauncherIcon? { + ): DynamicCalendarIcon? { val resources = try { context.packageManager.getResourcesForApplication(iconPack) } catch (e: PackageManager.NameNotFoundException) { @@ -192,13 +197,9 @@ class IconPackIconProvider( if (id == 0) return null id }.toIntArray() - return CalendarDynamicLauncherIcon( - foreground = ColorDrawable(0), - background = ColorDrawable(0), - foregroundScale = 1.5f, - backgroundScale = 1.5f, - packageName = iconPack, - drawableIds = drawableIds, + return DynamicCalendarIcon( + resources = resources, + resourceIds = drawableIds ) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt index 539f5dbf..c396c87a 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/PlaceholderIconProvider.kt @@ -5,7 +5,7 @@ import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.search.data.Searchable class PlaceholderIconProvider(val context: Context) : IconProvider { - override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { + override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon { return searchable.getPlaceholderIcon(context) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt index 23323fe7..d7594fac 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/SystemIconProvider.kt @@ -6,10 +6,9 @@ import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.search.data.Searchable class SystemIconProvider( - private val context: Context, - private val legacyIconBackground: Settings.IconSettings.LegacyIconBackground - ) : IconProvider { + private val context: Context +) : IconProvider { override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { - return searchable.loadIcon(context, size, legacyIconBackground) + return searchable.loadIcon(context, size) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedIconProvider.kt index 3703dc5a..1642bb46 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedIconProvider.kt @@ -4,13 +4,13 @@ import android.content.ComponentName import android.content.Context import android.content.pm.PackageManager import android.content.res.Resources -import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.graphics.drawable.LayerDrawable +import android.graphics.drawable.RotateDrawable import androidx.core.content.res.ResourcesCompat import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.database.AppDatabase import de.mm20.launcher2.icons.* +import de.mm20.launcher2.ktx.getDrawableOrNull import de.mm20.launcher2.ktx.obtainTypedArrayOrNull import de.mm20.launcher2.search.data.Application import de.mm20.launcher2.search.data.Searchable @@ -37,21 +37,22 @@ internal class ThemedIconProvider( } - private suspend fun getGreyscaleIcon(packageName: String): Icon? { + private suspend fun getGreyscaleIcon(packageName: String): IconPackIcon? { val iconDao = AppDatabase.getInstance(context).iconDao() return iconDao.getGreyscaleIcon(ComponentName(packageName, packageName).flattenToString()) - ?.let { Icon(it) } + ?.let { IconPackIcon(it) } } private fun getStaticIcon(resources: Resources, resId: Int): LauncherIcon? { try { val fg = ResourcesCompat.getDrawable(resources, resId, null) ?: return null - return LauncherIcon( - foreground = fg, - foregroundScale = 0.5f, - background = ColorDrawable(Color.WHITE), - isThemeable = true + return StaticLauncherIcon( + foregroundLayer = TintedIconLayer( + icon = fg, + scale = 0.5f, + ), + backgroundLayer = ColorLayer() ) } catch (e: Resources.NotFoundException) { return null @@ -64,7 +65,9 @@ internal class ThemedIconProvider( var i = 0 var drawable: LayerDrawable? = null var minuteIndex: Int? = null + var defaultMinute = 0 var hourIndex: Int? = null + var defaultHour = 0 while (i < array.length()) { when (array.getString(i)) { "com.android.launcher3.LEVEL_PER_TICK_ICON_ROUND" -> { @@ -79,19 +82,46 @@ internal class ThemedIconProvider( i++ minuteIndex = array.getInt(i, -1).takeIf { it != -1 } } + "com.android.launcher3.DEFAULT_HOUR" -> { + i++ + defaultHour = array.getInt(i, 0) + } + "com.android.launcher3.DEFAULT_MINUTE" -> { + i++ + defaultMinute = array.getInt(i, 0) + } } i++ } if (drawable != null && minuteIndex != null && hourIndex != null) { - return ClockDynamicLauncherIcon( - foreground = drawable, - background = ColorDrawable(Color.WHITE), - foregroundScale = 1.5f, - backgroundScale = 1f, - hourLayer = hourIndex, - minuteLayer = minuteIndex, - secondLayer = -1, - isThemeable = true, + + return StaticLauncherIcon( + foregroundLayer = TintedClockLayer( + sublayers = (0 until drawable.numberOfLayers).map { + val drw = drawable.getDrawable(it) + if (drw is RotateDrawable) { + drw.level = when (it) { + hourIndex -> { + (12 - defaultHour) * 60 + } + minuteIndex -> { + (60 - defaultMinute) + } + else -> 0 + } + } + ClockSublayer( + drawable = drw, + role = when { + it == hourIndex -> ClockSublayerRole.Hour + it == minuteIndex -> ClockSublayerRole.Minute + else -> ClockSublayerRole.Static + } + ) + }, + scale = 1.5f, + ), + backgroundLayer = ColorLayer() ) } } catch (e: Resources.NotFoundException) { @@ -108,15 +138,13 @@ internal class ThemedIconProvider( val array = resources.obtainTypedArrayOrNull(resId) ?: return null if (array.length() != 31) return null - return ThemedCalendarDynamicLauncherIcon( - foregroundScale = 0.5f, - packageName = iconProviderPackage, - foregroundIds = IntArray(31) { + return DynamicCalendarIcon( + resources = resources, + resourceIds = IntArray(31) { array.getResourceId(it, 0).takeIf { it != 0 } ?: return null }, - background = ColorDrawable(Color.WHITE), + isThemed = true ) - } catch (e: Resources.NotFoundException) { } return null diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedPlaceholderIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedPlaceholderIconProvider.kt index 3e38f65f..f896493a 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedPlaceholderIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/ThemedPlaceholderIconProvider.kt @@ -1,7 +1,7 @@ package de.mm20.launcher2.icons.providers import android.content.Context -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.* import de.mm20.launcher2.search.data.Searchable internal class ThemedPlaceholderIconProvider( @@ -11,13 +11,30 @@ internal class ThemedPlaceholderIconProvider( override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon { val icon = searchable.getPlaceholderIcon(context) - return LauncherIcon( - foreground = icon.foreground, - foregroundScale = icon.foregroundScale, - background = icon.background, - backgroundScale = icon.backgroundScale, - isThemeable = true + return StaticLauncherIcon( + foregroundLayer = asThemed(icon.foregroundLayer), + backgroundLayer = asThemed(icon.backgroundLayer), ) } + private fun asThemed(layer: LauncherIconLayer): LauncherIconLayer { + return when (layer) { + is ClockLayer -> TintedClockLayer( + scale = layer.scale, + color = 0, + sublayers = layer.sublayers, + ) + is ColorLayer -> layer.copy(color = 0) + is StaticIconLayer -> TintedIconLayer( + icon = layer.icon, + color = 0, + scale = layer.scale, + ) + is TextLayer -> layer.copy(color = 0) + is TintedIconLayer -> layer.copy(color = 0) + is TintedClockLayer -> return layer.copy(color = 0) + is TransparentLayer -> return layer + } + } + } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/transformations/LauncherIconTransformation.kt b/icons/src/main/java/de/mm20/launcher2/icons/transformations/LauncherIconTransformation.kt new file mode 100644 index 00000000..48463686 --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/transformations/LauncherIconTransformation.kt @@ -0,0 +1,7 @@ +package de.mm20.launcher2.icons.transformations + +import de.mm20.launcher2.icons.StaticLauncherIcon + +internal interface LauncherIconTransformation { + suspend fun transform(icon: StaticLauncherIcon): StaticLauncherIcon +} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/transformations/LegacyToAdaptiveTransformation.kt b/icons/src/main/java/de/mm20/launcher2/icons/transformations/LegacyToAdaptiveTransformation.kt new file mode 100644 index 00000000..80efbb49 --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/transformations/LegacyToAdaptiveTransformation.kt @@ -0,0 +1,50 @@ +package de.mm20.launcher2.icons.transformations + +import android.graphics.drawable.BitmapDrawable +import androidx.core.graphics.drawable.toBitmap +import androidx.palette.graphics.Palette +import de.mm20.launcher2.icons.* +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +internal class LegacyToAdaptiveTransformation: LauncherIconTransformation { + override suspend fun transform(icon: StaticLauncherIcon): StaticLauncherIcon { + if (icon.backgroundLayer !is TransparentLayer) return icon + + val bgColor = extractColor(icon.foregroundLayer) + return StaticLauncherIcon( + foregroundLayer = scale(icon.foregroundLayer, 0.7f), + backgroundLayer = ColorLayer(bgColor) + ) + } + + private fun scale(layer: LauncherIconLayer, scale: Float): LauncherIconLayer { + return when(layer) { + is ClockLayer -> layer.copy(scale = scale) + is StaticIconLayer -> layer.copy(scale = scale) + is TintedClockLayer -> layer.copy(scale = scale) + is TintedIconLayer -> layer.copy(scale = scale) + else -> layer + } + } + + private suspend fun extractColor(layer: LauncherIconLayer): Int { + + if (layer is StaticIconLayer) { + val drawable = layer.icon + val bitmap = if (drawable is BitmapDrawable) { + drawable.bitmap + } else { + drawable.toBitmap(48, 48) + } + + val palette = withContext(Dispatchers.Default) { + Palette.from(bitmap).generate() + } + return palette.getDominantColor(0) + } else if (layer is ColorLayer) { + return layer.color + } + return 0 + } +} \ No newline at end of file diff --git a/ktx/src/main/java/de/mm20/launcher2/ktx/Drawable.kt b/ktx/src/main/java/de/mm20/launcher2/ktx/Drawable.kt index 53ab699e..927e537d 100644 --- a/ktx/src/main/java/de/mm20/launcher2/ktx/Drawable.kt +++ b/ktx/src/main/java/de/mm20/launcher2/ktx/Drawable.kt @@ -1,6 +1,8 @@ package de.mm20.launcher2.ktx import android.graphics.Bitmap +import android.graphics.Canvas +import android.graphics.ColorFilter import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import androidx.annotation.Px @@ -13,4 +15,11 @@ fun Drawable.toBitmapOrNull( ): Bitmap? { if (this is BitmapDrawable && bitmap == null) return null return toBitmap(width, height, config) +} + +fun Drawable.drawWithColorFilter(canvas: Canvas, colorFilter: ColorFilter?) { + val cf = this.colorFilter + this.colorFilter = colorFilter + this.draw(canvas) + this.colorFilter = cf } \ No newline at end of file diff --git a/ktx/src/main/java/de/mm20/launcher2/ktx/LayerDrawable.kt b/ktx/src/main/java/de/mm20/launcher2/ktx/LayerDrawable.kt new file mode 100644 index 00000000..8ed63771 --- /dev/null +++ b/ktx/src/main/java/de/mm20/launcher2/ktx/LayerDrawable.kt @@ -0,0 +1,12 @@ +package de.mm20.launcher2.ktx + +import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable + +fun LayerDrawable.getDrawableOrNull(index: Int): Drawable? { + return try { + this.getDrawable(index) + } catch (e: IndexOutOfBoundsException) { + return null + } +} \ No newline at end of file diff --git a/permissions/src/main/java/de/mm20/launcher2/data/search/MissingPermission.kt b/permissions/src/main/java/de/mm20/launcher2/data/search/MissingPermission.kt deleted file mode 100644 index 8c39c7b7..00000000 --- a/permissions/src/main/java/de/mm20/launcher2/data/search/MissingPermission.kt +++ /dev/null @@ -1,25 +0,0 @@ -package de.mm20.launcher2.search.data - -import android.content.Context -import android.graphics.drawable.ColorDrawable -import androidx.core.content.ContextCompat -import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.permissions.PermissionGroup -import de.mm20.launcher2.permissions.R - -class MissingPermission( - override val label: String, - val permissionGroup: PermissionGroup, - val secondaryActionLabel: String? = null, - val secondaryAction: (() -> Unit)? = null - ): Searchable() { - override val key: String - get() = "permission://${permissionGroup.ordinal}" - - override fun getPlaceholderIcon(context: Context): LauncherIcon { - return LauncherIcon( - foreground = ContextCompat.getDrawable(context, R.drawable.ic_permission)!!, - background = ColorDrawable(ContextCompat.getColor(context, R.color.bluegrey)) - ) - } -} \ No newline at end of file diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt index 58fa6148..4c8cdad8 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/DataStore.kt @@ -19,12 +19,11 @@ internal val Context.dataStore: LauncherDataStore by dataStore( }, corruptionHandler = ReplaceFileCorruptionHandler { CrashReporter.logException(it) - Log.d("MM20", "corruptionHandler") Settings.getDefaultInstance() } ) -internal const val SchemaVersion = 6 +internal const val SchemaVersion = 7 internal fun getMigrations(context: Context): List> { return listOf( @@ -34,5 +33,6 @@ internal fun getMigrations(context: Context): List> { Migration_3_4(), Migration_4_5(), Migration_5_6(), + Migration_6_7(), ) } \ No newline at end of file diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt index 30d92836..a46272cf 100644 --- a/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/Defaults.kt @@ -128,7 +128,7 @@ fun createFactorySettings(context: Context): Settings { ) .setIcons( Settings.IconSettings.newBuilder() - .setLegacyIconBg(Settings.IconSettings.LegacyIconBackground.Dynamic) + .setAdaptify(true) .setShape(Settings.IconSettings.IconShape.PlatformDefault) .setThemedIcons(false) .setIconPack("") diff --git a/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_6_7.kt b/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_6_7.kt new file mode 100644 index 00000000..136a904d --- /dev/null +++ b/preferences/src/main/java/de/mm20/launcher2/preferences/migrations/Migration_6_7.kt @@ -0,0 +1,12 @@ +package de.mm20.launcher2.preferences.migrations + +import de.mm20.launcher2.preferences.Settings + +class Migration_6_7 : VersionedMigration(6, 7) { + override suspend fun applyMigrations(builder: Settings.Builder): Settings.Builder { + return builder.setIcons( + builder.icons.toBuilder() + .setAdaptify(true) + ) + } +} \ No newline at end of file diff --git a/preferences/src/main/proto/settings.proto b/preferences/src/main/proto/settings.proto index d0253a2f..b7a28933 100644 --- a/preferences/src/main/proto/settings.proto +++ b/preferences/src/main/proto/settings.proto @@ -210,12 +210,7 @@ message Settings { IconShape shape = 1; bool themed_icons = 2; string icon_pack = 3; - enum LegacyIconBackground { - Dynamic = 0; - None = 1; - White = 2; - } - LegacyIconBackground legacyIconBg = 4; + bool adaptify = 5; } IconSettings icons = 21; diff --git a/search/src/main/java/de/mm20/launcher2/search/data/Searchable.kt b/search/src/main/java/de/mm20/launcher2/search/data/Searchable.kt index 1d1a40d3..ab649710 100644 --- a/search/src/main/java/de/mm20/launcher2/search/data/Searchable.kt +++ b/search/src/main/java/de/mm20/launcher2/search/data/Searchable.kt @@ -5,9 +5,9 @@ import android.content.Intent import android.os.Bundle import android.widget.Toast import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.ktx.romanize import de.mm20.launcher2.ktx.tryStartActivity -import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground import de.mm20.launcher2.search.R import java.text.Collator @@ -37,10 +37,9 @@ abstract class Searchable : Comparable { open suspend fun loadIcon( context: Context, size: Int, - legacyIconBackground: LegacyIconBackground ): LauncherIcon? = null - abstract fun getPlaceholderIcon(context: Context): LauncherIcon + abstract fun getPlaceholderIcon(context: Context): StaticLauncherIcon override fun compareTo(other: Searchable): Int { return Collator.getInstance().apply { strength = Collator.SECONDARY } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/ShapedLauncherIcon.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/ShapedLauncherIcon.kt index 257b0014..acfcb018 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/ShapedLauncherIcon.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/ShapedLauncherIcon.kt @@ -1,14 +1,11 @@ package de.mm20.launcher2.ui.component +import android.graphics.* import android.graphics.Matrix import android.graphics.Path -import android.graphics.RectF import android.graphics.drawable.AdaptiveIconDrawable -import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.foundation.Canvas -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.Image -import androidx.compose.foundation.combinedClickable +import androidx.compose.animation.core.* +import androidx.compose.foundation.* import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -25,15 +22,26 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.toRect import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.drawscope.drawIntoCanvas +import androidx.compose.ui.graphics.drawscope.scale +import androidx.compose.ui.graphics.drawscope.withTransform import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import de.mm20.launcher2.badges.Badge -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.* +import de.mm20.launcher2.ktx.drawWithColorFilter import de.mm20.launcher2.preferences.Settings.IconSettings.IconShape +import de.mm20.launcher2.ui.base.LocalTime +import de.mm20.launcher2.ui.locals.LocalDarkTheme +import palettes.TonalPalette +import java.time.Instant +import java.time.ZoneId import kotlin.math.pow import kotlin.math.roundToInt @@ -48,6 +56,25 @@ fun ShapedLauncherIcon( onLongClick: (() -> Unit)? = null, shape: Shape = LocalIconShape.current ) { + + val time = LocalTime.current + + var currentIcon by remember(icon) { + mutableStateOf( + when (icon) { + is DynamicLauncherIcon -> null + is StaticLauncherIcon -> icon + null -> null + } + ) + } + + if (icon is DynamicLauncherIcon) { + LaunchedEffect(time) { + currentIcon = icon.getIcon(time) + } + } + Box( modifier = modifier .size(size) @@ -56,7 +83,7 @@ fun ShapedLauncherIcon( modifier = Modifier .fillMaxSize() .graphicsLayer { - clip = true + clip = currentIcon?.backgroundLayer !is TransparentLayer this.shape = shape } .combinedClickable( @@ -68,45 +95,19 @@ fun ShapedLauncherIcon( ), contentAlignment = Alignment.Center ) { - if (icon != null) { - - val fgScale = icon.foregroundScale - val bgScale = icon.backgroundScale - - val themedFgColor = MaterialTheme.colorScheme.onPrimaryContainer - val themedBgColor = MaterialTheme.colorScheme.primaryContainer - - val fg = remember(icon, icon.isThemeable, themedFgColor) { - icon.foreground.also { - if (icon.isThemeable) it.setTint(themedFgColor.toArgb()) - } - } - val bg = remember(icon, icon.isThemeable, themedBgColor) { - icon.background?.also { - if (icon.isThemeable) it.setTint(themedBgColor.toArgb()) - } - } - - Canvas(modifier = Modifier.fillMaxSize()) { - drawIntoCanvas { - val paddingFg = (size * (1 - fgScale) * 0.5f).toPx() - val paddingBg = (size * (1 - bgScale) * 0.5f).toPx() - bg?.setBounds( - paddingBg.toInt(), - paddingBg.toInt(), - (this.size.width - paddingBg).toInt(), - (this.size.height - paddingBg).toInt() - ) - bg?.draw(it.nativeCanvas) - fg.setBounds( - paddingFg.toInt(), - paddingFg.toInt(), - (this.size.width - paddingFg).toInt(), - (this.size.height - paddingFg).toInt() - ) - fg.draw(it.nativeCanvas) - } - } + currentIcon?.let { + IconLayer( + it.backgroundLayer, + size, + colorTone = if (!LocalDarkTheme.current) 30 else 90, + MaterialTheme.colorScheme.primaryContainer + ) + IconLayer( + it.foregroundLayer, + size, + colorTone = if (!LocalDarkTheme.current) 90 else 10, + MaterialTheme.colorScheme.onPrimaryContainer + ) } } if (badge != null) { @@ -138,12 +139,18 @@ fun ShapedLauncherIcon( val number = badge.number if (badgeIconRes != null) { Image( - modifier = Modifier.fillMaxSize().padding(size / 48), + modifier = Modifier + .fillMaxSize() + .padding(size / 48), painter = painterResource(badgeIconRes), contentDescription = null ) } else if (badgeIcon != null) { - Canvas(modifier = Modifier.fillMaxSize().padding(size / 48)) { + Canvas( + modifier = Modifier + .fillMaxSize() + .padding(size / 48) + ) { badgeIcon.setBounds( 0, 0, @@ -171,6 +178,172 @@ fun ShapedLauncherIcon( } } +@Composable +private fun IconLayer( + layer: LauncherIconLayer, + size: Dp, + colorTone: Int, + defaultTintColor: Color +) { + when (layer) { + is ClockLayer -> { + ClockLayer(layer.sublayers, scale = layer.scale, tintColor = null) + } + is TintedClockLayer -> { + ClockLayer( + layer.sublayers, + scale = layer.scale, + tintColor = if (layer.color == 0) defaultTintColor + else Color(getTone(layer.color, colorTone)) + ) + + } + is ColorLayer -> { + Box( + modifier = Modifier + .fillMaxSize() + .background( + if (layer.color == 0) { + defaultTintColor + } else { + Color(getTone(layer.color, colorTone)) + } + ) + ) + } + is StaticIconLayer -> { + Canvas(modifier = Modifier.fillMaxSize()) { + withTransform({ + this.scale(layer.scale) + }) { + drawIntoCanvas { + layer.icon.bounds = this.size.toRect().toAndroidRect() + layer.icon.draw(it.nativeCanvas) + } + } + } + } + is TextLayer -> { + Text( + text = layer.text, + style = MaterialTheme.typography.headlineSmall.copy( + fontSize = 20.sp * (size / 48.dp) + ), + color = if (layer.color == 0) { + defaultTintColor + } else { + Color(getTone(layer.color, colorTone)) + }, + ) + } + is TintedIconLayer -> { + val color = + if (layer.color == 0) defaultTintColor.toArgb() + else getTone(layer.color, colorTone) + Canvas(modifier = Modifier.fillMaxSize()) { + withTransform({ + this.scale(layer.scale) + }) { + drawIntoCanvas { + layer.icon.bounds = this.size.toRect().toAndroidRect() + layer.icon.drawWithColorFilter( + it.nativeCanvas, + PorterDuffColorFilter(color, PorterDuff.Mode.SRC_IN) + ) + } + } + } + } + is TransparentLayer -> {} + } +} + +private fun getTone(argb: Int, tone: Int): Int { + return TonalPalette + .fromInt(argb) + .tone(tone) +} + +@Composable +private fun ClockLayer( + sublayers: List, + scale: Float, + tintColor: Color?, +) { + val time = remember { + Instant.ofEpochMilli(System.currentTimeMillis()).atZone(ZoneId.systemDefault()) + } + val transition = rememberInfiniteTransition() + + val minute by transition.animateFloat( + initialValue = 0f, + targetValue = 60f, + animationSpec = InfiniteRepeatableSpec( + animation = tween(durationMillis = 60 * 60 * 1000, easing = LinearEasing), + initialStartOffset = StartOffset( + offsetMillis = time.minute * 60 * 1000 + time.second * 1000, + offsetType = StartOffsetType.FastForward + ) + ) + ) + + val hour by transition.animateFloat( + initialValue = 0f, + targetValue = 12f, + animationSpec = InfiniteRepeatableSpec( + animation = tween(durationMillis = 12 * 60 * 60 * 1000, easing = LinearEasing), + initialStartOffset = StartOffset( + offsetMillis = (time.hour % 12) * 60 * 60 * 1000 + time.minute * 60 * 1000 + time.second * 1000, + offsetType = StartOffsetType.FastForward + ) + ) + ) + + + val second by transition.animateFloat( + initialValue = 0f, + targetValue = 60f, + animationSpec = InfiniteRepeatableSpec( + animation = tween(durationMillis = 60000, easing = LinearEasing), + initialStartOffset = StartOffset( + offsetMillis = time.second * 1000, + offsetType = StartOffsetType.FastForward + ) + ) + ) + + Canvas(modifier = Modifier.fillMaxSize()) { + val colorFilter = tintColor?.let { + PorterDuffColorFilter(tintColor.toArgb(), PorterDuff.Mode.SRC_IN) + } + withTransform({ + this.scale(scale) + }) { + for (sublayer in sublayers) { + withTransform({ + when (sublayer.role) { + ClockSublayerRole.Hour -> { + rotate(hour / 12f * 360f) + } + ClockSublayerRole.Minute -> { + rotate(minute / 60f * 360f) + } + ClockSublayerRole.Second -> { + rotate(second / 60f * 360f) + } + ClockSublayerRole.Static -> {} + } + }) { + drawIntoCanvas { + sublayer.drawable.bounds = this.size.toRect().toAndroidRect() + sublayer.drawable.drawWithColorFilter(it.nativeCanvas, colorFilter) + } + } + } + } + } +} + val LocalIconShape = compositionLocalOf { CircleShape } fun getShape(iconShape: IconShape): Shape { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/icons/Searchable.kt b/ui/src/main/java/de/mm20/launcher2/ui/icons/Searchable.kt deleted file mode 100644 index d8cb1432..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/icons/Searchable.kt +++ /dev/null @@ -1,145 +0,0 @@ -package de.mm20.launcher2.ui.icons - -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.rounded.* -import androidx.compose.runtime.Composable -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.res.colorResource -import de.mm20.launcher2.search.data.Application -import de.mm20.launcher2.search.data.File -import de.mm20.launcher2.search.data.Searchable -import de.mm20.launcher2.ui.* - -data class PlaceholderIcon( - val color: Color, - val icon: ImageVector -) - -@Composable -fun Searchable.getPlaceholderIcon(): PlaceholderIcon { - return when (this) { - is Application -> getPlaceholderIcon() - is File -> getPlaceholderIcon() - else -> PlaceholderIcon( - Color.LightGray, - Icons.Rounded.Circle - ) - } -} - -@Composable -fun Application.getPlaceholderIcon(): PlaceholderIcon { - return PlaceholderIcon( - colorResource(id = R.color.android_green), - Icons.Rounded.Android - ) -} - -@Composable -fun File.getPlaceholderIcon(): PlaceholderIcon { - return when { - isDirectory -> PlaceholderIcon( - colorResource(id = R.color.lightblue), - Icons.Rounded.Folder - ) - mimeType.startsWith("image/") -> PlaceholderIcon( - colorResource(id = R.color.teal), - Icons.Rounded.Image - ) - mimeType.startsWith("audio/") -> PlaceholderIcon( - colorResource(id = R.color.orange), - Icons.Rounded.Audiotrack - ) - mimeType.startsWith("video/") -> PlaceholderIcon( - colorResource(id = R.color.purple), - Icons.Rounded.Movie - ) - /* - else -> when (mimeType) { - - - - - - "application/vnd.google-apps.drawing" -> R.drawable.ic_file_picture to R.color.teal - }*/ - else -> when (mimeType) { - "application/pdf" -> PlaceholderIcon( - colorResource(id = R.color.red), - Icons.Rounded.Pdf - ) - "application/zip", - "application/x-gtar", - "application/x-tar", - "application/java-archive", - "application/x-7z-compressed", - "application/x-compressed-tar", - "application/x-zip-compressed", - "application/x-gzip", - "application/x-bzip2" -> PlaceholderIcon( - colorResource(id = R.color.brown), - Icons.Rounded.Archive - ) - "application/vnd.oasis.opendocument.text", - "application/vnd.openxmlformats-officedocument.wordprocessingml.document", - "application/msword", - "text/plain", - "application/x-iwork-pages-sffpages", - "application/vnd.apple.pages", - "application/vnd.google-apps.document" -> PlaceholderIcon( - colorResource(id = R.color.blue), - Icons.Rounded.Notes - ) - "application/vnd.oasis.opendocument.spreadsheet", - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - "application/vnd.ms-excel", - "application/x-iwork-numbers-sffnumbers", - "application/vnd.apple.numbers", - "application/vnd.google-apps.spreadsheet" -> PlaceholderIcon( - colorResource(id = R.color.lightgreen), - Icons.Rounded.BorderAll - ) - "application/vnd.openxmlformats-officedocument.presentationml.presentation", - "application/vnd.ms-powerpoint", - "application/x-iwork-keynote-sffkey", - "application/vnd.apple.keynote", - "application/vnd.google-apps.presentation" -> PlaceholderIcon( - colorResource(id = R.color.amber), - Icons.Rounded.Slideshow - ) - "application/vnd.android.package-archive" -> PlaceholderIcon( - colorResource(id = R.color.android_green), - Icons.Rounded.Android - ) - "text/x-asm", - "text/x-c", - "text/x-java-source", - "text/x-script.phyton", - "text/x-pascal", - "text/x-script.perl", - "text/javascript", - "application/json" -> PlaceholderIcon( - colorResource(id = R.color.pink), - Icons.Rounded.Code - ) - "text/xml", - "text/html" -> PlaceholderIcon( - colorResource(id = R.color.deeporange), - Icons.Rounded.Code - ) - "application/vnd.google-apps.form" -> PlaceholderIcon( - colorResource(id = R.color.deeppurple), - Icons.Rounded.ViewList - ) - "application/epub+zip" -> PlaceholderIcon( - colorResource(id = R.color.blue), - Icons.Rounded.Book - ) - else -> PlaceholderIcon( - colorResource(id = R.color.bluegrey), - Icons.Rounded.InsertDriveFile - ) - } - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt index 1c7e28f9..df64aa6c 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt @@ -31,7 +31,6 @@ import com.afollestad.materialdialogs.callbacks.onDismiss import com.afollestad.materialdialogs.customview.customView import com.android.launcher3.GestureNavContract import com.google.accompanist.systemuicontroller.rememberSystemUiController -import de.mm20.launcher2.icons.DynamicIconController import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.base.BaseActivity @@ -45,7 +44,6 @@ import de.mm20.launcher2.ui.launcher.transitions.LocalHomeTransitionManager import de.mm20.launcher2.ui.locals.LocalSnackbarHostState import de.mm20.launcher2.ui.locals.LocalWindowSize import de.mm20.launcher2.ui.theme.LauncherTheme -import org.koin.android.ext.android.inject class LauncherActivity : BaseActivity() { @@ -174,10 +172,6 @@ class LauncherActivity : BaseActivity() { editFavoritesDialog = null } } - - val dynamicIconController: DynamicIconController by inject() - - lifecycle.addObserver(dynamicIconController) } override fun onAttachedToWindow() { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherIconView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherIconView.kt index 8a6fb13b..97fefd0a 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherIconView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/LauncherIconView.kt @@ -1,31 +1,10 @@ package de.mm20.launcher2.ui.legacy.view -import android.animation.AnimatorSet -import android.animation.ObjectAnimator import android.content.Context -import android.graphics.* -import android.graphics.drawable.AdaptiveIconDrawable import android.util.AttributeSet -import android.view.HapticFeedbackConstants -import android.view.MotionEvent import android.view.View -import android.view.ViewConfiguration -import androidx.core.content.ContextCompat -import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.ktx.dp -import de.mm20.launcher2.ktx.toRectF -import de.mm20.launcher2.preferences.LauncherDataStore -import de.mm20.launcher2.preferences.Settings.IconSettings.IconShape -import de.mm20.launcher2.ui.R -import de.mm20.launcher2.ui.legacy.helper.BitmapHolder -import kotlinx.coroutines.flow.* import org.koin.core.component.KoinComponent -import org.koin.core.component.get -import java.lang.Math.pow -import kotlin.math.abs -import kotlin.math.hypot -import kotlin.math.roundToInt class LauncherIconView : View, KoinComponent { constructor(context: Context) : super(context) @@ -36,499 +15,10 @@ class LauncherIconView : View, KoinComponent { defStyleRes ) - var shape: IconShape - set(value) { - if (value == IconShape.PlatformDefault) { - platformShape = getSystemShape() - transformMatrix = Matrix() - platformShapeBounds = RectF() - field = value - } else { - platformShape = null - transformMatrix = null - platformShapeBounds = null - field = value - } - } - - private var platformShape: Path? = null - private var transformMatrix: Matrix? = null - private var platformShapeBounds: RectF? = null - - - private fun getSystemShape(): Path { - return AdaptiveIconDrawable(null, null).iconMask - } - var icon: LauncherIcon? = null set(value) { field = value - foregroundScale = value?.foregroundScale ?: 1f - backgroundScale = value?.backgroundScale ?: 1f - value?.registerCallback(iconObserver) invalidate() } - - private val iconObserver: (LauncherIcon) -> Unit = { - foregroundScale = it.foregroundScale - backgroundScale = it.backgroundScale - // Implicit invalidate - } - - var foregroundScale = 1f - set(value) { - field = value - postInvalidate() - } - - var backgroundScale = 1f - set(value) { - field = value - postInvalidate() - } - - init { - shape = IconShape.Circle - setLayerType(LAYER_TYPE_SOFTWARE, null) - } - - - override fun setElevation(elevation: Float) { - super.setElevation(elevation) - shadowPaint = updateShadowPaint() - badgeShadowPaint = updateBadgeShadowPaing() - } - - override fun setTranslationZ(translationZ: Float) { - super.setTranslationZ(translationZ) - shadowPaint = updateShadowPaint() - badgeShadowPaint = updateBadgeShadowPaing() - } - - private var shadowPaint: Paint = updateShadowPaint() - - private var badgeShadowPaint = updateBadgeShadowPaing() - - private fun updateShadowPaint(): Paint { - return Paint().apply { - xfermode = PorterDuffXfermode(PorterDuff.Mode.DST_OVER) - color = Color.TRANSPARENT - isAntiAlias = true - setShadowLayer(0.5f * z, 0f, 0.5f * z, 0x40000000) - } - } - - private fun updateBadgeShadowPaing(): Paint { - return Paint().apply { - xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_OVER) - color = Color.TRANSPARENT - isAntiAlias = true - setShadowLayer(0.5f * z, 0f, 0.5f * z, 0x40000000) - } - } - - private val drawRect = Rect() - private val bmpDrawRect = Rect() - - private val maskPaint = Paint().apply { - style = Paint.Style.FILL - color = 0xFF000000.toInt() - isAntiAlias = true - } - - private val bitmapPaint = Paint().apply { - color = 0xFF000000.toInt() - xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN) - isAntiAlias = true - isFilterBitmap = true - } - - private val badgeTextPaint = Paint().apply { - color = ContextCompat.getColor(context, R.color.badge_text) - isAntiAlias = true - textAlign = Paint.Align.CENTER - } - - private val badgeProgressPaint = Paint().apply { - color = 0x30000000 - isAntiAlias = true - textAlign = Paint.Align.CENTER - } - - private var path: Path = Path() - - private val badgeRect = RectF() - private val textBounds = Rect() - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - val fg = icon?.foreground ?: return - val bg = icon?.background - canvas.getClipBounds(drawRect) - drawRect.left += paddingLeft - drawRect.top += paddingTop - drawRect.right -= paddingRight - drawRect.bottom -= paddingBottom - val (bmp, c) = BitmapHolder.getBitmapAndCanvas((drawRect.width() * 1.8).toInt()) - c.getClipBounds(bmpDrawRect) - fg.bounds = bmpDrawRect - if (bg != null) { - bg.bounds = bmpDrawRect - when (shape) { - IconShape.PlatformDefault -> { - path.rewind() - val matrix = transformMatrix!! - val bounds = platformShapeBounds!! - val shape = platformShape!! - shape.computeBounds(bounds, true) - matrix.setRectToRect( - bounds, - badgeRect.also { drawRect.toRectF(it) }, - Matrix.ScaleToFit.CENTER - ) - path.rewind() - shape.transform(matrix, path) - canvas.drawPath(path, maskPaint) - } - IconShape.Circle -> { - canvas.drawOval( - drawRect.left.toFloat(), - drawRect.top.toFloat(), - drawRect.right.toFloat(), - drawRect.bottom.toFloat(), - maskPaint - ) - } - IconShape.Square -> { - canvas.drawRect(drawRect, maskPaint) - } - IconShape.RoundedSquare -> { - canvas.drawRoundRect( - drawRect.left.toFloat(), - drawRect.top.toFloat(), - drawRect.right.toFloat(), - drawRect.bottom.toFloat(), - width * 0.125f, - height * 0.125f, - maskPaint - ) - } - IconShape.Triangle -> { - path.rewind() - var cx = drawRect.left.toFloat() - var cy = drawRect.top + drawRect.height().toFloat() * 0.86f - val r = drawRect.width() - path.moveTo(cx, cy) - path.arcTo(cx - r, cy - r, cx + r, cy + r, 300f, 60f, true) - canvas.drawArc(cx - r, cy - r, cx + r, cy + r, 300f, 60f, true, maskPaint) - cx = drawRect.right.toFloat() - cy = drawRect.top + drawRect.height().toFloat() * 0.86f - path.lineTo(cx, cy) - path.arcTo(cx - r, cy - r, cx + r, cy + r, 180f, 60f, true) - canvas.drawArc(cx - r, cy - r, cx + r, cy + r, 180f, 60f, true, maskPaint) - cx = drawRect.left + drawRect.width() * 0.5f - cy = drawRect.top.toFloat() - path.lineTo(cx, cy) - path.close() - path.arcTo(cx - r, cy - r, cx + r, cy + r, 60f, 60f, true) - canvas.drawArc(cx - r, cy - r, cx + r, cy + r, 60f, 60f, true, maskPaint) - - } - IconShape.Squircle -> { - path.rewind() - val radius = drawRect.width() / 2 - val radiusToPow = pow(radius.toDouble(), 3.0) - path.moveTo(-radius.toFloat(), 0f) - for (x in -radius..radius) - path.lineTo( - x.toFloat(), - Math.cbrt(radiusToPow - Math.abs(x * x * x)).toFloat() - ) - for (x in radius downTo -radius) - path.lineTo( - x.toFloat(), - (-Math.cbrt(radiusToPow - Math.abs(x * x * x))).toFloat() - ) - path.close() - canvas.save() - canvas.translate(width / 2f, height / 2f) - canvas.drawPath(path, maskPaint) - canvas.restore() - } - IconShape.Hexagon -> { - path.rewind() - path.moveTo( - drawRect.left + drawRect.width() * 0.25f, - drawRect.top + drawRect.height() * 0.933f - ) - path.lineTo( - drawRect.left + drawRect.width() * 0.75f, - drawRect.top + drawRect.height() * 0.933f - ) - path.lineTo( - drawRect.left + drawRect.width() * 1.0f, - drawRect.top + drawRect.height() * 0.5f - ) - path.lineTo( - drawRect.left + drawRect.width() * 0.75f, - drawRect.top + drawRect.height() * 0.067f - ) - path.lineTo( - drawRect.left + drawRect.width() * 0.25f, - drawRect.top + drawRect.height() * 0.067f - ) - path.lineTo(drawRect.left.toFloat(), drawRect.top + drawRect.height() * 0.5f) - path.close() - canvas.drawPath(path, maskPaint) - } - IconShape.EasterEgg -> { - path.rewind() - path.moveTo( - 0.49999999f * drawRect.width() + drawRect.left, - 1f * drawRect.height() + drawRect.top - ) - path.lineTo( - 0.42749999f * drawRect.width() + drawRect.left, - 0.9339999999999999f * drawRect.height() + drawRect.top - ) - path.cubicTo( - 0.16999998f * drawRect.width() + drawRect.left, - 0.7005004f * drawRect.height() + drawRect.top, - 0f + drawRect.left, - 0.5460004f * drawRect.height() + drawRect.top, - 0f + drawRect.left, - 0.3575003f * drawRect.height() + drawRect.top - ) - path.cubicTo( - 0f + drawRect.left, - 0.2030004f * drawRect.height() + drawRect.top, - 0.12100002f * drawRect.width() + drawRect.left, - 0.0825004f * drawRect.height() + drawRect.top, - 0.275f * drawRect.width() + drawRect.left, - 0.0825004f * drawRect.height() + drawRect.top - ) - path.cubicTo( - 0.362f * drawRect.width() + drawRect.left, - 0.0825004f * drawRect.height() + drawRect.top, - 0.4455f * drawRect.width() + drawRect.left, - 0.123f * drawRect.height() + drawRect.top, - 0.5f * drawRect.width() + drawRect.left, - 0.1865003f * drawRect.height() + drawRect.top - ) - path.cubicTo( - 0.55449999f * drawRect.width() + drawRect.left, - 0.123f * drawRect.height() + drawRect.top, - 0.638f * drawRect.width() + drawRect.left, - 0.0825f * drawRect.height() + drawRect.top, - 0.725f * drawRect.width() + drawRect.left, - 0.0825f * drawRect.height() + drawRect.top - ) - path.cubicTo( - 0.87900006f * drawRect.width() + drawRect.left, - 0.0825004f * drawRect.height() + drawRect.top, - 1f * drawRect.width() + drawRect.left, - 0.2030004f * drawRect.height() + drawRect.top, - 1f * drawRect.width() + drawRect.left, - 0.3575003f * drawRect.height() + drawRect.top - ) - path.cubicTo( - 1f * drawRect.width() + drawRect.left, - 0.5460004f * drawRect.height() + drawRect.top, - 0.82999999f * drawRect.width() + drawRect.left, - 0.7005004f * drawRect.height() + drawRect.top, - 0.57250001f * drawRect.width() + drawRect.left, - 0.9340004f * drawRect.height() + drawRect.top - ) - path.close() - canvas.drawPath(path, maskPaint) - } - IconShape.Pentagon -> { - path.rewind() - path.moveTo( - 0.49997027f * drawRect.width() + drawRect.left, - 0.0060308f * drawRect.height() + drawRect.top - ) - path.lineTo( - 0.99994053f * drawRect.width() + drawRect.left, - 0.36928048f * drawRect.height() + drawRect.top - ) - path.lineTo( - 0.80896887f * drawRect.width() + drawRect.left, - 0.95703078f * drawRect.height() + drawRect.top - ) - path.lineTo( - 0.19097162f * drawRect.width() + drawRect.left, - 0.95703076f * drawRect.height() + drawRect.top - ) - path.lineTo( - drawRect.left.toFloat(), - 0.36928045f * drawRect.height() + drawRect.top - ) - path.close() - canvas.drawPath(path, maskPaint) - } - } - c.save() - c.scale( - backgroundScale, - backgroundScale, - bmpDrawRect.centerX().toFloat(), - bmpDrawRect.centerY().toFloat() - ) - bg.draw(c) - c.restore() - } - c.save() - c.scale( - foregroundScale, - foregroundScale, - bmpDrawRect.centerX().toFloat(), - bmpDrawRect.centerY().toFloat() - ) - fg.draw(c) - c.restore() - if (bg != null) { - canvas.drawBitmap(bmp, bmpDrawRect, drawRect, bitmapPaint) - } else { - canvas.drawBitmap(bmp, bmpDrawRect, drawRect, maskPaint) - } - if (bg != null) { - when (shape) { - IconShape.Circle -> { - canvas.drawOval( - drawRect.left.toFloat(), - drawRect.top.toFloat(), - drawRect.right.toFloat(), - drawRect.bottom.toFloat(), - shadowPaint - ) - } - IconShape.Square -> { - canvas.drawRect(drawRect, shadowPaint) - } - IconShape.RoundedSquare -> { - canvas.drawRoundRect( - drawRect.left.toFloat(), - drawRect.top.toFloat(), - drawRect.right.toFloat(), - drawRect.bottom.toFloat(), - width * 0.125f, - height * 0.125f, - shadowPaint - ) - } - IconShape.Triangle, IconShape.Hexagon, IconShape.EasterEgg, IconShape.Pentagon, IconShape.PlatformDefault -> { - canvas.drawPath(path, shadowPaint) - } - IconShape.Squircle -> { - canvas.save() - canvas.translate(width / 2f, height / 2f) - canvas.drawPath(path, shadowPaint) - canvas.restore() - } - } - } - } - - private var longClicked = false - private val longClickRunnable = Runnable { - longClicked = true - performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) - performLongClick() - } - - private var downX = 0f - private var downY = 0f - - - override fun onTouchEvent(ev: MotionEvent): Boolean { - if (!hasOnClickListeners()) return false - when (ev.action) { - MotionEvent.ACTION_DOWN -> { - animateTouchDown() - downX = ev.rawX - downY = ev.rawY - longClicked = false - handler?.postDelayed( - longClickRunnable, - ViewConfiguration.getLongPressTimeout().toLong() - ) - return true - } - MotionEvent.ACTION_MOVE -> { - if (abs(hypot(downX - ev.rawX, downY - ev.rawY)) > width * 0.25f) { - handler?.removeCallbacks(longClickRunnable) - animateTouchUp() - return false - } - } - MotionEvent.ACTION_UP -> { - animateTouchUp() - if (ev.x > 0 && ev.x < width && ev.y > 0 && ev.y < height && !longClicked) { - performClick() - } - handler?.removeCallbacks(longClickRunnable) - return false - } - MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_OUTSIDE -> { - animateTouchUp() - handler?.removeCallbacks(longClickRunnable) - return false - } - } - return true - } - - private fun animateTouchUp() { - AnimatorSet().also { - it.playTogether( - ViewPropertyObjectAnimator.animate(this).translationZ(0f).get(), - ObjectAnimator.ofFloat(this, "foregroundScale", icon?.foregroundScale ?: 1f), - ObjectAnimator.ofFloat(this, "backgroundScale", icon?.backgroundScale ?: 1f) - ) - it.duration = 300 - it.start() - } - } - - private fun animateTouchDown() { - AnimatorSet().also { - it.playTogether( - ViewPropertyObjectAnimator.animate(this).translationZ(2 * dp).get(), - ObjectAnimator.ofFloat( - this, "foregroundScale", (icon?.foregroundScale - ?: 1f) * 0.8f - ), - ObjectAnimator.ofFloat( - this, "backgroundScale", (icon?.backgroundScale - ?: 1f) * 1.2f - ) - ) - it.duration = 250 - it.start() - } - } - - - companion object: KoinComponent { - - var currentShape: IconShape = IconShape.PlatformDefault - - fun getDefaultShape(): Flow = channelFlow { - send(currentShape) - val dataStore: LauncherDataStore = get() - dataStore.data.map { it.icons.shape }.distinctUntilChanged().collectLatest { shape -> - dataStore.data.map { it.easterEgg }.distinctUntilChanged().collectLatest { ee -> - if (ee) { - currentShape = IconShape.EasterEgg - send(IconShape.EasterEgg) - } else { - currentShape = shape - send(shape) - } - } - } - } - } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt index cbd6dbe4..aa3e2bf3 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreen.kt @@ -1,17 +1,12 @@ package de.mm20.launcher2.ui.settings.appearance -import android.graphics.drawable.ColorDrawable import androidx.appcompat.app.AppCompatActivity -import androidx.appcompat.content.res.AppCompatResources -import androidx.compose.foundation.BorderStroke import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.items -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.runtime.livedata.observeAsState @@ -24,6 +19,7 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.window.Dialog +import androidx.core.content.ContextCompat import androidx.lifecycle.viewmodel.compose.viewModel import com.airbnb.lottie.LottieProperty import com.airbnb.lottie.compose.* @@ -31,7 +27,9 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPagerIndicator import com.google.accompanist.pager.rememberPagerState -import de.mm20.launcher2.icons.LauncherIcon +import de.mm20.launcher2.icons.ColorLayer +import de.mm20.launcher2.icons.StaticIconLayer +import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.preferences.Settings.* import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme @@ -58,8 +56,8 @@ fun AppearanceSettingsScreen() { title = stringResource(id = R.string.preference_layout), summary = stringResource(id = R.string.preference_layout_summary), value = layout, onValueChanged = { - viewModel.setLayout(it) - }) + viewModel.setLayout(it) + }) val theme by viewModel.theme.observeAsState() ListPreference( title = stringResource(id = R.string.preference_theme), @@ -147,6 +145,15 @@ fun AppearanceSettingsScreen() { viewModel.setIconShape(it) } ) + val adaptifyLegacyIcons by viewModel.adaptifyLegacyIcons.observeAsState() + SwitchPreference( + title = stringResource(R.string.preference_enforce_icon_shape), + summary = stringResource(R.string.preference_enforce_icon_shape_summary), + value = adaptifyLegacyIcons == true, + onValueChanged = { + viewModel.setAdaptifyLegacyIcons(it) + } + ) val themedIcons by viewModel.themedIcons.observeAsState() SwitchPreference( title = stringResource(R.string.preference_themed_icons), @@ -176,17 +183,6 @@ fun AppearanceSettingsScreen() { if (it != null) viewModel.setIconPack(it) } ) - - val legacyIconBackground by viewModel.legacyIconBackground.observeAsState() - LegacyIconBackgroundPreference( - title = stringResource(R.string.preference_legacy_icon_bg), - summary = stringResource(R.string.preference_legacy_icon_bg_summary), - value = legacyIconBackground, - onValueChanged = { - viewModel.setLegacyIconBackground(it) - }, - iconShape = iconShape - ) } PreferenceCategory(stringResource(R.string.preference_category_searchbar)) { val searchBarStyle by viewModel.searchBarStyle.observeAsState() @@ -370,13 +366,15 @@ fun IconShapePreference( ) { ShapedLauncherIcon( size = 48.dp, - icon = LauncherIcon( - foreground = AppCompatResources.getDrawable( - LocalContext.current, - R.mipmap.ic_launcher_foreground - )!!, - foregroundScale = 1.5f, - background = ColorDrawable(LocalContext.current.getColor(R.color.ic_launcher_background)) + icon = StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = ContextCompat.getDrawable( + LocalContext.current, + R.mipmap.ic_launcher_foreground + )!!, + scale = 1.5f, + ), + ColorLayer(LocalContext.current.getColor(R.color.ic_launcher_background)) ), onClick = { onValueChanged(it) @@ -399,82 +397,6 @@ fun IconShapePreference( } } -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun LegacyIconBackgroundPreference( - title: String, - summary: String? = null, - value: IconSettings.LegacyIconBackground?, - onValueChanged: (IconSettings.LegacyIconBackground) -> Unit, - iconShape: IconSettings.IconShape -) { - var showDialog by remember { mutableStateOf(false) } - Preference(title = title, summary = summary, onClick = { showDialog = true }) - - if (showDialog && value != null) { - val colors = remember { - IconSettings.LegacyIconBackground.values() - .filter { it != IconSettings.LegacyIconBackground.UNRECOGNIZED } - } - Dialog(onDismissRequest = { showDialog = false }) { - Surface( - tonalElevation = 16.dp, - shadowElevation = 16.dp, - shape = MaterialTheme.shapes.extraLarge, - modifier = Modifier - .fillMaxWidth() - .padding(vertical = 16.dp), - ) { - Column( - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = title, - style = MaterialTheme.typography.titleLarge, - modifier = Modifier.padding( - start = 24.dp, end = 24.dp, top = 16.dp, bottom = 8.dp - ) - ) - LazyVerticalGrid( - columns = GridCells.Adaptive(96.dp), - modifier = Modifier - .fillMaxWidth() - .padding(bottom = 16.dp, start = 16.dp, end = 16.dp) - ) { - items(colors) { - Column( - modifier = Modifier - .padding(8.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - ShapedLauncherIcon( - size = 48.dp, - icon = LauncherIcon( - foreground = AppCompatResources.getDrawable( - LocalContext.current, - R.mipmap.ic_launcher_foreground - )!!, - background = null, - autoGenerateBackgroundMode = when (it) { - IconSettings.LegacyIconBackground.Dynamic -> LauncherIcon.BACKGROUND_DYNAMIC - IconSettings.LegacyIconBackground.None -> LauncherIcon.BACKGROUND_NONE - else -> LauncherIcon.BACKGROUND_WHITE - } - ), - onClick = { - onValueChanged(it) - showDialog = false - } - ) - } - } - } - } - } - } - } -} - @OptIn(ExperimentalPagerApi::class) @Composable fun LayoutPreference( diff --git a/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreenVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreenVM.kt index 01e76834..27b0fd8e 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreenVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/settings/appearance/AppearanceSettingsScreenVM.kt @@ -9,7 +9,6 @@ import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme -import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground import de.mm20.launcher2.preferences.Settings.SearchBarSettings import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -108,14 +107,14 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent { } } - val legacyIconBackground = dataStore.data.map { it.icons.legacyIconBg }.asLiveData() - fun setLegacyIconBackground(legacyIconBackground: LegacyIconBackground) { + val adaptifyLegacyIcons = dataStore.data.map { it.icons.adaptify }.asLiveData() + fun setAdaptifyLegacyIcons(adaptify: Boolean) { viewModelScope.launch { dataStore.updateData { it.toBuilder() .setIcons( it.icons.toBuilder() - .setLegacyIconBg(legacyIconBackground) + .setAdaptify(adaptify) ) .build() } diff --git a/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt b/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt index bb88b1c4..0163231f 100644 --- a/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt +++ b/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt @@ -3,22 +3,13 @@ package de.mm20.launcher2.search.data import android.content.Context import android.content.Intent import android.graphics.Color -import android.graphics.Typeface import android.graphics.drawable.ColorDrawable import android.net.Uri -import androidx.core.graphics.drawable.toBitmap -import androidx.palette.graphics.Palette +import androidx.core.content.ContextCompat import coil.imageLoader import coil.request.ImageRequest -import de.mm20.launcher2.graphics.TextDrawable -import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.ktx.dp -import de.mm20.launcher2.ktx.sp -import de.mm20.launcher2.preferences.Settings -import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground +import de.mm20.launcher2.icons.* import de.mm20.launcher2.websites.R -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import java.util.concurrent.ExecutionException class Website( @@ -31,7 +22,10 @@ class Website( ) : Searchable() { override val key = "web://$url" - override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? { + override suspend fun loadIcon( + context: Context, + size: Int, + ): LauncherIcon? { if (favicon.isEmpty()) return null try { val request = ImageRequest.Builder(context) @@ -40,11 +34,13 @@ class Website( .allowHardware(false) .build() val icon = context.imageLoader.execute(request).drawable ?: return null - return LauncherIcon( - foreground = icon, - background = color.let { ColorDrawable(it) }, - foregroundScale = 0.7f, - autoGenerateBackgroundMode = legacyIconBackground.number + + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = icon, + scale = 0.7f, + ), + backgroundLayer = ColorLayer(color) ) } catch (e: ExecutionException) { return null @@ -52,19 +48,20 @@ class Website( } - override fun getPlaceholderIcon(context: Context): LauncherIcon { - val drawable = if (label.isNotEmpty()) { - TextDrawable( - label[0].toString(), - typeface = Typeface.DEFAULT_BOLD, - fontSize = 40 * context.sp, - height = (48 * context.dp).toInt() + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { + if (label.isNotBlank()) { + return StaticLauncherIcon( + foregroundLayer = TextLayer(text = label[0].toString(), color = Color.WHITE), + backgroundLayer = ColorLayer(Color.LTGRAY) ) - } else context.getDrawable(R.drawable.ic_website)!! - return LauncherIcon( - foreground = drawable, - background = ColorDrawable(if (color != 0) color else Color.LTGRAY), - foregroundScale = 1f + } + + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = ContextCompat.getDrawable(context, R.drawable.ic_website)!!, + scale = 0.5f, + ), + backgroundLayer = ColorLayer(if (color != 0) color else Color.LTGRAY) ) } diff --git a/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt b/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt index fd5ff5d1..2ebd2c83 100644 --- a/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt +++ b/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt @@ -3,46 +3,40 @@ package de.mm20.launcher2.search.data import android.content.Context import android.content.Intent import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.net.Uri -import android.os.Bundle -import android.text.Spanned import androidx.browser.customtabs.CustomTabsIntent -import androidx.core.text.HtmlCompat -import androidx.core.text.toHtml +import androidx.core.content.ContextCompat +import de.mm20.launcher2.icons.ColorLayer +import de.mm20.launcher2.icons.StaticIconLayer +import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.wikipedia.R -import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.helper.NetworkUtils -import okhttp3.OkHttpClient -import okhttp3.Request -import org.json.JSONException -import org.json.JSONObject -import java.io.IOException -import kotlin.math.min class Wikipedia( - override val label: String, - val id: Long, - val text: String, - val image: String?, - val wikipediaUrl: String, + override val label: String, + val id: Long, + val text: String, + val image: String?, + val wikipediaUrl: String, ) : Searchable() { override val key = "wikipedia://$wikipediaUrl:$id" - override fun getPlaceholderIcon(context: Context): LauncherIcon { - return LauncherIcon( - foreground = context.getDrawable(R.drawable.ic_wikipedia)!!, - background = ColorDrawable(0xFFF0F0F0.toInt()) + override fun getPlaceholderIcon(context: Context): StaticLauncherIcon { + return StaticLauncherIcon( + foregroundLayer = StaticIconLayer( + icon = ContextCompat.getDrawable(context, R.drawable.ic_wikipedia)!!, + scale = 1f + ), + backgroundLayer = ColorLayer(0xFFF0F0F0.toInt()) ) } override fun getLaunchIntent(context: Context): Intent? { val intent = CustomTabsIntent - .Builder() - .setToolbarColor(Color.BLACK) - .enableUrlBarHiding() - .setShowTitle(true) - .build() + .Builder() + .setToolbarColor(Color.BLACK) + .enableUrlBarHiding() + .setShowTitle(true) + .build() val uri = "${wikipediaUrl}/wiki?curid=$id" intent.intent.data = Uri.parse(uri) return intent.intent