Migrate icon settings

This commit is contained in:
MM20 2022-01-28 23:15:27 +01:00
parent 75529b8f42
commit f72e7a1993
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
28 changed files with 347 additions and 190 deletions

View File

@ -62,27 +62,19 @@ class PreferencesAppearanceFragment : PreferenceFragmentCompat() {
} else { } else {
isEnabled = true isEnabled = true
summary = "%s" summary = "%s"
value = packs.indexOfFirst { it.packageName == iconPackManager.selectedIconPack }.toString()
} }
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
val index = (newValue as String).toInt() val index = (newValue as String).toInt()
if (index == -1) iconPackManager.selectIconPack("")
else {
iconPackManager.selectIconPack(packs[index].packageName)
}
iconRepository.recreate()
true true
} }
} }
} }
findPreference<Preference>("legacy_icon_bg")?.setOnPreferenceChangeListener { _, _ -> findPreference<Preference>("legacy_icon_bg")?.setOnPreferenceChangeListener { _, _ ->
iconRepository.recreate()
true true
} }
findPreference<Preference>("themed_icons")?.setOnPreferenceChangeListener { _, _ -> findPreference<Preference>("themed_icons")?.setOnPreferenceChangeListener { _, _ ->
iconRepository.recreate()
true true
} }

View File

