From 6152c566f08689159d21736e9ec6db2e2e4701fa Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Thu, 28 Jul 2022 22:48:12 +0200 Subject: [PATCH] (Absolutely awful) picker for any icon from any pack --- .../de/mm20/launcher2/database/IconDao.kt | 3 + icons/build.gradle.kts | 3 +- .../mm20/launcher2/icons/IconPackManager.kt | 6 ++ .../launcher2/icons/IconPackPagingSource.kt | 54 +++++++++++++++ .../de/mm20/launcher2/icons/IconRepository.kt | 69 ++++++------------- .../providers/CustomIconPackIconProvider.kt | 4 +- .../launcher2/icons/providers/IconProvider.kt | 13 ++++ .../LauncherIconTransformation.kt | 11 +++ settings.gradle.kts | 6 +- .../customattrs/CustomizeSearchableSheet.kt | 53 +++++++++++++- .../customattrs/CustomizeSearchableSheetVM.kt | 21 +++++- 11 files changed, 185 insertions(+), 58 deletions(-) create mode 100644 icons/src/main/java/de/mm20/launcher2/icons/IconPackPagingSource.kt diff --git a/database/src/main/java/de/mm20/launcher2/database/IconDao.kt b/database/src/main/java/de/mm20/launcher2/database/IconDao.kt index 89ca0ffe..acb2491b 100644 --- a/database/src/main/java/de/mm20/launcher2/database/IconDao.kt +++ b/database/src/main/java/de/mm20/launcher2/database/IconDao.kt @@ -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 + @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 + @Query("DELETE FROM Icons WHERE iconPack = :iconPack") fun deleteIcons(iconPack: String) diff --git a/icons/build.gradle.kts b/icons/build.gradle.kts index 732d7015..2bdb2f2f 100644 --- a/icons/build.gradle.kts +++ b/icons/build.gradle.kts @@ -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) diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt index b991b542..70bf3030 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt @@ -191,6 +191,12 @@ class IconPackManager( .map { IconPackIcon(it) } } + suspend fun getIcons(iconPack: String, offset: Int, limit: Int): List { + 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) diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconPackPagingSource.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconPackPagingSource.kt new file mode 100644 index 00000000..7ab967b6 --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconPackPagingSource.kt @@ -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 +) : PagingSource() { + override fun getRefreshKey(state: PagingState): Int? { + return null + } + + override suspend fun load(params: LoadParams): LoadResult { + val page = params.key ?: 0 + + val icons = withContext(Dispatchers.IO) { + iconPackManager.getIcons(iconPack, page, page + params.loadSize) + } + + val customIcons = mutableListOf() + 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 + ) + + } + +} \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt index 6bc2e6d0..0ec34cc6 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt @@ -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 { - val suggestions = mutableListOf() + ): List { + val suggestions = mutableListOf() - 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 - ): 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 - ): 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 { + 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?, ) \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomIconPackIconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomIconPackIconProvider.kt index 6e202abd..b6db2607 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomIconPackIconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/CustomIconPackIconProvider.kt @@ -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 ) } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt index 5a6cde1a..d8f0a767 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/providers/IconProvider.kt @@ -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.getFirstIcon( + searchable: Searchable, + size: Int +): LauncherIcon? { + for (provider in this) { + val icon = provider.getIcon(searchable, size) + if (icon != null) { + return icon + } + } + return null } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/transformations/LauncherIconTransformation.kt b/icons/src/main/java/de/mm20/launcher2/icons/transformations/LauncherIconTransformation.kt index 48463686..42204e13 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/transformations/LauncherIconTransformation.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/transformations/LauncherIconTransformation.kt @@ -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.apply(icon: LauncherIcon): LauncherIcon { + var transformedIcon = icon + if (transformedIcon is StaticLauncherIcon) { + for (transformation in this) { + transformedIcon = transformation.transform(transformedIcon as StaticLauncherIcon) + } + } + return transformedIcon } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index ef25f38d..1f922478 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -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") diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt index 2a27e52e..e4cc1964 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheet.kt @@ -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 LazyGridScope.itemsIndexed( + items: LazyPagingItems, + 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]) + } } \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt index 53e6e02e..280b5e24 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/search/common/customattrs/CustomizeSearchableSheetVM.kt @@ -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 + } + }) + + } } \ No newline at end of file