Migrate icon settings
This commit is contained in:
parent
75529b8f42
commit
f72e7a1993
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -35,8 +35,4 @@ class GDriveFile(
|
||||
flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loadIcon(context: Context, size: Int): LauncherIcon? {
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -23,9 +23,7 @@ class ClockDynamicLauncherIcon(
|
||||
foreground,
|
||||
background,
|
||||
foregroundScale,
|
||||
backgroundScale,
|
||||
/** Not needed, we already have a background **/
|
||||
LauncherIcon.BACKGROUND_WHITE
|
||||
backgroundScale
|
||||
) {
|
||||
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
@ -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()) }
|
||||
}
|
||||
@ -21,8 +21,6 @@ class ThemedCalendarDynamicLauncherIcon(
|
||||
background = background,
|
||||
foregroundScale = foregroundScale,
|
||||
backgroundScale = 1f,
|
||||
/** Not needed, we already have a background **/
|
||||
BACKGROUND_WHITE
|
||||
) {
|
||||
|
||||
var currentDay = 0
|
||||
|
||||
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
)
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)!!,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user