@ -10,6 +10,8 @@ import android.graphics.drawable.ColorDrawable
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import de.mm20.launcher2.applications.R import de.mm20.launcher2.applications.R
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
class AppInstallation( class AppInstallation(
val session: PackageInstaller.SessionInfo val session: PackageInstaller.SessionInfo
@ -38,7 +40,7 @@ class AppInstallation(
foregroundScale = 0.5f) foregroundScale = 0.5f)
} }
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? { override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? {
val icon = session.appIcon ?: return getPlaceholderIcon(context) val icon = session.appIcon ?: return getPlaceholderIcon(context)
val foreground = BitmapDrawable(context.resources, icon) val foreground = BitmapDrawable(context.resources, icon)
foreground.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply { foreground.colorFilter = ColorMatrixColorFilter(ColorMatrix().apply {

View File

@ -17,6 +17,8 @@ import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.getSerialNumber import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.ktx.isAtLeastApiLevel
import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -68,7 +70,7 @@ class AppShortcut(
) )
} }
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? { override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? {
val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
val icon = withContext(Dispatchers.IO) { val icon = withContext(Dispatchers.IO) {
launcherApps.getShortcutIconDrawable( launcherApps.getShortcutIconDrawable(
@ -87,7 +89,7 @@ class AppShortcut(
return LauncherIcon( return LauncherIcon(
foreground = icon, foreground = icon,
foregroundScale = 1f, foregroundScale = 1f,
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() autoGenerateBackgroundMode = legacyIconBackground.number
) )
} }
} }

View File

@ -8,11 +8,16 @@ import android.content.pm.LauncherApps
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.pm.ShortcutInfo import android.content.pm.ShortcutInfo
import android.graphics.drawable.AdaptiveIconDrawable import android.graphics.drawable.AdaptiveIconDrawable
import android.os.* import android.os.Build
import android.os.Bundle
import android.os.Process
import android.os.UserHandle
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.getSerialNumber import de.mm20.launcher2.ktx.getSerialNumber
import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -68,29 +73,26 @@ class LauncherApp(
return launcherActivityInfo.user return launcherActivityInfo.user
} }
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? { override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? {
try { try {
val icon = val icon =
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
launcherActivityInfo.getIcon(context.resources.displayMetrics.densityDpi) launcherActivityInfo.getIcon(context.resources.displayMetrics.densityDpi)
} ?: return null } ?: return null
when { if (icon is AdaptiveIconDrawable) {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> { return LauncherIcon(
return LauncherIcon( foreground = icon.foreground ?: return null,
foreground = icon.foreground ?: return null, background = icon.background,
background = icon.background, foregroundScale = 1.5f,
foregroundScale = 1.5f, backgroundScale = 1.5f
backgroundScale = 1.5f )
) } else {
} return LauncherIcon(
else -> { foreground = icon,
return LauncherIcon( foregroundScale = 0.7f,
foreground = icon, autoGenerateBackgroundMode = legacyIconBackground.number
foregroundScale = 0.7f, )
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
)
}
} }
} catch (e: PackageManager.NameNotFoundException) { } catch (e: PackageManager.NameNotFoundException) {
return null return null

View File

@ -25,7 +25,7 @@ open class LauncherIcon(
private fun updateBackgroundColor() { private fun updateBackgroundColor() {
if (background == null) { if (background == null) {
when (autoGenerateBackgroundMode) { when (autoGenerateBackgroundMode) {
BACKGROUND_COLOR -> { BACKGROUND_DYNAMIC -> {
val palette = Palette val palette = Palette
.from(foreground.toBitmap()) .from(foreground.toBitmap())
.generate() .generate()
@ -77,8 +77,8 @@ open class LauncherIcon(
} }
companion object { companion object {
const val BACKGROUND_NONE = 0 const val BACKGROUND_NONE = 1
const val BACKGROUND_COLOR = 1 const val BACKGROUND_DYNAMIC = 0
const val BACKGROUND_WHITE = 2 const val BACKGROUND_WHITE = 2
} }
} }

View File

@ -17,6 +17,8 @@ import de.mm20.launcher2.ktx.sp
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
@ -58,7 +60,7 @@ class Contact(
) )
} }
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? { override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? {
val contentResolver = context.contentResolver val contentResolver = context.contentResolver
val bmp = withContext(Dispatchers.IO) { val bmp = withContext(Dispatchers.IO) {
val uri = ContactsContract.Contacts.getLookupUri(id, lookupKey) ?: return@withContext null val uri = ContactsContract.Contacts.getLookupUri(id, lookupKey) ?: return@withContext null
@ -66,7 +68,9 @@ class Contact(
?.asBitmap() ?.asBitmap()
} ?: return null } ?: return null
return LauncherIcon( return LauncherIcon(
foreground = bmp.toDrawable(context.resources) foreground = bmp.toDrawable(context.resources),
background = null,
autoGenerateBackgroundMode = legacyIconBackground.number
) )
} }

View File

@ -35,8 +35,4 @@ class GDriveFile(
flags = Intent.FLAG_ACTIVITY_NEW_TASK flags = Intent.FLAG_ACTIVITY_NEW_TASK
} }
} }
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? {
return null
}
} }

View File

@ -13,21 +13,17 @@ import android.provider.MediaStore
import android.text.format.DateUtils import android.text.format.DateUtils
import android.util.Size import android.util.Size
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.database.getStringOrNull
import androidx.exifinterface.media.ExifInterface import androidx.exifinterface.media.ExifInterface
import de.mm20.launcher2.files.R import de.mm20.launcher2.files.R
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.formatToString import de.mm20.launcher2.ktx.formatToString
import de.mm20.launcher2.media.ThumbnailUtilsCompat import de.mm20.launcher2.media.ThumbnailUtilsCompat
import de.mm20.launcher2.permissions.PermissionGroup import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.permissions.PermissionsManager import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
import de.mm20.launcher2.preferences.LauncherPreferences
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import java.io.IOException import java.io.IOException
import java.util.*
import java.io.File as JavaIOFile import java.io.File as JavaIOFile
open class LocalFile( open class LocalFile(
@ -45,7 +41,7 @@ open class LocalFile(
override val isStoredInCloud = false override val isStoredInCloud = false
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? { override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? {
if (!JavaIOFile(path).exists()) return null if (!JavaIOFile(path).exists()) return null
when { when {
mimeType.startsWith("image/") -> { mimeType.startsWith("image/") -> {
@ -58,7 +54,7 @@ open class LocalFile(
return LauncherIcon( return LauncherIcon(
foreground = BitmapDrawable(context.resources, thumbnail), foreground = BitmapDrawable(context.resources, thumbnail),
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR autoGenerateBackgroundMode = legacyIconBackground.number
) )
} }
mimeType.startsWith("video/") -> { mimeType.startsWith("video/") -> {
@ -70,7 +66,7 @@ open class LocalFile(
} ?: return null } ?: return null
return LauncherIcon( return LauncherIcon(
foreground = BitmapDrawable(context.resources, thumbnail), foreground = BitmapDrawable(context.resources, thumbnail),
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR autoGenerateBackgroundMode = legacyIconBackground.number
) )
} }
mimeType.startsWith("audio/") -> { mimeType.startsWith("audio/") -> {
@ -97,7 +93,7 @@ open class LocalFile(
thumbnail ?: return null thumbnail ?: return null
return LauncherIcon( return LauncherIcon(
foreground = BitmapDrawable(context.resources, thumbnail), foreground = BitmapDrawable(context.resources, thumbnail),
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR autoGenerateBackgroundMode = legacyIconBackground.number
) )
} }
@ -107,7 +103,7 @@ open class LocalFile(
pkgInfo?.applicationInfo?.loadIcon(context.packageManager) pkgInfo?.applicationInfo?.loadIcon(context.packageManager)
} ?: return null } ?: return null
when { when {
Build.VERSION.SDK_INT > Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> { icon is AdaptiveIconDrawable -> {
return LauncherIcon( return LauncherIcon(
foreground = icon.foreground, foreground = icon.foreground,
background = icon.background, background = icon.background,
@ -119,7 +115,7 @@ open class LocalFile(
return LauncherIcon( return LauncherIcon(
foreground = icon, foreground = icon,
foregroundScale = 0.7f, foregroundScale = 0.7f,
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR autoGenerateBackgroundMode = legacyIconBackground.number
) )
} }
} }

View File

@ -26,10 +26,6 @@ class OneDriveFile(
override val isStoredInCloud = true override val isStoredInCloud = true
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? {
return null
}
override fun getLaunchIntent(context: Context): Intent? { override fun getLaunchIntent(context: Context): Intent? {
return Intent(Intent.ACTION_VIEW).apply { return Intent(Intent.ACTION_VIEW).apply {
data = Uri.parse(webUrl) data = Uri.parse(webUrl)

View File

@ -257,6 +257,7 @@
<string name="preference_location_disabled_summary">Von diesem Wetterdienst nicht unterstützt</string> <string name="preference_location_disabled_summary">Von diesem Wetterdienst nicht unterstützt</string>
<string name="preference_category_search_unitconverter">Einheitenrechner</string> <string name="preference_category_search_unitconverter">Einheitenrechner</string>
<string name="preference_legacy_icon_bg">Symbol-Hintergrund</string> <string name="preference_legacy_icon_bg">Symbol-Hintergrund</string>
<string name="preference_legacy_icon_bg_summary">Stil von Legacy-Icons</string>
<string name="preference_legacy_icon_bg_none">Keiner</string> <string name="preference_legacy_icon_bg_none">Keiner</string>
<string name="preference_legacy_icon_bg_color">Dynamisch</string> <string name="preference_legacy_icon_bg_color">Dynamisch</string>
<string name="preference_legacy_icon_bg_white">Weiß</string> <string name="preference_legacy_icon_bg_white">Weiß</string>

View File

@ -257,6 +257,7 @@
<string name="preference_calendar_max_events">Number of events</string> <string name="preference_calendar_max_events">Number of events</string>
<string name="preference_location_disabled_summary">Not supported by this provider</string> <string name="preference_location_disabled_summary">Not supported by this provider</string>
<string name="preference_legacy_icon_bg">Icon background</string> <string name="preference_legacy_icon_bg">Icon background</string>
<string name="preference_legacy_icon_bg_summary">Legacy icon style</string>
<string name="preference_legacy_icon_bg_none">None</string> <string name="preference_legacy_icon_bg_none">None</string>
<string name="preference_legacy_icon_bg_color">Dynamic</string> <string name="preference_legacy_icon_bg_color">Dynamic</string>
<string name="preference_legacy_icon_bg_white">White</string> <string name="preference_legacy_icon_bg_white">White</string>

View File

@ -16,14 +16,11 @@ class CalendarDynamicLauncherIcon(
backgroundScale: Float, backgroundScale: Float,
val packageName: String, val packageName: String,
val drawableIds: IntArray, val drawableIds: IntArray,
autoGenerateBackgroundMode: Int
) : DynamicLauncherIcon( ) : DynamicLauncherIcon(
foreground, foreground,
background, background,
foregroundScale, foregroundScale,
backgroundScale, backgroundScale,
/** Not needed, we already have a background **/
autoGenerateBackgroundMode
) { ) {
var currentDay = 0 var currentDay = 0
@ -39,7 +36,7 @@ class CalendarDynamicLauncherIcon(
Executors.newSingleThreadExecutor().execute { Executors.newSingleThreadExecutor().execute {
val currentDayDrawable = resources.getDrawableOrNull(drawableIds[day - 1]) val currentDayDrawable = resources.getDrawableOrNull(drawableIds[day - 1])
?: return@execute ?: return@execute
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && currentDayDrawable is AdaptiveIconDrawable) { if (currentDayDrawable is AdaptiveIconDrawable) {
foreground = currentDayDrawable.foreground foreground = currentDayDrawable.foreground
background = currentDayDrawable.background background = currentDayDrawable.background
foregroundScale = 1.5f foregroundScale = 1.5f

View File

@ -23,9 +23,7 @@ class ClockDynamicLauncherIcon(
foreground, foreground,
background, background,
foregroundScale, foregroundScale,
backgroundScale, backgroundScale
/** Not needed, we already have a background **/
LauncherIcon.BACKGROUND_WHITE
) { ) {

View File

@ -7,15 +7,13 @@ abstract class DynamicLauncherIcon(
foreground: Drawable, foreground: Drawable,
background: Drawable?, background: Drawable?,
foregroundScale: Float, foregroundScale: Float,
backgroundScale: Float, backgroundScale: Float
autoGenerateBackgroundMode: Int
) )
: LauncherIcon( : LauncherIcon(
foreground, foreground,
background, background,
foregroundScale, foregroundScale,
backgroundScale, backgroundScale,
autoGenerateBackgroundMode
) { ) {
abstract fun update(context: Context) abstract fun update(context: Context)

View File

@ -28,24 +28,6 @@ private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf(
class IconPackManager( class IconPackManager(
val context: Context val context: Context
) { ) {
var selectedIconPack: String
get() {
return context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
.getString(KEY_ICON_PACK, "")!!
}
set(value) {
Log.d("MM20", "Selected icon pack: $value")
context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
.edit()
.putString(KEY_ICON_PACK, value)
.apply()
}
fun selectIconPack(iconPack: String) {
selectedIconPack = iconPack
}
suspend fun getInstalledIconPacks(): List<IconPack> { suspend fun getInstalledIconPacks(): List<IconPack> {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map { AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map {

View File

@ -6,16 +6,17 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.util.LruCache import android.util.LruCache
import de.mm20.launcher2.icons.providers.* import de.mm20.launcher2.icons.providers.*
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.flow
class IconRepository( class IconRepository(
val context: Context, val context: Context,
private val iconPackManager: IconPackManager, private val iconPackManager: IconPackManager,
private val dynamicIconController: DynamicIconController, private val dynamicIconController: DynamicIconController,
private val dataStore: LauncherDataStore
) { ) {
private val appReceiver = object : BroadcastReceiver() { private val appReceiver = object : BroadcastReceiver() {
@ -28,7 +29,7 @@ class IconRepository(
private val cache = LruCache<String, LauncherIcon>(200) private val cache = LruCache<String, LauncherIcon>(200)
private var iconProviders: List<IconProvider> = listOf() private var iconProviders: MutableStateFlow<List<IconProvider>> = MutableStateFlow(listOf())
private lateinit var placeholderProvider: IconProvider private lateinit var placeholderProvider: IconProvider
init { init {
@ -41,33 +42,64 @@ class IconRepository(
addAction(Intent.ACTION_PACKAGE_CHANGED) addAction(Intent.ACTION_PACKAGE_CHANGED)
addDataScheme("package") addDataScheme("package")
}) })
recreate()
scope.launch {
dataStore.data.map { it.icons }.distinctUntilChanged().collectLatest {
val placeholderProvider = if (it.themedIcons) {
ThemedPlaceholderIconProvider(context)
} else {
PlaceholderIconProvider(context)
}
val providers = mutableListOf<IconProvider>()
if (it.themedIcons) {
providers.add(ThemedIconProvider(context))
}
if (it.iconPack.isNotBlank()) {
providers.add(IconPackIconProvider(context, it.iconPack, it.legacyIconBg))
}
providers.add(GoogleClockIconProvider(context))
providers.add(CalendarIconProvider(context))
providers.add(SystemIconProvider(context, it.legacyIconBg))
providers.add(placeholderProvider)
cache.evictAll()
this@IconRepository.placeholderProvider = placeholderProvider
iconProviders.value = providers
}
}
} }
fun getIcon(searchable: Searchable, size: Int): Flow<LauncherIcon> = flow { fun getIcon(searchable: Searchable, size: Int): Flow<LauncherIcon> = channelFlow {
var icon = cache.get(searchable.key) iconProviders.collectLatest { providers ->
if (icon != null) { var icon = cache.get(searchable.key)
emit(icon) if (icon != null) {
return@flow send(icon)
} return@collectLatest
}
icon = placeholderProvider.getIcon(searchable, size)
emit(icon) val placeholder = placeholderProvider.getIcon(searchable, size)
placeholder?.let { send(it) }
for (provider in iconProviders) {
val ic = provider.getIcon(searchable, size) for (provider in providers) {
if (ic != null) { val ic = provider.getIcon(searchable, size)
icon = ic if (ic != null) {
if (icon is DynamicLauncherIcon) { if (ic is DynamicLauncherIcon) {
dynamicIconController.registerIcon(icon) dynamicIconController.registerIcon(ic)
} }
break icon = ic
break
}
}
if (icon != null) {
cache.put(searchable.key, icon)
send(icon)
} else {
icon = placeholderProvider.getIcon(searchable, size)
send(icon)
} }
}
if (icon != null) {
cache.put(searchable.key, icon)
emit(icon)
} }
} }
@ -85,27 +117,7 @@ class IconRepository(
} }
} }
fun recreate() { suspend fun getInstalledIconPacks(): List<IconPack> {
placeholderProvider = if (LauncherPreferences.instance.themedIcons) { return iconPackManager.getInstalledIconPacks()
ThemedPlaceholderIconProvider(context)
} else {
PlaceholderIconProvider(context)
}
val providers = mutableListOf<IconProvider>()
if (LauncherPreferences.instance.themedIcons) {
providers.add(ThemedIconProvider(context))
}
if (iconPackManager.selectedIconPack.isNotBlank()) {
providers.add(IconPackIconProvider(context, iconPackManager.selectedIconPack))
}
providers.add(GoogleClockIconProvider(context))
providers.add(CalendarIconProvider(context))
providers.add(SystemIconProvider(context))
providers.add(placeholderProvider)
cache.evictAll()
iconProviders = providers
} }
} }

View File

@ -6,5 +6,5 @@ import org.koin.dsl.module
val iconsModule = module { val iconsModule = module {
single { DynamicIconController(androidContext()) } single { DynamicIconController(androidContext()) }
single { IconPackManager(androidContext()) } single { IconPackManager(androidContext()) }
single { IconRepository(androidContext(), get(), get()) } single { IconRepository(androidContext(), get(), get(), get()) }
} }

View File

@ -21,8 +21,6 @@ class ThemedCalendarDynamicLauncherIcon(
background = background, background = background,
foregroundScale = foregroundScale, foregroundScale = foregroundScale,
backgroundScale = 1f, backgroundScale = 1f,
/** Not needed, we already have a background **/
BACKGROUND_WHITE
) { ) {
var currentDay = 0 var currentDay = 0

View File

@ -42,8 +42,7 @@ class CalendarIconProvider(val context: Context): IconProvider {
foregroundScale = 1.5f, foregroundScale = 1.5f,
backgroundScale = 1.5f, backgroundScale = 1.5f,
packageName = component.packageName, packageName = component.packageName,
drawableIds = drawableIds, drawableIds = drawableIds
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
) )
} }
} }

View File

@ -17,13 +17,16 @@ import de.mm20.launcher2.database.AppDatabase
import de.mm20.launcher2.icons.CalendarDynamicLauncherIcon import de.mm20.launcher2.icons.CalendarDynamicLauncherIcon
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.randomElementOrNull import de.mm20.launcher2.ktx.randomElementOrNull
import de.mm20.launcher2.preferences.IconShape import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.data.LauncherApp import de.mm20.launcher2.search.data.LauncherApp
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import kotlin.math.roundToInt import kotlin.math.roundToInt
class IconPackIconProvider(val context: Context, val iconPack: String): IconProvider { class IconPackIconProvider(
private val context: Context,
private val iconPack: String,
private val legacyIconBackground: Settings.IconSettings.LegacyIconBackground
): IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null if (searchable !is LauncherApp) return null
val res = try { val res = try {
@ -57,18 +60,14 @@ class IconPackIconProvider(val context: Context, val iconPack: String): IconProv
LauncherIcon( LauncherIcon(
foreground = drawable, foreground = drawable,
foregroundScale = getScale(), foregroundScale = getScale(),
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() autoGenerateBackgroundMode = legacyIconBackground.number
) )
} }
} }
} }
private fun getScale(): Float { private fun getScale(): Float {
return when (LauncherPreferences.instance.iconShape) { return 0.7f
IconShape.CIRCLE, IconShape.PLATFORM_DEFAULT -> 0.7f
else -> 0.8f
}
} }
private suspend fun generateIcon( private suspend fun generateIcon(
@ -151,7 +150,7 @@ class IconPackIconProvider(val context: Context, val iconPack: String): IconProv
return LauncherIcon( return LauncherIcon(
foreground = BitmapDrawable(context.resources, bitmap), foreground = BitmapDrawable(context.resources, bitmap),
foregroundScale = getScale(), foregroundScale = getScale(),
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() autoGenerateBackgroundMode = legacyIconBackground.number
) )
} }
@ -200,7 +199,6 @@ class IconPackIconProvider(val context: Context, val iconPack: String): IconProv
backgroundScale = 1.5f, backgroundScale = 1.5f,
packageName = iconPack, packageName = iconPack,
drawableIds = drawableIds, drawableIds = drawableIds,
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
) )
} }
} }

View File

@ -2,10 +2,14 @@ package de.mm20.launcher2.icons.providers
import android.content.Context import android.content.Context
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
class SystemIconProvider(val context: Context) : IconProvider { class SystemIconProvider(
private val context: Context,
private val legacyIconBackground: Settings.IconSettings.LegacyIconBackground
) : IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? { override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
return searchable.loadIcon(context, size) return searchable.loadIcon(context, size, legacyIconBackground)
} }
} }

View File

@ -6,6 +6,8 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
import de.mm20.launcher2.search.R import de.mm20.launcher2.search.R
import java.text.Collator import java.text.Collator
@ -32,7 +34,7 @@ abstract class Searchable : Comparable<Searchable> {
} }
} }
open suspend fun loadIcon(context: Context, size: Int): LauncherIcon? = null open suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? = null
abstract fun getPlaceholderIcon(context: Context): LauncherIcon abstract fun getPlaceholderIcon(context: Context): LauncherIcon
override fun compareTo(other: Searchable): Int { override fun compareTo(other: Searchable): Int {

View File

@ -23,6 +23,8 @@ import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.ui.icons.PlaceholderIcon import de.mm20.launcher2.ui.icons.PlaceholderIcon
import de.mm20.launcher2.ui.icons.getPlaceholderIcon import de.mm20.launcher2.ui.icons.getPlaceholderIcon
@ -48,7 +50,7 @@ fun ShapedLauncherIcon(
LaunchedEffect(item) { LaunchedEffect(item) {
icon = withContext(Dispatchers.IO) { icon = withContext(Dispatchers.IO) {
item.loadIcon(context, iconSize) item.loadIcon(context, iconSize, LegacyIconBackground.Dynamic)
} }
} }

View File

@ -35,8 +35,6 @@ class LauncherActivity : BaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
val iconRepository: IconRepository by inject()
iconRepository.recreate()
WindowCompat.setDecorFitsSystemWindows(window, false) WindowCompat.setDecorFitsSystemWindows(window, false)

View File

@ -1,13 +1,15 @@
package de.mm20.launcher2.ui.settings.appearance package de.mm20.launcher2.ui.settings.appearance
import android.content.Context
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.GridCells import androidx.compose.foundation.lazy.GridCells
import androidx.compose.foundation.lazy.LazyVerticalGrid import androidx.compose.foundation.lazy.LazyVerticalGrid
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
@ -30,7 +32,6 @@ import com.google.accompanist.pager.HorizontalPagerIndicator
import com.google.accompanist.pager.rememberPagerState import com.google.accompanist.pager.rememberPagerState
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.dp
import de.mm20.launcher2.preferences.IconShape
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
import de.mm20.launcher2.preferences.Settings.IconSettings import de.mm20.launcher2.preferences.Settings.IconSettings
@ -111,7 +112,7 @@ fun AppearanceSettingsScreen() {
) )
} }
PreferenceCategory(stringResource(R.string.preference_category_icons)) { PreferenceCategory(stringResource(R.string.preference_category_icons)) {
val iconShape by viewModel.iconShape.observeAsState() val iconShape by viewModel.iconShape.observeAsState(IconSettings.IconShape.PlatformDefault)
IconShapePreference( IconShapePreference(
title = stringResource(R.string.preference_icon_shape), title = stringResource(R.string.preference_icon_shape),
summary = getShapeName(iconShape), summary = getShapeName(iconShape),
@ -120,6 +121,46 @@ fun AppearanceSettingsScreen() {
viewModel.setIconShape(it) viewModel.setIconShape(it)
} }
) )
val themedIcons by viewModel.themedIcons.observeAsState()
SwitchPreference(
title = stringResource(R.string.preference_themed_icons),
summary = stringResource(R.string.preference_themed_icons_summary),
value = themedIcons == true,
onValueChanged = {
viewModel.setThemedIcons(it)
}
)
val iconPack by viewModel.iconPack.observeAsState()
val installedIconPacks by viewModel.installedIconPacks.observeAsState(emptyList())
val items = installedIconPacks.map {
it.name to it.packageName
}
ListPreference(
title = stringResource(R.string.preference_icon_pack),
items = items,
summary = if (items.size <= 1) {
stringResource(R.string.preference_icon_pack_summary_empty)
} else {
items.firstOrNull { iconPack == it.value }?.label ?: "System"
},
enabled = installedIconPacks.size > 1,
value = iconPack,
onValueChanged = {
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)) { PreferenceCategory(stringResource(R.string.preference_category_searchbar)) {
val searchBarStyle by viewModel.searchBarStyle.observeAsState() val searchBarStyle by viewModel.searchBarStyle.observeAsState()
@ -263,14 +304,19 @@ fun IconShapePreference(
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp) .padding(bottom = 16.dp, start = 16.dp, end = 16.dp)
) { ) {
items(shapes) { items(shapes) {
Column(modifier = Modifier Column(
.padding(8.dp), modifier = Modifier
horizontalAlignment = Alignment.CenterHorizontally) { .padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
AndroidView(factory = { context -> AndroidView(factory = { context ->
LauncherIconView(context).apply { LauncherIconView(context).apply {
shape = it shape = it
icon = LauncherIcon( icon = LauncherIcon(
foreground = AppCompatResources.getDrawable(context, R.mipmap.ic_launcher_foreground)!!, foreground = AppCompatResources.getDrawable(
context,
R.mipmap.ic_launcher_foreground
)!!,
background = ColorDrawable(context.getColor(R.color.ic_launcher_background)) background = ColorDrawable(context.getColor(R.color.ic_launcher_background))
) )
setOnClickListener { _ -> setOnClickListener { _ ->
@ -287,7 +333,90 @@ fun IconShapePreference(
getShapeName(it) ?: "", getShapeName(it) ?: "",
textAlign = TextAlign.Center, textAlign = TextAlign.Center,
style = MaterialTheme.typography.labelMedium, style = MaterialTheme.typography.labelMedium,
modifier = Modifier.padding(top = 4.dp)) modifier = Modifier.padding(top = 4.dp)
)
}
}
}
}
}
}
}
}
@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 = RoundedCornerShape(16.dp),
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(
cells = 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
) {
AndroidView(factory = { context ->
LauncherIconView(context).apply {
shape = iconShape
icon = LauncherIcon(
foreground = AppCompatResources.getDrawable(
context,
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
}
)
setOnClickListener { _ ->
onValueChanged(it)
showDialog = false
}
layoutParams = ViewGroup.LayoutParams(
(48 * context.dp).toInt(),
(48 * context.dp).toInt(),
)
}
})
} }
} }
} }
@ -300,15 +429,17 @@ fun IconShapePreference(
@Composable @Composable
private fun getShapeName(shape: IconSettings.IconShape?): String? { private fun getShapeName(shape: IconSettings.IconShape?): String? {
return stringResource(when (shape) { return stringResource(
IconSettings.IconShape.Triangle -> R.string.preference_icon_shape_triangle when (shape) {
IconSettings.IconShape.Hexagon -> R.string.preference_icon_shape_hexagon IconSettings.IconShape.Triangle -> R.string.preference_icon_shape_triangle
IconSettings.IconShape.RoundedSquare -> R.string.preference_icon_shape_rounded_square IconSettings.IconShape.Hexagon -> R.string.preference_icon_shape_hexagon
IconSettings.IconShape.Squircle -> R.string.preference_icon_shape_squircle IconSettings.IconShape.RoundedSquare -> R.string.preference_icon_shape_rounded_square
IconSettings.IconShape.Square -> R.string.preference_icon_shape_square IconSettings.IconShape.Squircle -> R.string.preference_icon_shape_squircle
IconSettings.IconShape.Pentagon -> R.string.preference_icon_shape_pentagon IconSettings.IconShape.Square -> R.string.preference_icon_shape_square
IconSettings.IconShape.PlatformDefault -> R.string.preference_icon_shape_platform IconSettings.IconShape.Pentagon -> R.string.preference_icon_shape_pentagon
IconSettings.IconShape.Circle -> R.string.preference_icon_shape_circle IconSettings.IconShape.PlatformDefault -> R.string.preference_icon_shape_platform
else -> return null IconSettings.IconShape.Circle -> R.string.preference_icon_shape_circle
}) else -> return null
}
)
} }

View File

@ -2,13 +2,14 @@ package de.mm20.launcher2.ui.settings.appearance
import android.content.Intent import android.content.Intent
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel import androidx.lifecycle.*
import androidx.lifecycle.asLiveData import de.mm20.launcher2.icons.IconPack
import androidx.lifecycle.viewModelScope import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.Settings import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.ColorScheme
import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme import de.mm20.launcher2.preferences.Settings.AppearanceSettings.Theme
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
import de.mm20.launcher2.preferences.Settings.SearchBarSettings import de.mm20.launcher2.preferences.Settings.SearchBarSettings
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -18,6 +19,8 @@ import org.koin.core.component.inject
class AppearanceSettingsScreenVM : ViewModel(), KoinComponent { class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject() private val dataStore: LauncherDataStore by inject()
private val iconRepository: IconRepository by inject()
val theme = dataStore.data.map { it.appearance.theme }.asLiveData() val theme = dataStore.data.map { it.appearance.theme }.asLiveData()
fun setTheme(theme: Theme) { fun setTheme(theme: Theme) {
viewModelScope.launch { viewModelScope.launch {
@ -93,4 +96,56 @@ class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
} }
} }
} }
val legacyIconBackground = dataStore.data.map { it.icons.legacyIconBg }.asLiveData()
fun setLegacyIconBackground(legacyIconBackground: LegacyIconBackground) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setLegacyIconBg(legacyIconBackground)
)
.build()
}
}
}
val themedIcons = dataStore.data.map { it.icons.themedIcons }.asLiveData()
fun setThemedIcons(themedIcons: Boolean) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setThemedIcons(themedIcons)
)
.build()
}
}
}
val installedIconPacks: LiveData<List<IconPack>> = liveData {
emit(
listOf(IconPack(
name = "System",
packageName = "",
version = "",
)) +
iconRepository.getInstalledIconPacks()
)
}
val iconPack = dataStore.data.map { it.icons.iconPack }.asLiveData()
fun setIconPack(iconPack: String) {
viewModelScope.launch {
dataStore.updateData {
it.toBuilder()
.setIcons(
it.icons.toBuilder()
.setIconPack(iconPack)
)
.build()
}
}
}
} }

View File

@ -13,6 +13,8 @@ import coil.request.ImageRequest
import de.mm20.launcher2.graphics.TextDrawable import de.mm20.launcher2.graphics.TextDrawable
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.sp import de.mm20.launcher2.ktx.sp
import de.mm20.launcher2.preferences.Settings
import de.mm20.launcher2.preferences.Settings.IconSettings.LegacyIconBackground
import de.mm20.launcher2.websites.R import de.mm20.launcher2.websites.R
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -28,7 +30,7 @@ class Website(
) : Searchable() { ) : Searchable() {
override val key = "web://$url" override val key = "web://$url"
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? { override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? {
if (favicon.isEmpty()) return null if (favicon.isEmpty()) return null
try { try {
val request = ImageRequest.Builder(context) val request = ImageRequest.Builder(context)
@ -36,18 +38,11 @@ class Website(
.size(size) .size(size)
.build() .build()
val icon = context.imageLoader.execute(request).drawable ?: return null val icon = context.imageLoader.execute(request).drawable ?: return null
val color = if (color != 0) color else {
withContext(Dispatchers.Default) {
Palette
.from(icon.toBitmap())
.generate()
.getLightMutedColor(Color.WHITE)
}
}
return LauncherIcon( return LauncherIcon(
foreground = icon, foreground = icon,
background = ColorDrawable(color), background = color.let { ColorDrawable(it) },
foregroundScale = 0.7f foregroundScale = 0.7f,
autoGenerateBackgroundMode = legacyIconBackground.number
) )
} catch (e: ExecutionException) { } catch (e: ExecutionException) {
return null return null

View File

@ -30,10 +30,6 @@ class Wikipedia(
) : Searchable() { ) : Searchable() {
override val key = "wikipedia://$wikipediaUrl:$id" override val key = "wikipedia://$wikipediaUrl:$id"
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? {
return null
}
override fun getPlaceholderIcon(context: Context): LauncherIcon { override fun getPlaceholderIcon(context: Context): LauncherIcon {
return LauncherIcon( return LauncherIcon(
foreground = context.getDrawable(R.drawable.ic_wikipedia)!!, foreground = context.getDrawable(R.drawable.ic_wikipedia)!!,