(Absolutely awful) picker for any icon from any pack

This commit is contained in:
MM20 2022-07-28 22:48:12 +02:00
parent a2a22bd257
commit 6152c566f0
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
11 changed files with 185 additions and 58 deletions

View File

@ -19,6 +19,9 @@ interface IconDao {
@Query("SELECT * FROM Icons WHERE componentName = :componentName AND (type = 'app' OR type = 'calendar')") @Query("SELECT * FROM Icons WHERE componentName = :componentName AND (type = 'app' OR type = 'calendar')")
suspend fun getIconsFromAllPacks(componentName: String): List<IconEntity> suspend fun getIconsFromAllPacks(componentName: String): List<IconEntity>
@Query("SELECT * FROM Icons WHERE iconPack = :iconPack AND (type = 'app' OR type = 'calendar') LIMIT :limit OFFSET :offset")
suspend fun getIcons(iconPack: String, offset: Int, limit: Int): List<IconEntity>
@Query("DELETE FROM Icons WHERE iconPack = :iconPack") @Query("DELETE FROM Icons WHERE iconPack = :iconPack")
fun deleteIcons(iconPack: String) fun deleteIcons(iconPack: String)

View File

@ -39,7 +39,8 @@ dependencies {
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.androidx.palette) implementation(libs.androidx.palette)
implementation(libs.androidx.paging.runtime)
implementation(libs.androidx.paging.common)
implementation(libs.materialcomponents.core) implementation(libs.materialcomponents.core)

View File

@ -191,6 +191,12 @@ class IconPackManager(
.map { IconPackIcon(it) } .map { IconPackIcon(it) }
} }
suspend fun getIcons(iconPack: String, offset: Int, limit: Int): List<IconPackIcon> {
val iconDao = appDatabase.iconDao()
return iconDao.getIcons(iconPack, offset, limit)
.map { IconPackIcon(it) }
}
private suspend fun getIconBack(iconPack: String): String? { private suspend fun getIconBack(iconPack: String): String? {
val iconDao = appDatabase.iconDao() val iconDao = appDatabase.iconDao()
val iconbacks = iconDao.getIconBacks(iconPack) val iconbacks = iconDao.getIconBacks(iconPack)

View File

@ -0,0 +1,54 @@
package de.mm20.launcher2.icons
import androidx.paging.PagingSource
import androidx.paging.PagingState
import de.mm20.launcher2.customattrs.CustomIconPackIcon
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
import de.mm20.launcher2.icons.transformations.apply
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal class IconPackPagingSource(
private val iconPackManager: IconPackManager,
private val iconPack: String,
private val transformations: List<LauncherIconTransformation>
) : PagingSource<Int, CustomIconWithPreview>() {
override fun getRefreshKey(state: PagingState<Int, CustomIconWithPreview>): Int? {
return null
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CustomIconWithPreview> {
val page = params.key ?: 0
val icons = withContext(Dispatchers.IO) {
iconPackManager.getIcons(iconPack, page, page + params.loadSize)
}
val customIcons = mutableListOf<CustomIconWithPreview>()
withContext(Dispatchers.Default) {
for (icon in icons) {
val data = CustomIconPackIcon(iconPack, icon.componentName?.flattenToString() ?: continue)
val ic = iconPackManager.getIcon(
iconPack,
icon.componentName
) ?: continue
customIcons.add(
CustomIconWithPreview(
preview = transformations.apply(ic),
customIcon = data,
)
)
}
}
return LoadResult.Page(
data = customIcons,
prevKey = if (page > 0) page - 1 else null,
nextKey = if (icons.size == params.loadSize) page + 1 else null
)
}
}

View File

@ -6,10 +6,12 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.graphics.Color import android.graphics.Color
import android.util.LruCache import android.util.LruCache
import androidx.paging.PagingSource
import de.mm20.launcher2.customattrs.* import de.mm20.launcher2.customattrs.*
import de.mm20.launcher2.icons.providers.* import de.mm20.launcher2.icons.providers.*
import de.mm20.launcher2.icons.transformations.LauncherIconTransformation import de.mm20.launcher2.icons.transformations.LauncherIconTransformation
import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation import de.mm20.launcher2.icons.transformations.LegacyToAdaptiveTransformation
import de.mm20.launcher2.icons.transformations.apply
import de.mm20.launcher2.preferences.LauncherDataStore import de.mm20.launcher2.preferences.LauncherDataStore
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
@ -112,10 +114,10 @@ class IconRepository(
val placeholder = placeholderProvider?.getIcon(searchable, size) val placeholder = placeholderProvider?.getIcon(searchable, size)
placeholder?.let { send(it) } placeholder?.let { send(it) }
icon = getFirstIcon(searchable, size, provs) icon = provs.getFirstIcon(searchable, size)
if (icon != null) { if (icon != null) {
icon = applyTransformations(icon, transforms) icon = transforms.apply(icon)
cache.put(searchable.key + customIcon.hashCode(), icon) cache.put(searchable.key + customIcon.hashCode(), icon)
send(icon) send(icon)
@ -172,21 +174,17 @@ class IconRepository(
suspend fun getCustomIconSuggestions( suspend fun getCustomIconSuggestions(
searchable: Searchable, searchable: Searchable,
size: Int size: Int
): List<CustomIconSuggestion> { ): List<CustomIconWithPreview> {
val suggestions = mutableListOf<CustomIconSuggestion>() val suggestions = mutableListOf<CustomIconWithPreview>()
var rawIcon = getFirstIcon(searchable, size, iconProviders.first()) val rawIcon = iconProviders.first().getFirstIcon(searchable, size) ?: return emptyList()
if (rawIcon == null) {
return emptyList()
}
val defaultTransformations = transformations.first() val defaultTransformations = transformations.first()
val defaultTransformedIcon = applyTransformations(rawIcon, defaultTransformations) val defaultTransformedIcon = defaultTransformations.apply(rawIcon)
suggestions.add( suggestions.add(
CustomIconSuggestion( CustomIconWithPreview(
defaultTransformedIcon, defaultTransformedIcon,
null, null,
) )
@ -229,11 +227,11 @@ class IconRepository(
val transformations = getTransformations(it) ?: defaultTransformations val transformations = getTransformations(it) ?: defaultTransformations
val providers = getProviders(it) val providers = getProviders(it)
val icon = getFirstIcon(searchable, size, providers) ?: rawIcon val icon = providers.getFirstIcon(searchable, size) ?: rawIcon
CustomIconSuggestion( CustomIconWithPreview(
icon = applyTransformations(icon, transformations), preview = transformations.apply(icon),
data = it, customIcon = it,
) )
} }
@ -261,11 +259,11 @@ class IconRepository(
providerOptions.mapNotNull { providerOptions.mapNotNull {
val providers = getProviders(it) val providers = getProviders(it)
val icon = getFirstIcon(searchable, size, providers) ?: return@mapNotNull null val icon = providers.getFirstIcon(searchable, size) ?: return@mapNotNull null
CustomIconSuggestion( CustomIconWithPreview(
icon = applyTransformations(icon, defaultTransformations), preview = defaultTransformations.apply(icon),
data = it, customIcon = it,
) )
} }
@ -275,31 +273,8 @@ class IconRepository(
} }
private suspend fun getFirstIcon( suspend fun getAllIconsFromPack(iconPack: String): PagingSource<Int, CustomIconWithPreview> {
searchable: Searchable, return IconPackPagingSource(iconPackManager, iconPack, transformations.first())
size: Int,
providers: List<IconProvider>
): LauncherIcon? {
for (provider in providers) {
val icon = provider.getIcon(searchable, size)
if (icon != null) {
return icon
}
}
return null
}
private suspend fun applyTransformations(
icon: LauncherIcon,
transformations: List<LauncherIconTransformation>
): LauncherIcon {
var transformedIcon = icon
if (transformedIcon is StaticLauncherIcon) {
for (transformation in transformations) {
transformedIcon = transformation.transform(transformedIcon as StaticLauncherIcon)
}
}
return transformedIcon
} }
fun setCustomIcon(searchable: Searchable, icon: CustomIcon?) { fun setCustomIcon(searchable: Searchable, icon: CustomIcon?) {
@ -308,7 +283,7 @@ class IconRepository(
} }
data class CustomIconSuggestion( data class CustomIconWithPreview(
val icon: LauncherIcon, val preview: LauncherIcon,
val data: CustomIcon?, val customIcon: CustomIcon?,
) )

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.icons.providers package de.mm20.launcher2.icons.providers
import android.content.ComponentName
import de.mm20.launcher2.customattrs.CustomIconPackIcon import de.mm20.launcher2.customattrs.CustomIconPackIcon
import de.mm20.launcher2.icons.IconPackManager import de.mm20.launcher2.icons.IconPackManager
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
@ -11,10 +12,9 @@ class CustomIconPackIconProvider(
private val iconPackManager: IconPackManager, private val iconPackManager: IconPackManager,
) : IconProvider { ) : 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
return iconPackManager.getIcon( return iconPackManager.getIcon(
customIcon.iconPackPackage, customIcon.iconPackPackage,
searchable.launcherActivityInfo.componentName ComponentName.unflattenFromString(customIcon.iconComponentName) ?: return null
) )
} }
} }

View File

@ -5,4 +5,17 @@ import de.mm20.launcher2.search.data.Searchable
interface IconProvider { interface IconProvider {
suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon?
}
internal suspend fun Iterable<IconProvider>.getFirstIcon(
searchable: Searchable,
size: Int
): LauncherIcon? {
for (provider in this) {
val icon = provider.getIcon(searchable, size)
if (icon != null) {
return icon
}
}
return null
} }

View File

@ -1,7 +1,18 @@
package de.mm20.launcher2.icons.transformations package de.mm20.launcher2.icons.transformations
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.icons.StaticLauncherIcon import de.mm20.launcher2.icons.StaticLauncherIcon
internal interface LauncherIconTransformation { internal interface LauncherIconTransformation {
suspend fun transform(icon: StaticLauncherIcon): StaticLauncherIcon suspend fun transform(icon: StaticLauncherIcon): StaticLauncherIcon
}
internal suspend fun Iterable<LauncherIconTransformation>.apply(icon: LauncherIcon): LauncherIcon {
var transformedIcon = icon
if (transformedIcon is StaticLauncherIcon) {
for (transformation in this) {
transformedIcon = transformation.transform(transformedIcon as StaticLauncherIcon)
}
}
return transformedIcon
} }

View File

@ -238,9 +238,9 @@ dependencyResolutionManagement {
.to("androidx.navigation", "navigation-compose") .to("androidx.navigation", "navigation-compose")
.version("2.5.0-rc02") .version("2.5.0-rc02")
alias("androidx.paging.runtime") alias("androidx.paging.common")
.to("androidx.paging", "paging-runtime") .to("androidx.paging", "paging-common-ktx")
.version("2.5.0-rc02") .version("3.2.0-alpha01")
alias("androidx.paging.compose") alias("androidx.paging.compose")
.to("androidx.paging", "paging-compose") .to("androidx.paging", "paging-compose")
.version("1.0.0-alpha15") .version("1.0.0-alpha15")

View File

@ -6,9 +6,13 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyItemScope
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items import androidx.compose.foundation.lazy.grid.items
import androidx.paging.compose.itemsIndexed
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
@ -24,6 +28,8 @@ import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.collectAsLazyPagingItems
import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.Badge
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.R
@ -108,6 +114,15 @@ fun CustomizeSearchableSheet(
val suggestions by val suggestions by
remember { viewModel.getIconSuggestions(iconSizePx.toInt()) } remember { viewModel.getIconSuggestions(iconSizePx.toInt()) }
.observeAsState(emptyList()) .observeAsState(emptyList())
val iconPackIcons by remember {
viewModel.getAllIconsFromAllIconPacks()
}.observeAsState(emptyList())
val pagingItems = iconPackIcons.map {
it.flow.collectAsLazyPagingItems()
}
LazyVerticalGrid(columns = GridCells.Fixed(LocalGridColumns.current)) { LazyVerticalGrid(columns = GridCells.Fixed(LocalGridColumns.current)) {
items(suggestions) { items(suggestions) {
Box( Box(
@ -116,15 +131,49 @@ fun CustomizeSearchableSheet(
) { ) {
ShapedLauncherIcon( ShapedLauncherIcon(
size = iconSize, size = iconSize,
icon = it.icon, icon = it.preview,
onClick = { onClick = {
viewModel.pickIcon(it.data) viewModel.pickIcon(it.customIcon)
} }
) )
} }
} }
for (pager in pagingItems) {
itemsIndexed(pager) { index, item ->
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.padding(vertical = 8.dp)
) {
ShapedLauncherIcon(
size = iconSize,
icon = item?.preview,
onClick = {
viewModel.pickIcon(item?.customIcon)
}
)
}
}
}
} }
} }
} }
}
fun <T : Any> LazyGridScope.itemsIndexed(
items: LazyPagingItems<T>,
key: ((index: Int, item: T) -> Any)? = null,
itemContent: @Composable LazyGridScope.(index: Int, value: T?) -> Unit
) {
items(
count = items.itemCount,
key = if (key == null) null else { index ->
val item = items.peek(index)
if (item == null) {
} else {
key(index, item)
}
}
) { index ->
this@itemsIndexed.itemContent(index, items[index])
}
} }

View File

@ -1,15 +1,14 @@
package de.mm20.launcher2.ui.launcher.search.common.customattrs package de.mm20.launcher2.ui.launcher.search.common.customattrs
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData import androidx.lifecycle.liveData
import androidx.paging.Pager
import androidx.paging.PagingConfig
import de.mm20.launcher2.customattrs.CustomIcon import de.mm20.launcher2.customattrs.CustomIcon
import de.mm20.launcher2.icons.CustomIconSuggestion
import de.mm20.launcher2.icons.IconRepository import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import org.koin.androidx.compose.inject
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.inject import org.koin.core.component.inject
@ -40,4 +39,20 @@ class CustomizeSearchableSheetVM(
iconRepository.setCustomIcon(searchable, icon) iconRepository.setCustomIcon(searchable, icon)
closeIconPicker() closeIconPicker()
} }
fun getAllIconsFromAllIconPacks() = liveData {
emit(emptyList())
val iconPacks = iconRepository.getInstalledIconPacks()
emit(iconPacks.map {
val source = iconRepository.getAllIconsFromPack(it.packageName)
Pager(
PagingConfig(pageSize = 20, enablePlaceholders = false, maxSize = 200),
) {
source
}
})
}
} }