From 2b18544f5b3a4bafe8a782118d4ca6bc2c78a595 Mon Sep 17 00:00:00 2001 From: HeroBrine1st Erquilenne Date: Tue, 7 Jan 2025 00:25:29 +0300 Subject: [PATCH] Search Screen: replace file extensions with post types --- .../e621/api/search/PostsSearchOptions.kt | 38 ++++++++---- .../component/search/SearchComponent.kt | 6 +- .../e621/ui/screen/search/Search.kt | 62 +++++++++++++------ app/src/main/res/values-ru-rRU/strings.xml | 6 +- app/src/main/res/values/strings.xml | 6 +- 5 files changed, 79 insertions(+), 39 deletions(-) diff --git a/app/src/main/java/ru/herobrine1st/e621/api/search/PostsSearchOptions.kt b/app/src/main/java/ru/herobrine1st/e621/api/search/PostsSearchOptions.kt index ecdee0b1..88aab532 100644 --- a/app/src/main/java/ru/herobrine1st/e621/api/search/PostsSearchOptions.kt +++ b/app/src/main/java/ru/herobrine1st/e621/api/search/PostsSearchOptions.kt @@ -33,6 +33,13 @@ import ru.herobrine1st.e621.api.model.Rating import ru.herobrine1st.e621.api.model.Tag import ru.herobrine1st.e621.util.debug +// A simplification of filetype, as there's actually no need to differ between png and jpg +enum class PostType { + IMAGE, // png, jpg + ANIMATION, // gif + VIDEO // webm +} + @Serializable data class PostsSearchOptions( val allOf: Set = emptySet(), @@ -42,8 +49,7 @@ data class PostsSearchOptions( val orderAscending: Boolean = false, val rating: Set = emptySet(), val favouritesOf: String? = null, // "favorited_by" in api - val fileType: FileType? = null, - val fileTypeInvert: Boolean = false, + val types: Set = emptySet(), val parent: PostId = PostId.INVALID, val poolId: PoolId = -1, ) : SearchOptions { @@ -56,7 +62,21 @@ data class PostsSearchOptions( cache += noneOf.map { it.asExcluded } cache += anyOf.map { it.asAlternative } cache += optimizeRatingSelection(rating) - fileType?.extension?.let { cache += (if (fileTypeInvert) "-" else "") + "type:" + it } + val fileTypes = types.flatMap { + when (it) { + PostType.IMAGE -> listOf(FileType.PNG, FileType.JPG) + PostType.ANIMATION -> listOf(FileType.GIF) + PostType.VIDEO -> listOf(FileType.WEBM) + } + } + // API does not support OR-ing file types. Alternative tags work, but on common conditions, + // so e.g. `~type:png ~type:jpg ~anthro ~feral` returns posts that have `anthro` despite being a video) + // De Morgan's Law is here to save the day + if (fileTypes.size == 1) { + cache += "filetype:${fileTypes.single().extension}" + } else if (fileTypes.size > 1) { + cache += (FileType.entries - fileTypes - FileType.UNDEFINED).map { "-filetype:${it.extension}" } + } favouritesOf?.let { cache += "fav:$it" } (if (orderAscending) order.ascendingApiName else order.apiName)?.let { cache += "order:$it" } if (parent != PostId.INVALID) cache += "parent:${parent.value}" @@ -107,8 +127,7 @@ data class PostsSearchOptions( orderAscending = orderAscending, rating = rating.toSet(), favouritesOf = favouritesOf, - fileType = fileType, - fileTypeInvert = fileTypeInvert, + types = types, parent = parent, poolId = poolId ) @@ -131,8 +150,7 @@ data class PostsSearchOptions( var orderAscending: Boolean = false, var rating: MutableSet = mutableSetOf(), var favouritesOf: String? = null, - var fileType: FileType? = null, - var fileTypeInvert: Boolean = false, + var types: Set = mutableSetOf(), var parent: PostId = PostId.INVALID, var poolId: PoolId = -1, ) { @@ -145,8 +163,7 @@ data class PostsSearchOptions( orderAscending = orderAscending, rating = rating, favouritesOf = favouritesOf, - fileType = fileType, - fileTypeInvert = fileTypeInvert, + types = this@Builder.types, parent = parent, poolId = poolId ) @@ -162,8 +179,7 @@ data class PostsSearchOptions( orderAscending = orderAscending, rating = rating.toMutableSet(), favouritesOf = favouritesOf, - fileType = fileType, - fileTypeInvert = fileTypeInvert, + types = types, parent = parent, poolId = poolId ) diff --git a/app/src/main/java/ru/herobrine1st/e621/navigation/component/search/SearchComponent.kt b/app/src/main/java/ru/herobrine1st/e621/navigation/component/search/SearchComponent.kt index 2b97e7aa..50107eac 100644 --- a/app/src/main/java/ru/herobrine1st/e621/navigation/component/search/SearchComponent.kt +++ b/app/src/main/java/ru/herobrine1st/e621/navigation/component/search/SearchComponent.kt @@ -76,8 +76,7 @@ class SearchComponent private constructor( var orderAscending by mutableStateOf(initialSearchOptions.orderAscending) val rating = initialSearchOptions.rating.toMutableStateList() var favouritesOf by mutableStateOf(initialSearchOptions.favouritesOf ?: "") - var fileType by mutableStateOf(initialSearchOptions.fileType) - var fileTypeInvert by mutableStateOf(initialSearchOptions.fileTypeInvert) + val postTypes = initialSearchOptions.types.toMutableStateList() var parentPostId by mutableIntStateOf(initialSearchOptions.parent.value) var poolId by mutableIntStateOf(initialSearchOptions.poolId) @@ -234,8 +233,7 @@ class SearchComponent private constructor( orderAscending = orderAscending, rating = rating.toSet(), favouritesOf = favouritesOf.ifBlank { null }, - fileType = fileType, - fileTypeInvert = fileTypeInvert, + types = postTypes.toSet(), parent = PostId(parentPostId), poolId = poolId ) diff --git a/app/src/main/java/ru/herobrine1st/e621/ui/screen/search/Search.kt b/app/src/main/java/ru/herobrine1st/e621/ui/screen/search/Search.kt index 44cfd5cf..9f59acc7 100644 --- a/app/src/main/java/ru/herobrine1st/e621/ui/screen/search/Search.kt +++ b/app/src/main/java/ru/herobrine1st/e621/ui/screen/search/Search.kt @@ -82,11 +82,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import ru.herobrine1st.e621.R import ru.herobrine1st.e621.api.AutocompleteSuggestionsAPI -import ru.herobrine1st.e621.api.model.FileType import ru.herobrine1st.e621.api.model.Order import ru.herobrine1st.e621.api.model.Rating import ru.herobrine1st.e621.api.model.Tag import ru.herobrine1st.e621.api.model.TagAutocompleteSuggestion +import ru.herobrine1st.e621.api.search.PostType import ru.herobrine1st.e621.api.search.PostsSearchOptions import ru.herobrine1st.e621.module.CachedDataStore import ru.herobrine1st.e621.module.DataStoreModule @@ -323,30 +323,52 @@ fun Search( } item("file type") { SettingCard( - title = stringResource(R.string.file_type), + title = stringResource(R.string.post_type), modifier = Modifier.selectableGroup() ) { - ItemSelectionRadioButton( - selected = component.fileType == null, - text = stringResource(R.string.any) + FlowRow( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(2.dp) ) { - component.fileType = null - component.fileTypeInvert = false - } - for (v in FileType.supportedValues()) { - ItemSelectionRadioButton( - selected = v == component.fileType, - text = v.extension - ) { - component.fileType = v + CompositionLocalProvider(LocalRippleConfiguration provides null) { + for (type in PostType.entries) { + val selected = type in component.postTypes + FilterChip( + selected = selected, + onClick = { + if (selected) + component.postTypes.remove(type) + else + component.postTypes.add(type) + // Do not force any behavior: users are free to select all + // or select none as it is the same + }, + label = { + Text( + when (type) { + PostType.IMAGE -> stringResource(R.string.post_type_image) + PostType.ANIMATION -> stringResource(R.string.post_type_animation) + PostType.VIDEO -> stringResource(R.string.post_type_video) + } + ) + }, + leadingIcon = { + AnimatedVisibility( + visible = selected, + enter = fadeIn() + expandIn(expandFrom = Alignment.CenterStart), + exit = shrinkOut(shrinkTowards = Alignment.CenterStart) + fadeOut(), + ) { + Icon( + Icons.Default.Check, + null, + modifier = Modifier.size(FilterChipDefaults.IconSize) + ) + } + } + ) + } } } - ItemSelectionCheckbox( - checked = component.fileTypeInvert, - text = stringResource(R.string.file_type_invert_selection) - ) { - component.fileTypeInvert = it - } } } item("favourites of") { diff --git a/app/src/main/res/values-ru-rRU/strings.xml b/app/src/main/res/values-ru-rRU/strings.xml index 862ac5c5..310cdb91 100644 --- a/app/src/main/res/values-ru-rRU/strings.xml +++ b/app/src/main/res/values-ru-rRU/strings.xml @@ -119,8 +119,7 @@ Порт Имя пользователя Пароль - Тип файла - Исключить + Тип публикации К сожалению, данной вики-страницы не существует. Возможно, она еще не была создана. Запись чёрного списка Создать @@ -170,4 +169,7 @@ Информация о сборке "Версия: %1$s\nТип сборки: %2$s\nUser-Agent: %3$s" Не удалось получить чёрный список из аккаунта + Изображение + Видео + Анимация \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7113f798..3735d84b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -120,8 +120,7 @@ You should have received a copy of the GNU General Public License along with thi Port Username Password - File type - Exclude + Post type "There\'s no such wiki page<br />It may not have been created yet" Blacklist entry New @@ -170,4 +169,7 @@ You should have received a copy of the GNU General Public License along with thi "Version: %1$s\nBuild type: %2$s\nUser-Agent: %3$s" Couldn\'t fetch blacklist from user account Contributor + Image + Video + Animation \ No newline at end of file