Refactor icons
This commit is contained in:
parent
08e736f5d1
commit
6de89f3f79
@ -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))
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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" -> {
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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<WeakReference<(LauncherIcon) -> 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
|
||||
}
|
||||
@ -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<ClockSublayer>,
|
||||
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<ClockSublayer>,
|
||||
val scale: Float,
|
||||
val color: Int = 0,
|
||||
) : LauncherIconLayer
|
||||
|
||||
data class TextLayer(
|
||||
val text: String,
|
||||
val color: Int = 0,
|
||||
) : LauncherIconLayer
|
||||
|
||||
object TransparentLayer: LauncherIconLayer
|
||||
@ -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())
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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" -> {
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,8 +188,6 @@
|
||||
<string name="weather_condition_heavyrainandthunder">Starkregen und Gewitter</string>
|
||||
<string name="weather_condition_wind">Wind</string>
|
||||
<string name="weather_condition_unknown">Unbekannt</string>
|
||||
<string name="preference_legacy_icon_bg">Symbol-Hintergrund</string>
|
||||
<string name="preference_legacy_icon_bg_summary">Stil von Legacy-Icons</string>
|
||||
<string name="easter_egg_1">Hier gibt es keine Easter Eggs, es sei denn Ihr hättet sie mitgebracht.</string>
|
||||
<string name="easter_egg_2">Bitte, hör auf, du verschwendest deine Zeit</string>
|
||||
<string name="easter_egg_3">Ich werde es nicht noch einmal sagen: hier sind definitiv keine Easter Eggs versteckt</string>
|
||||
|
||||
@ -173,8 +173,6 @@
|
||||
<string name="shortcut_summary">Par %1$s</string>
|
||||
<string name="installation_in_progress">Installation en cours… (%1$s)</string>
|
||||
<string name="error_activity_not_found">Impossible d\'ouvrir %1$s</string>
|
||||
<string name="preference_legacy_icon_bg">Arrière-plan de l\'icône</string>
|
||||
<string name="preference_legacy_icon_bg_summary">Style d\'icône legacy</string>
|
||||
<!-- Easter egg preference toasts -->
|
||||
<string name="easter_egg_1">Il n\'y a pas d\'easter egg ici, à moins que vous ne l\'ayez amené avec vous.</string>
|
||||
<string name="easter_egg_2">Par pitié, arrêtez, vous perdez votre temps</string>
|
||||
|
||||
@ -291,7 +291,6 @@
|
||||
<string name="preference_owncloud_signin_summary">Accedi per cercare sul tuo server Owncloud</string>
|
||||
<string name="preference_about_telegram">Gruppo Telegram</string>
|
||||
<string name="preference_about_fdroid">Repository F-Droid</string>
|
||||
<string name="preference_legacy_icon_bg">Sfondo icona</string>
|
||||
<string name="preference_category_license">Licenza</string>
|
||||
<string name="preference_category_grid">Griglia</string>
|
||||
<string name="preference_grid_column_count">Numero di colonne</string>
|
||||
@ -303,7 +302,6 @@
|
||||
<string name="preference_screen_musicwidget">Musica</string>
|
||||
<string name="preference_screen_clockwidget">Orologio</string>
|
||||
<string name="preference_clockwidget_layout_vertical">Verticale</string>
|
||||
<string name="preference_legacy_icon_bg_summary">Stile icone legacy</string>
|
||||
<string name="preference_about_license_summary">Concesso in licenza sotto la GNU General Public License 3.0</string>
|
||||
<string name="preference_clock_widget_style">Stile</string>
|
||||
<string name="preference_clock_widget_style_summary">Seleziona un orologio</string>
|
||||
|
||||
@ -196,7 +196,6 @@
|
||||
<string name="preference_owncloud_signin">Zaloguj się do Owncloud</string>
|
||||
<string name="preference_about_telegram">Grupa Telegram</string>
|
||||
<string name="preference_about_fdroid">Repozytorium F-Droid</string>
|
||||
<string name="preference_legacy_icon_bg">Tło ikon</string>
|
||||
<string name="preference_owncloud_signin_summary">Zaloguj się, aby móc przeszukiwać twój serwer Owncloud</string>
|
||||
<string name="preference_about_license">Ta aplikacja jest wolnym oprogramowaniem.</string>
|
||||
<string name="preference_category_grid">Siatka</string>
|
||||
@ -212,7 +211,6 @@
|
||||
<string name="preference_clockwidget_layout">Układ</string>
|
||||
<string name="preference_clockwidget_layout_vertical">Pionowy</string>
|
||||
<string name="preference_clockwidget_layout_horizontal">Poziomy</string>
|
||||
<string name="preference_legacy_icon_bg_summary">Przestarzały styl ikon</string>
|
||||
<string name="preference_clock_widget_style_summary">Wybierz typ zegara</string>
|
||||
<string name="preference_clockwidget_date_part">Data</string>
|
||||
<string name="preference_clockwidget_date_part_summary">Pokaż aktualną datę</string>
|
||||
|
||||
@ -294,7 +294,6 @@
|
||||
<string name="preference_category_services_owncloud">Owncloud</string>
|
||||
<string name="preference_owncloud_signin_summary">Autenticar para pesquisar no seu servidor Owncloud</string>
|
||||
<string name="preference_about_fdroid">Repositório F-Droid</string>
|
||||
<string name="preference_legacy_icon_bg">Fundo do ícone</string>
|
||||
<string name="preference_category_license">Licença</string>
|
||||
<string name="preference_about_license">Este aplicativo é um software livre.</string>
|
||||
<string name="preference_about_license_summary">Licenciado sob a GNU General Public License 3.0</string>
|
||||
|
||||
@ -278,8 +278,6 @@
|
||||
<string name="preference_owncloud_signin_summary">Conectează-te pentru a căuta în serverul tău Owncloud</string>
|
||||
<string name="preference_about_telegram">Grup Telegram</string>
|
||||
<string name="preference_about_fdroid">Repository F-Droid</string>
|
||||
<string name="preference_legacy_icon_bg">Fundal pictogramă</string>
|
||||
<string name="preference_legacy_icon_bg_summary">Stilul pictogramei clasice</string>
|
||||
<string name="preference_category_license">Licență</string>
|
||||
<string name="preference_about_license">Această aplicație este un software gratuit.</string>
|
||||
<string name="preference_category_grid">Grilă</string>
|
||||
|
||||
@ -260,8 +260,6 @@
|
||||
<string name="preference_owncloud_signin">Logga in på Owncloud</string>
|
||||
<string name="preference_about_telegram">Telegram-grupp</string>
|
||||
<string name="preference_about_fdroid">F-Droid-kodförråd</string>
|
||||
<string name="preference_legacy_icon_bg">Ikonbakgrund</string>
|
||||
<string name="preference_legacy_icon_bg_summary">Äldre ikonstil</string>
|
||||
<string name="preference_category_license">Licens</string>
|
||||
<string name="preference_about_license">Denna app är fri programvara.</string>
|
||||
<string name="preference_about_license_summary">Licensierad under GNU General Public License 3.0</string>
|
||||
|
||||
@ -294,8 +294,6 @@
|
||||
<string name="preference_owncloud_signin_summary">登陆以查找你的Owncloud服务</string>
|
||||
<string name="preference_about_telegram">Telegram群组</string>
|
||||
<string name="preference_about_fdroid">F-Droid repository</string>
|
||||
<string name="preference_legacy_icon_bg">图标背景</string>
|
||||
<string name="preference_legacy_icon_bg_summary">经典图标风格</string>
|
||||
<string name="preference_category_license">许可证</string>
|
||||
<string name="preference_about_license_summary">根据 GNU 通用公共许可证 3.0 授权</string>
|
||||
<string name="preference_category_grid">网格</string>
|
||||
|
||||
@ -491,8 +491,8 @@
|
||||
<string name="preference_owncloud_signin_summary">Sign in to search your Owncloud server</string>
|
||||
<string name="preference_about_telegram">Telegram group</string>
|
||||
<string name="preference_about_fdroid">F-Droid repository</string>
|
||||
<string name="preference_legacy_icon_bg">Icon background</string>
|
||||
<string name="preference_legacy_icon_bg_summary">Legacy icon style</string>
|
||||
<string name="preference_enforce_icon_shape">Enforce shape</string>
|
||||
<string name="preference_enforce_icon_shape_summary">Apply shape to all icons including those that would normally not support it</string>
|
||||
<string name="preference_category_license">License</string>
|
||||
<string name="preference_about_license">This app is free software.</string>
|
||||
<string name="preference_about_license_summary">Licensed under the GNU General Public License 3.0</string>
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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<LauncherIconTransformation> = 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
|
||||
}
|
||||
}
|
||||
@ -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<WeakReference<DynamicLauncherIcon>>()
|
||||
|
||||
@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))
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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?,
|
||||
@ -124,7 +124,7 @@ class UpdateIconPacksWorker(val context: Context) {
|
||||
parser.setInput(inStream)
|
||||
}
|
||||
|
||||
val icons = mutableListOf<Icon>()
|
||||
val icons = mutableListOf<IconPackIcon>()
|
||||
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<Icon>()
|
||||
val icons = mutableListOf<IconPackIcon>()
|
||||
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,
|
||||
|
||||
@ -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<List<IconProvider>> = MutableStateFlow(listOf())
|
||||
private var placeholderProvider: IconProvider? = null
|
||||
|
||||
private var transformations: MutableStateFlow<List<LauncherIconTransformation>> =
|
||||
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<LauncherIconTransformation>()
|
||||
|
||||
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<LauncherIcon> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()) }
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
12
ktx/src/main/java/de/mm20/launcher2/ktx/LayerDrawable.kt
Normal file
12
ktx/src/main/java/de/mm20/launcher2/ktx/LayerDrawable.kt
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
@ -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))
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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<DataMigration<Settings>> {
|
||||
return listOf(
|
||||
@ -34,5 +33,6 @@ internal fun getMigrations(context: Context): List<DataMigration<Settings>> {
|
||||
Migration_3_4(),
|
||||
Migration_4_5(),
|
||||
Migration_5_6(),
|
||||
Migration_6_7(),
|
||||
)
|
||||
}
|
||||
@ -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("")
|
||||
|
||||
@ -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)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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<Searchable> {
|
||||
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 }
|
||||
|
||||
@ -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<ClockSublayer>,
|
||||
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<Shape> { CircleShape }
|
||||
|
||||
fun getShape(iconShape: IconShape): Shape {
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
@ -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<IconShape> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user