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 {
isEnabled = true
summary = "%s"
value = packs.indexOfFirst { it.packageName == iconPackManager.selectedIconPack }.toString()
}
setOnPreferenceChangeListener { _, newValue ->
val index = (newValue as String).toInt()
if (index == -1) iconPackManager.selectIconPack("")
else {
iconPackManager.selectIconPack(packs[index].packageName)
}
iconRepository.recreate()
true
}
}
}
findPreference<Preference>("legacy_icon_bg")?.setOnPreferenceChangeListener { _, _ ->
iconRepository.recreate()
true
}
findPreference<Preference>("themed_icons")?.setOnPreferenceChangeListener { _, _ ->
iconRepository.recreate()
true
}

View File

@ -10,6 +10,8 @@ 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
class AppInstallation(
val session: PackageInstaller.SessionInfo
@ -38,7 +40,7 @@ class AppInstallation(
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 foreground = BitmapDrawable(context.resources, icon)
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.isAtLeastApiLevel
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.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 icon = withContext(Dispatchers.IO) {
launcherApps.getShortcutIconDrawable(
@ -87,7 +89,7 @@ class AppShortcut(
return LauncherIcon(
foreground = icon,
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.ShortcutInfo
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 de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.getSerialNumber
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.withContext
import org.koin.core.component.KoinComponent
@ -68,29 +73,26 @@ class LauncherApp(
return launcherActivityInfo.user
}
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? {
override suspend fun loadIcon(context: Context, size: Int, legacyIconBackground: LegacyIconBackground): LauncherIcon? {
try {
val icon =
withContext(Dispatchers.IO) {
launcherActivityInfo.getIcon(context.resources.displayMetrics.densityDpi)
} ?: return null
when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> {
return LauncherIcon(
foreground = icon.foreground ?: return null,
background = icon.background,
foregroundScale = 1.5f,
backgroundScale = 1.5f
)
}
else -> {
return LauncherIcon(
foreground = icon,
foregroundScale = 0.7f,
autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt()
)
}
if (icon is AdaptiveIconDrawable) {
return LauncherIcon(
foreground = icon.foreground ?: return null,
background = icon.background,
foregroundScale = 1.5f,
backgroundScale = 1.5f
)
} else {
return LauncherIcon(
foreground = icon,
foregroundScale = 0.7f,
autoGenerateBackgroundMode = legacyIconBackground.number
)
}
} catch (e: PackageManager.NameNotFoundException) {
return null

View File

@ -25,7 +25,7 @@ open class LauncherIcon(
private fun updateBackgroundColor() {
if (background == null) {
when (autoGenerateBackgroundMode) {
BACKGROUND_COLOR -> {
BACKGROUND_DYNAMIC -> {
val palette = Palette
.from(foreground.toBitmap())
.generate()
@ -77,8 +77,8 @@ open class LauncherIcon(
}
companion object {
const val BACKGROUND_NONE = 0
const val BACKGROUND_COLOR = 1
const val BACKGROUND_NONE = 1
const val BACKGROUND_DYNAMIC = 0
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.PermissionsManager
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.withContext
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 bmp = withContext(Dispatchers.IO) {
val uri = ContactsContract.Contacts.getLookupUri(id, lookupKey) ?: return@withContext null
@ -66,7 +68,9 @@ class Contact(
?.asBitmap()
} ?: return null
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
}
}
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.util.Size
import androidx.core.content.FileProvider
import androidx.core.database.getStringOrNull
import androidx.exifinterface.media.ExifInterface
import de.mm20.launcher2.files.R
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.formatToString
import de.mm20.launcher2.media.ThumbnailUtilsCompat
import de.mm20.launcher2.permissions.PermissionGroup
import de.mm20.launcher2.permissions.PermissionsManager
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.withContext
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import java.io.IOException
import java.util.*
import java.io.File as JavaIOFile
open class LocalFile(
@ -45,7 +41,7 @@ open class LocalFile(
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
when {
mimeType.startsWith("image/") -> {
@ -58,7 +54,7 @@ open class LocalFile(
return LauncherIcon(
foreground = BitmapDrawable(context.resources, thumbnail),
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
autoGenerateBackgroundMode = legacyIconBackground.number
)
}
mimeType.startsWith("video/") -> {
@ -70,7 +66,7 @@ open class LocalFile(
} ?: return null
return LauncherIcon(
foreground = BitmapDrawable(context.resources, thumbnail),
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
autoGenerateBackgroundMode = legacyIconBackground.number
)
}
mimeType.startsWith("audio/") -> {
@ -97,7 +93,7 @@ open class LocalFile(
thumbnail ?: return null
return LauncherIcon(
foreground = BitmapDrawable(context.resources, thumbnail),
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
autoGenerateBackgroundMode = legacyIconBackground.number
)
}
@ -107,7 +103,7 @@ open class LocalFile(
pkgInfo?.applicationInfo?.loadIcon(context.packageManager)
} ?: return null
when {
Build.VERSION.SDK_INT > Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> {
icon is AdaptiveIconDrawable -> {
return LauncherIcon(
foreground = icon.foreground,
background = icon.background,
@ -119,7 +115,7 @@ open class LocalFile(
return LauncherIcon(
foreground = icon,
foregroundScale = 0.7f,
autoGenerateBackgroundMode = LauncherIcon.BACKGROUND_COLOR
autoGenerateBackgroundMode = legacyIconBackground.number
)
}
}

View File

@ -26,10 +26,6 @@ class OneDriveFile(
override val isStoredInCloud = true
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? {
return null
}
override fun getLaunchIntent(context: Context): Intent? {
return Intent(Intent.ACTION_VIEW).apply {
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_category_search_unitconverter">Einheitenrechner</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_color">Dynamisch</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_location_disabled_summary">Not supported by this provider</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_color">Dynamic</string>
<string name="preference_legacy_icon_bg_white">White</string>

View File

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

View File

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

View File

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

View File

@ -28,24 +28,6 @@ private val SUPPORTED_GRAYSCALE_MAP_PROVIDERS = arrayOf(
class IconPackManager(
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> {
return withContext(Dispatchers.IO) {
AppDatabase.getInstance(context).iconDao().getInstalledIconPacks().map {

View File

@ -6,16 +6,17 @@ import android.content.Intent
import android.content.IntentFilter
import android.util.LruCache
import de.mm20.launcher2.icons.providers.*
import de.mm20.launcher2.preferences.LauncherDataStore
import de.mm20.launcher2.preferences.LauncherPreferences
import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.*
class IconRepository(
val context: Context,
private val iconPackManager: IconPackManager,
private val dynamicIconController: DynamicIconController,
private val dataStore: LauncherDataStore
) {
private val appReceiver = object : BroadcastReceiver() {
@ -28,7 +29,7 @@ class IconRepository(
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
init {
@ -41,33 +42,64 @@ class IconRepository(
addAction(Intent.ACTION_PACKAGE_CHANGED)
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 {
var icon = cache.get(searchable.key)
if (icon != null) {
emit(icon)
return@flow
}
icon = placeholderProvider.getIcon(searchable, size)
emit(icon)
for (provider in iconProviders) {
val ic = provider.getIcon(searchable, size)
if (ic != null) {
icon = ic
if (icon is DynamicLauncherIcon) {
dynamicIconController.registerIcon(icon)
}
break
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
}
}
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() {
placeholderProvider = if (LauncherPreferences.instance.themedIcons) {
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
suspend fun getInstalledIconPacks(): List<IconPack> {
return iconPackManager.getInstalledIconPacks()
}
}

View File

@ -6,5 +6,5 @@ import org.koin.dsl.module
val iconsModule = module {
single { DynamicIconController(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,
foregroundScale = foregroundScale,
backgroundScale = 1f,
/** Not needed, we already have a background **/
BACKGROUND_WHITE
) {
var currentDay = 0

View File

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

View File

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

View File

@ -2,10 +2,14 @@ package de.mm20.launcher2.icons.providers
import android.content.Context
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.preferences.Settings
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? {
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.widget.Toast
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 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
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 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.ui.icons.PlaceholderIcon
import de.mm20.launcher2.ui.icons.getPlaceholderIcon
@ -48,7 +50,7 @@ fun ShapedLauncherIcon(
LaunchedEffect(item) {
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?) {
super.onCreate(savedInstanceState)
val iconRepository: IconRepository by inject()
iconRepository.recreate()
WindowCompat.setDecorFitsSystemWindows(window, false)

View File

@ -1,13 +1,15 @@
package de.mm20.launcher2.ui.settings.appearance
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.content.res.AppCompatResources
import androidx.compose.foundation.ExperimentalFoundationApi
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.LazyVerticalGrid
import androidx.compose.foundation.lazy.items
@ -30,7 +32,6 @@ import com.google.accompanist.pager.HorizontalPagerIndicator
import com.google.accompanist.pager.rememberPagerState
import de.mm20.launcher2.icons.LauncherIcon
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.Theme
import de.mm20.launcher2.preferences.Settings.IconSettings
@ -111,7 +112,7 @@ fun AppearanceSettingsScreen() {
)
}
PreferenceCategory(stringResource(R.string.preference_category_icons)) {
val iconShape by viewModel.iconShape.observeAsState()
val iconShape by viewModel.iconShape.observeAsState(IconSettings.IconShape.PlatformDefault)
IconShapePreference(
title = stringResource(R.string.preference_icon_shape),
summary = getShapeName(iconShape),
@ -120,6 +121,46 @@ fun AppearanceSettingsScreen() {
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)) {
val searchBarStyle by viewModel.searchBarStyle.observeAsState()
@ -263,14 +304,19 @@ fun IconShapePreference(
.padding(bottom = 16.dp, start = 16.dp, end = 16.dp)
) {
items(shapes) {
Column(modifier = Modifier
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally) {
Column(
modifier = Modifier
.padding(8.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
AndroidView(factory = { context ->
LauncherIconView(context).apply {
shape = it
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))
)
setOnClickListener { _ ->
@ -287,7 +333,90 @@ fun IconShapePreference(
getShapeName(it) ?: "",
textAlign = TextAlign.Center,
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
private fun getShapeName(shape: IconSettings.IconShape?): String? {
return stringResource(when (shape) {
IconSettings.IconShape.Triangle -> R.string.preference_icon_shape_triangle
IconSettings.IconShape.Hexagon -> R.string.preference_icon_shape_hexagon
IconSettings.IconShape.RoundedSquare -> R.string.preference_icon_shape_rounded_square
IconSettings.IconShape.Squircle -> R.string.preference_icon_shape_squircle
IconSettings.IconShape.Square -> R.string.preference_icon_shape_square
IconSettings.IconShape.Pentagon -> R.string.preference_icon_shape_pentagon
IconSettings.IconShape.PlatformDefault -> R.string.preference_icon_shape_platform
IconSettings.IconShape.Circle -> R.string.preference_icon_shape_circle
else -> return null
})
return stringResource(
when (shape) {
IconSettings.IconShape.Triangle -> R.string.preference_icon_shape_triangle
IconSettings.IconShape.Hexagon -> R.string.preference_icon_shape_hexagon
IconSettings.IconShape.RoundedSquare -> R.string.preference_icon_shape_rounded_square
IconSettings.IconShape.Squircle -> R.string.preference_icon_shape_squircle
IconSettings.IconShape.Square -> R.string.preference_icon_shape_square
IconSettings.IconShape.Pentagon -> R.string.preference_icon_shape_pentagon
IconSettings.IconShape.PlatformDefault -> R.string.preference_icon_shape_platform
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 androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.ViewModel
import androidx.lifecycle.asLiveData
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.*
import de.mm20.launcher2.icons.IconPack
import de.mm20.launcher2.icons.IconRepository
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
@ -18,6 +19,8 @@ import org.koin.core.component.inject
class AppearanceSettingsScreenVM : ViewModel(), KoinComponent {
private val dataStore: LauncherDataStore by inject()
private val iconRepository: IconRepository by inject()
val theme = dataStore.data.map { it.appearance.theme }.asLiveData()
fun setTheme(theme: Theme) {
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.icons.LauncherIcon
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -28,7 +30,7 @@ class Website(
) : Searchable() {
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
try {
val request = ImageRequest.Builder(context)
@ -36,18 +38,11 @@ class Website(
.size(size)
.build()
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(
foreground = icon,
background = ColorDrawable(color),
foregroundScale = 0.7f
background = color.let { ColorDrawable(it) },
foregroundScale = 0.7f,
autoGenerateBackgroundMode = legacyIconBackground.number
)
} catch (e: ExecutionException) {
return null

View File

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