(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')")
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")
fun deleteIcons(iconPack: String)

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package de.mm20.launcher2.icons.providers
import android.content.ComponentName
import de.mm20.launcher2.customattrs.CustomIconPackIcon
import de.mm20.launcher2.icons.IconPackManager
import de.mm20.launcher2.icons.LauncherIcon
@ -11,10 +12,9 @@ class CustomIconPackIconProvider(
private val iconPackManager: IconPackManager,
) : IconProvider {
override suspend fun getIcon(searchable: Searchable, size: Int): LauncherIcon? {
if (searchable !is LauncherApp) return null
return iconPackManager.getIcon(
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 {
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
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.icons.StaticLauncherIcon
internal interface LauncherIconTransformation {
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")
.version("2.5.0-rc02")
alias("androidx.paging.runtime")
.to("androidx.paging", "paging-runtime")
.version("2.5.0-rc02")
alias("androidx.paging.common")
.to("androidx.paging", "paging-common-ktx")
.version("3.2.0-alpha01")
alias("androidx.paging.compose")
.to("androidx.paging", "paging-compose")
.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.fillMaxWidth
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.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.paging.compose.itemsIndexed
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
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.res.stringResource
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.search.data.Searchable
import de.mm20.launcher2.ui.R
@ -108,6 +114,15 @@ fun CustomizeSearchableSheet(
val suggestions by
remember { viewModel.getIconSuggestions(iconSizePx.toInt()) }
.observeAsState(emptyList())
val iconPackIcons by remember {
viewModel.getAllIconsFromAllIconPacks()
}.observeAsState(emptyList())
val pagingItems = iconPackIcons.map {
it.flow.collectAsLazyPagingItems()
}
LazyVerticalGrid(columns = GridCells.Fixed(LocalGridColumns.current)) {
items(suggestions) {
Box(
@ -116,15 +131,49 @@ fun CustomizeSearchableSheet(
) {
ShapedLauncherIcon(
size = iconSize,
icon = it.icon,
icon = it.preview,
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
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.paging.Pager
import androidx.paging.PagingConfig
import de.mm20.launcher2.customattrs.CustomIcon
import de.mm20.launcher2.icons.CustomIconSuggestion
import de.mm20.launcher2.icons.IconRepository
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.search.data.Searchable
import kotlinx.coroutines.flow.Flow
import org.koin.androidx.compose.inject
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject
@ -40,4 +39,20 @@ class CustomizeSearchableSheetVM(
iconRepository.setCustomIcon(searchable, icon)
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
}
})
}
}