Clean search filtering.

This commit is contained in:
Thomas Andres Gomez 2022-10-16 14:51:04 +02:00
parent e176b6827a
commit e69af172af
20 changed files with 177 additions and 219 deletions

View file

@ -0,0 +1,13 @@
package com.pixelized.biblib.model.search
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
@Stable
@Immutable
enum class SearchType {
Author,
Series,
Genre,
Language;
}

View file

@ -0,0 +1,14 @@
package com.pixelized.biblib.model.search
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
@Stable
@Immutable
enum class SortType {
Book,
Author,
Series,
Genre,
Language;
}

View file

@ -1,6 +1,7 @@
package com.pixelized.biblib.repository.search package com.pixelized.biblib.repository.search
import com.pixelized.biblib.model.book.* import com.pixelized.biblib.model.book.*
import com.pixelized.biblib.model.search.SortType
interface ISearchRepository { interface ISearchRepository {

View file

@ -7,6 +7,7 @@ import com.pixelized.biblib.database.crossref.BookGenreCrossRef
import com.pixelized.biblib.database.data.* import com.pixelized.biblib.database.data.*
import com.pixelized.biblib.database.relation.BookRelation import com.pixelized.biblib.database.relation.BookRelation
import com.pixelized.biblib.model.book.* import com.pixelized.biblib.model.book.*
import com.pixelized.biblib.model.search.SortType
import javax.inject.Inject import javax.inject.Inject
class SearchRepository @Inject constructor( class SearchRepository @Inject constructor(
@ -42,6 +43,7 @@ class SearchRepository @Inject constructor(
query += args.where(argument = languageId) { query += args.where(argument = languageId) {
BookDbo.run { "$TABLE.$LANGUAGE_ID LIKE ?" } BookDbo.run { "$TABLE.$LANGUAGE_ID LIKE ?" }
} }
query += BookDbo.run { " ORDER BY $TABLE.$SORT" }
// Limit and Offset the query. // Limit and Offset the query.
query += " LIMIT $limit OFFSET $offset;" query += " LIMIT $limit OFFSET $offset;"
// compute the query // compute the query
@ -57,6 +59,7 @@ class SearchRepository @Inject constructor(
val query = AuthorDbo.run { val query = AuthorDbo.run {
"SELECT $TABLE.* FROM $TABLE" + "SELECT $TABLE.* FROM $TABLE" +
args.where(argument = search) { "$TABLE.$NAME LIKE '%'||?||'%'" } + args.where(argument = search) { "$TABLE.$NAME LIKE '%'||?||'%'" } +
" ORDER BY $TABLE.$SORT" +
" LIMIT $limit OFFSET $offset;" " LIMIT $limit OFFSET $offset;"
} }
// compute the query // compute the query
@ -72,6 +75,7 @@ class SearchRepository @Inject constructor(
val query = GenreDbo.run { val query = GenreDbo.run {
"SELECT $TABLE.* FROM $TABLE" + "SELECT $TABLE.* FROM $TABLE" +
args.where(argument = search) { "$TABLE.$NAME LIKE '%'||?||'%'" } + args.where(argument = search) { "$TABLE.$NAME LIKE '%'||?||'%'" } +
" ORDER BY $TABLE.$NAME" +
" LIMIT $limit OFFSET $offset;" " LIMIT $limit OFFSET $offset;"
} }
// compute the query // compute the query
@ -87,6 +91,7 @@ class SearchRepository @Inject constructor(
val query = SeriesDbo.run { val query = SeriesDbo.run {
"SELECT $TABLE.* FROM $TABLE" + "SELECT $TABLE.* FROM $TABLE" +
args.where(argument = search) { "$TABLE.$NAME LIKE '%'||?||'%'" } + args.where(argument = search) { "$TABLE.$NAME LIKE '%'||?||'%'" } +
" ORDER BY $TABLE.$SORT" +
" LIMIT $limit OFFSET $offset;" " LIMIT $limit OFFSET $offset;"
} }
// compute the query // compute the query
@ -102,6 +107,7 @@ class SearchRepository @Inject constructor(
val query = LanguageDbo.run { val query = LanguageDbo.run {
"SELECT $TABLE.* FROM $TABLE" + "SELECT $TABLE.* FROM $TABLE" +
args.where(argument = search) { "$TABLE.$NAME LIKE '%'||?||'%'" } + args.where(argument = search) { "$TABLE.$NAME LIKE '%'||?||'%'" } +
" ORDER BY $TABLE.$NAME" +
" LIMIT $limit OFFSET $offset;" " LIMIT $limit OFFSET $offset;"
} }
// compute the query // compute the query

View file

@ -12,7 +12,8 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.biblib.ui.screen.home.page.search.FilterSearchPage import com.pixelized.biblib.model.search.SearchType
import com.pixelized.biblib.ui.screen.home.page.search.bottom.FilterSearchPage
import com.pixelized.biblib.ui.screen.home.page.search.viewModel.BookSearchViewModel import com.pixelized.biblib.ui.screen.home.page.search.viewModel.BookSearchViewModel
import com.pixelized.biblib.ui.theme.color.ShadowPalette import com.pixelized.biblib.ui.theme.color.ShadowPalette
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -20,14 +21,14 @@ import kotlinx.coroutines.launch
val LocalBookSearchViewModel = staticCompositionLocalOf<BookSearchViewModel> { val LocalBookSearchViewModel = staticCompositionLocalOf<BookSearchViewModel> {
error("SearchViewModel is not ready yet") error("SearchViewModel is not ready yet")
} }
val LocalCategorySearchBottomSheetState = staticCompositionLocalOf<SearchBottomSheetState> { val LocalFilterSearchBottomSheetState = staticCompositionLocalOf<FilterSearchBottomSheetState> {
error("BottomSearchState is not ready yet") error("FilterSearchBottomSheetState is not ready yet")
} }
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
fun CategorySearchBottomSheet( fun SearchOptionBottomSheet(
state: SearchBottomSheetState = rememberSearchBottomSheetState(), filterState: FilterSearchBottomSheetState = rememberSearchBottomSheetState(),
bookSearchViewModel: BookSearchViewModel = hiltViewModel(), bookSearchViewModel: BookSearchViewModel = hiltViewModel(),
content: @Composable () -> Unit, content: @Composable () -> Unit,
) { ) {
@ -35,23 +36,23 @@ fun CategorySearchBottomSheet(
CompositionLocalProvider( CompositionLocalProvider(
LocalBookSearchViewModel provides bookSearchViewModel, LocalBookSearchViewModel provides bookSearchViewModel,
LocalCategorySearchBottomSheetState provides state, LocalFilterSearchBottomSheetState provides filterState,
) { ) {
ModalBottomSheetLayout( ModalBottomSheetLayout(
modifier = Modifier.statusBarsPadding(), modifier = Modifier.statusBarsPadding(),
scrimColor = ShadowPalette.scrim, scrimColor = ShadowPalette.scrim,
sheetState = state.bottomSheetState, sheetState = filterState.bottomSheetState,
sheetContent = { sheetContent = {
FilterSearchPage( FilterSearchPage(
focusRequester = state.focusRequester, focusRequester = filterState.focusRequester,
) )
}, },
content = content, content = content,
) )
BackHandler(state.bottomSheetState.isVisible) { BackHandler(filterState.bottomSheetState.isVisible) {
scope.launch { scope.launch {
state.collapse() filterState.collapse()
} }
} }
} }
@ -65,12 +66,12 @@ fun rememberSearchBottomSheetState(
initialValue = Hidden, initialValue = Hidden,
skipHalfExpanded = true, skipHalfExpanded = true,
), ),
): SearchBottomSheetState { ): FilterSearchBottomSheetState {
val filter = rememberSaveable(bottomSheetState) { val filter = rememberSaveable(bottomSheetState) {
mutableStateOf<SearchFilter?>(null) mutableStateOf<SearchType?>(null)
} }
return remember(bottomSheetState) { return remember(bottomSheetState) {
SearchBottomSheetState( FilterSearchBottomSheetState(
bottomSheetState = bottomSheetState, bottomSheetState = bottomSheetState,
focusRequester = focusRequester, focusRequester = focusRequester,
filter = filter, filter = filter,
@ -80,15 +81,15 @@ fun rememberSearchBottomSheetState(
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Stable @Stable
class SearchBottomSheetState constructor( class FilterSearchBottomSheetState constructor(
val bottomSheetState: ModalBottomSheetState, val bottomSheetState: ModalBottomSheetState,
val focusRequester: FocusRequester, val focusRequester: FocusRequester,
filter: MutableState<SearchFilter?>, filter: MutableState<SearchType?>,
) { ) {
var filter: SearchFilter? by filter var filter: SearchType? by filter
private set private set
suspend fun expandSearch(filter: SearchFilter?) { suspend fun expandSearch(filter: SearchType?) {
this.filter = filter this.filter = filter
bottomSheetState.show() bottomSheetState.show()
focusRequester.requestFocus() focusRequester.requestFocus()
@ -97,13 +98,4 @@ class SearchBottomSheetState constructor(
suspend fun collapse() { suspend fun collapse() {
bottomSheetState.hide() bottomSheetState.hide()
} }
}
@Stable
@Immutable
enum class SearchFilter {
Author,
Series,
Genre,
Language;
} }

View file

@ -57,7 +57,7 @@ fun HomeScreen(
val focusRequester: FocusRequester = remember { FocusRequester() } val focusRequester: FocusRequester = remember { FocusRequester() }
DetailBottomSheet { DetailBottomSheet {
CategorySearchBottomSheet { SearchOptionBottomSheet {
SearchScaffold( SearchScaffold(
modifier = Modifier.statusBarsPadding(), modifier = Modifier.statusBarsPadding(),
state = searchScaffoldState, state = searchScaffoldState,
@ -138,7 +138,7 @@ fun HomeScreen(
}, },
) )
val bottomSearchState = LocalCategorySearchBottomSheetState.current val bottomSearchState = LocalFilterSearchBottomSheetState.current
val bottomDetailState = LocalDetailBottomSheetState.current val bottomDetailState = LocalDetailBottomSheetState.current
BackHandler( BackHandler(
enabled = searchScaffoldState.isExpended || bottomSearchState.bottomSheetState.isVisible || bottomDetailState.bottomSheetState.isVisible enabled = searchScaffoldState.isExpended || bottomSearchState.bottomSheetState.isVisible || bottomDetailState.bottomSheetState.isVisible

View file

@ -2,19 +2,17 @@ package com.pixelized.biblib.ui.screen.home.page.search
import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.animation.core.Spring import androidx.compose.foundation.layout.Arrangement
import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.animation.core.spring import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.* import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.* import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -23,33 +21,31 @@ import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items import androidx.paging.compose.items
import com.pixelized.biblib.R import com.pixelized.biblib.R
import com.pixelized.biblib.ui.scaffold.LocalCategorySearchBottomSheetState import com.pixelized.biblib.model.search.SearchType
import com.pixelized.biblib.ui.scaffold.LocalDetailBottomSheetState
import com.pixelized.biblib.ui.scaffold.LocalBookSearchViewModel import com.pixelized.biblib.ui.scaffold.LocalBookSearchViewModel
import com.pixelized.biblib.ui.scaffold.SearchFilter import com.pixelized.biblib.ui.scaffold.LocalDetailBottomSheetState
import com.pixelized.biblib.ui.scaffold.LocalFilterSearchBottomSheetState
import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnail import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnail
import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnailUio import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnailUio
import com.pixelized.biblib.ui.screen.home.page.search.item.SearchFilterList import com.pixelized.biblib.ui.screen.home.page.search.item.SearchFilterList
import com.pixelized.biblib.ui.screen.home.page.search.item.SearchFilterUio import com.pixelized.biblib.ui.screen.home.page.search.item.SearchFilterUio
import com.pixelized.biblib.ui.screen.home.page.search.item.searchFilterPreviewItems
import com.pixelized.biblib.ui.screen.home.page.search.viewModel.BookSearchViewModel import com.pixelized.biblib.ui.screen.home.page.search.viewModel.BookSearchViewModel
import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.default import com.pixelized.biblib.utils.extention.default
import com.pixelized.biblib.utils.extention.isLoading
import com.pixelized.biblib.utils.extention.navigationBarsHeight import com.pixelized.biblib.utils.extention.navigationBarsHeight
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class) @OptIn(ExperimentalComposeUiApi::class, ExperimentalMaterialApi::class)
@Composable @Composable
fun SearchPage( fun SearchPage(
bookSearchViewModel: BookSearchViewModel = LocalBookSearchViewModel.current, bookSearchViewModel: BookSearchViewModel = LocalBookSearchViewModel.current,
) { ) {
val keyboard = LocalSoftwareKeyboardController.current val keyboard = LocalSoftwareKeyboardController.current
val detail = LocalDetailBottomSheetState.current val detail = LocalDetailBottomSheetState.current
val search = LocalCategorySearchBottomSheetState.current val filter = LocalFilterSearchBottomSheetState.current
val focus = LocalFocusManager.current val focus = LocalFocusManager.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
@ -58,8 +54,17 @@ fun SearchPage(
search = bookSearchViewModel.paging, search = bookSearchViewModel.paging,
filters = filters(bookSearchViewModel), filters = filters(bookSearchViewModel),
onFilter = { onFilter = {
scope.launch { if (it.value == null) {
search.expandSearch(it.id) scope.launch {
filter.expandSearch(it.id)
}
} else {
when (it.id) {
SearchType.Author -> bookSearchViewModel.filterAuthor(null)
SearchType.Series -> bookSearchViewModel.filterSeries(null)
SearchType.Genre -> bookSearchViewModel.filterGenre(null)
SearchType.Language -> bookSearchViewModel.filterLanguage(null)
}
} }
}, },
onDetail = { onDetail = {
@ -75,10 +80,10 @@ fun SearchPage(
@Composable @Composable
private fun SearchPageContent( private fun SearchPageContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
search: Flow<PagingData<MicroBookThumbnailUio>>, search: Flow<PagingData<MicroBookThumbnailUio>> = emptyFlow(),
filters: List<SearchFilterUio> = emptyList(), filters: List<SearchFilterUio> = emptyList(),
onFilter: (filter: SearchFilterUio) -> Unit, onFilter: (filter: SearchFilterUio) -> Unit = default<SearchFilterUio>(),
onDetail: (item: MicroBookThumbnailUio) -> Unit, onDetail: (item: MicroBookThumbnailUio) -> Unit = default<MicroBookThumbnailUio>(),
) { ) {
val items = search.collectAsLazyPagingItems() val items = search.collectAsLazyPagingItems()
@ -105,29 +110,6 @@ private fun SearchPageContent(
) )
} }
} }
SearchLoader(
modifier = Modifier
.fillMaxWidth()
.height(MaterialTheme.bibLib.dimen.dp2),
isLoading = { items.isLoading },
)
}
@Composable
private fun SearchLoader(
modifier: Modifier = Modifier,
isLoading: () -> Boolean,
) {
val alpha by animateFloatAsState(
animationSpec = spring(stiffness = Spring.StiffnessLow),
targetValue = if (isLoading()) 1f else 0f
)
LinearProgressIndicator(
modifier = modifier.alpha(alpha),
color = MaterialTheme.colors.primary,
backgroundColor = Color.Transparent,
)
} }
@Composable @Composable
@ -135,22 +117,22 @@ private fun filters(
bookSearchViewModel: BookSearchViewModel, bookSearchViewModel: BookSearchViewModel,
) = listOf( ) = listOf(
SearchFilterUio( SearchFilterUio(
id = SearchFilter.Author, id = SearchType.Author,
label = stringResource(id = R.string.search_filter_author), label = stringResource(id = R.string.search_filter_author),
value = bookSearchViewModel.author?.filter, value = bookSearchViewModel.author?.filter,
), ),
SearchFilterUio( SearchFilterUio(
id = SearchFilter.Genre, id = SearchType.Genre,
label = stringResource(id = R.string.search_filter_genre), label = stringResource(id = R.string.search_filter_genre),
value = bookSearchViewModel.genre?.filter, value = bookSearchViewModel.genre?.filter,
), ),
SearchFilterUio( SearchFilterUio(
id = SearchFilter.Series, id = SearchType.Series,
label = stringResource(id = R.string.search_filter_serie), label = stringResource(id = R.string.search_filter_series),
value = bookSearchViewModel.series?.filter, value = bookSearchViewModel.series?.filter,
), ),
SearchFilterUio( SearchFilterUio(
id = SearchFilter.Language, id = SearchType.Language,
label = stringResource(id = R.string.search_filter_language), label = stringResource(id = R.string.search_filter_language),
value = bookSearchViewModel.language?.filter, value = bookSearchViewModel.language?.filter,
), ),
@ -163,10 +145,6 @@ private fun SearchPageContentPreview() {
BibLibTheme { BibLibTheme {
SearchPageContent( SearchPageContent(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
search = emptyFlow(),
filters = searchFilterPreviewItems(),
onFilter = default<SearchFilterUio>(),
onDetail = default<MicroBookThumbnailUio>(),
) )
} }
} }

View file

@ -1,4 +1,4 @@
package com.pixelized.biblib.ui.screen.home.page.search package com.pixelized.biblib.ui.screen.home.page.search.bottom
import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.content.res.Configuration.UI_MODE_NIGHT_YES
@ -21,9 +21,9 @@ import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items import androidx.paging.compose.items
import com.pixelized.biblib.R import com.pixelized.biblib.R
import com.pixelized.biblib.model.search.SearchType
import com.pixelized.biblib.ui.scaffold.LocalBookSearchViewModel import com.pixelized.biblib.ui.scaffold.LocalBookSearchViewModel
import com.pixelized.biblib.ui.scaffold.LocalCategorySearchBottomSheetState import com.pixelized.biblib.ui.scaffold.LocalFilterSearchBottomSheetState
import com.pixelized.biblib.ui.scaffold.SearchFilter
import com.pixelized.biblib.ui.screen.home.page.search.viewModel.* import com.pixelized.biblib.ui.screen.home.page.search.viewModel.*
import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.bibLib
@ -48,16 +48,16 @@ fun FilterSearchPage(
focusRequester: FocusRequester = FocusRequester(), focusRequester: FocusRequester = FocusRequester(),
) { ) {
val bookSearchViewModel = LocalBookSearchViewModel.current val bookSearchViewModel = LocalBookSearchViewModel.current
val bottomSearchState = LocalCategorySearchBottomSheetState.current val bottomSearchState = LocalFilterSearchBottomSheetState.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val filter = bottomSearchState.filter val filter = bottomSearchState.filter
val viewModel: IFilterViewModel? = remember(filter) { val viewModel: IFilterViewModel? = remember(filter) {
when (filter) { when (filter) {
SearchFilter.Author -> authorFilterViewModel SearchType.Author -> authorFilterViewModel
SearchFilter.Series -> seriesFilterViewModel SearchType.Series -> seriesFilterViewModel
SearchFilter.Genre -> genreViewModel SearchType.Genre -> genreViewModel
SearchFilter.Language -> languageViewModel SearchType.Language -> languageViewModel
else -> null else -> null
} }
} }
@ -72,10 +72,10 @@ fun FilterSearchPage(
}, },
onClose = { onClose = {
when (filter) { when (filter) {
SearchFilter.Author -> bookSearchViewModel.filterAuthor(null) SearchType.Author -> bookSearchViewModel.filterAuthor(null)
SearchFilter.Series -> bookSearchViewModel.filterSeries(null) SearchType.Series -> bookSearchViewModel.filterSeries(null)
SearchFilter.Genre -> bookSearchViewModel.filterGenre(null) SearchType.Genre -> bookSearchViewModel.filterGenre(null)
SearchFilter.Language -> bookSearchViewModel.filterLanguage(null) SearchType.Language -> bookSearchViewModel.filterLanguage(null)
else -> Unit else -> Unit
} }
scope.launch { scope.launch {
@ -84,10 +84,10 @@ fun FilterSearchPage(
}, },
onFilter = { onFilter = {
when (filter) { when (filter) {
SearchFilter.Author -> bookSearchViewModel.filterAuthor(it) SearchType.Author -> bookSearchViewModel.filterAuthor(it)
SearchFilter.Series -> bookSearchViewModel.filterSeries(it) SearchType.Series -> bookSearchViewModel.filterSeries(it)
SearchFilter.Genre -> bookSearchViewModel.filterGenre(it) SearchType.Genre -> bookSearchViewModel.filterGenre(it)
SearchFilter.Language -> bookSearchViewModel.filterLanguage(it) SearchType.Language -> bookSearchViewModel.filterLanguage(it)
else -> Unit else -> Unit
} }
scope.launch { scope.launch {
@ -100,7 +100,7 @@ fun FilterSearchPage(
@Composable @Composable
fun CategorySearchPageContent( fun CategorySearchPageContent(
focusRequester: FocusRequester = FocusRequester(), focusRequester: FocusRequester = FocusRequester(),
filter: SearchFilter?, filter: SearchType?,
search: () -> String?, search: () -> String?,
paging: () -> Flow<PagingData<FilterUio>> = { emptyFlow() }, paging: () -> Flow<PagingData<FilterUio>> = { emptyFlow() },
onSearchUpdate: (String) -> Unit = default<String>(), onSearchUpdate: (String) -> Unit = default<String>(),
@ -121,10 +121,10 @@ fun CategorySearchPageContent(
style = MaterialTheme.typography.h6, style = MaterialTheme.typography.h6,
color = MaterialTheme.colors.onSurface, color = MaterialTheme.colors.onSurface,
text = when (filter) { text = when (filter) {
SearchFilter.Author -> stringResource(id = R.string.search_filter_author) SearchType.Author -> stringResource(id = R.string.search_filter_author)
SearchFilter.Genre -> stringResource(id = R.string.search_filter_genre) SearchType.Genre -> stringResource(id = R.string.search_filter_genre)
SearchFilter.Series -> stringResource(id = R.string.search_filter_serie) SearchType.Series -> stringResource(id = R.string.search_filter_series)
SearchFilter.Language -> stringResource(id = R.string.search_filter_language) SearchType.Language -> stringResource(id = R.string.search_filter_language)
else -> "" else -> ""
}, },
) )
@ -181,7 +181,7 @@ fun CategorySearchPageContent(
private fun CategorySearchPageContentPreview() { private fun CategorySearchPageContentPreview() {
BibLibTheme { BibLibTheme {
CategorySearchPageContent( CategorySearchPageContent(
filter = SearchFilter.Author, filter = SearchType.Author,
search = { "Asimov" }, search = { "Asimov" },
) )
} }

View file

@ -1,22 +1,23 @@
package com.pixelized.biblib.ui.screen.home.page.search.item package com.pixelized.biblib.ui.screen.home.page.search.item
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.animation.*
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.* import androidx.compose.material.*
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.* import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.pixelized.biblib.R import com.pixelized.biblib.R
import com.pixelized.biblib.ui.scaffold.SearchFilter import com.pixelized.biblib.model.search.SearchType
import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.default import com.pixelized.biblib.utils.extention.default
@ -24,7 +25,7 @@ import com.pixelized.biblib.utils.extention.default
@Stable @Stable
@Immutable @Immutable
data class SearchFilterUio( data class SearchFilterUio(
val id: SearchFilter, val id: SearchType,
val label: String, val label: String,
val value: String? = null, val value: String? = null,
) )
@ -51,7 +52,7 @@ fun SearchFilterList(
} }
} }
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class)
@Composable @Composable
private fun SearchFilter( private fun SearchFilter(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -59,34 +60,34 @@ private fun SearchFilter(
onClick: () -> Unit = default(), onClick: () -> Unit = default(),
) { ) {
FilterChip( FilterChip(
modifier = modifier, modifier = modifier.animateContentSize(),
selected = uio.value != null, selected = uio.value != null,
onClick = onClick, onClick = onClick,
) { trailingIcon = {
Text( Icon(
color = MaterialTheme.bibLib.colors.typography.medium, modifier = Modifier.size(MaterialTheme.bibLib.dimen.dp12),
style = MaterialTheme.typography.caption, imageVector = when (uio.value) {
text = uio.label, null -> Icons.Default.KeyboardArrowDown
) else -> Icons.Default.Close
},
uio.value?.let { tint = MaterialTheme.bibLib.colors.typography.medium,
Text( contentDescription = null
color = MaterialTheme.bibLib.colors.typography.medium,
style = MaterialTheme.typography.caption,
text = ": "
) )
},
) {
AnimatedContent(
targetState = uio.value,
transitionSpec = { fadeIn() with fadeOut() },
) {
Text( Text(
color = MaterialTheme.bibLib.colors.typography.medium, color = MaterialTheme.bibLib.colors.typography.medium,
style = MaterialTheme.typography.caption, style = MaterialTheme.typography.caption,
text = it text = when {
it != null -> stringResource(id = R.string.search_filter_param, uio.label, it)
else -> uio.label
}
) )
} }
Icon(
imageVector = Icons.Default.ArrowDropDown,
tint = MaterialTheme.bibLib.colors.typography.medium,
contentDescription = null
)
} }
} }
@ -104,20 +105,20 @@ private fun SearchFilterPreview() {
@Composable @Composable
fun searchFilterPreviewItems() = listOf( fun searchFilterPreviewItems() = listOf(
SearchFilterUio( SearchFilterUio(
id = SearchFilter.Author, id = SearchType.Author,
label = stringResource(id = R.string.search_filter_author), label = stringResource(id = R.string.search_filter_author),
value = "Asimov", value = "Asimov",
), ),
SearchFilterUio( SearchFilterUio(
id = SearchFilter.Genre, id = SearchType.Genre,
label = stringResource(id = R.string.search_filter_genre), label = stringResource(id = R.string.search_filter_genre),
), ),
SearchFilterUio( SearchFilterUio(
id = SearchFilter.Series, id = SearchType.Series,
label = stringResource(id = R.string.search_filter_serie), label = stringResource(id = R.string.search_filter_series),
), ),
SearchFilterUio( SearchFilterUio(
id = SearchFilter.Language, id = SearchType.Language,
label = stringResource(id = R.string.search_filter_language), label = stringResource(id = R.string.search_filter_language),
), ),
) )

View file

@ -1,67 +0,0 @@
package com.pixelized.biblib.ui.screen.home.page.search.item
import android.content.res.Configuration
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.History
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
@Stable
@Immutable
data class SearchHistoryUio(
val label: String,
)
@Composable
fun SearchHistory(
uio: SearchHistoryUio,
) {
SearchHistoryContent(
modifier = Modifier.fillMaxWidth(),
label = uio.label
)
}
@Composable
private fun SearchHistoryContent(
modifier: Modifier = Modifier,
label: String,
) {
Row(
modifier = modifier.padding(all = MaterialTheme.bibLib.dimen.dp16)
) {
Icon(
modifier = Modifier.padding(end = MaterialTheme.bibLib.dimen.dp8),
tint = MaterialTheme.colors.onSurface,
imageVector = Icons.Default.History,
contentDescription = null,
)
Text(
color = MaterialTheme.bibLib.colors.typography.medium,
style = MaterialTheme.typography.body1,
text = label,
)
}
}
@Composable
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun SearchHistoryContentPreview() {
BibLibTheme {
SearchHistory(
uio = SearchHistoryUio("Asimov")
)
}
}

View file

@ -8,7 +8,8 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.* import androidx.paging.*
import com.pixelized.biblib.model.book.Author import com.pixelized.biblib.model.book.Author
import com.pixelized.biblib.repository.search.ISearchRepository import com.pixelized.biblib.repository.search.ISearchRepository
import com.pixelized.biblib.ui.screen.home.page.search.FilterUio import com.pixelized.biblib.ui.screen.home.page.search.bottom.FilterUio
import com.pixelized.biblib.ui.screen.home.page.search.source.AuthorSearchSource import com.pixelized.biblib.ui.screen.home.page.search.source.AuthorSearchSource
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -9,7 +9,7 @@ import androidx.paging.*
import com.pixelized.biblib.model.book.Book import com.pixelized.biblib.model.book.Book
import com.pixelized.biblib.repository.search.ISearchRepository import com.pixelized.biblib.repository.search.ISearchRepository
import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnailUio import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnailUio
import com.pixelized.biblib.ui.screen.home.page.search.FilterUio import com.pixelized.biblib.ui.screen.home.page.search.bottom.FilterUio
import com.pixelized.biblib.ui.screen.home.page.search.source.BookSearchSource import com.pixelized.biblib.ui.screen.home.page.search.source.BookSearchSource
import com.pixelized.biblib.utils.extention.toMicroThumbnailUio import com.pixelized.biblib.utils.extention.toMicroThumbnailUio
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -65,10 +65,12 @@ class BookSearchViewModel @Inject constructor(
series = criteria series = criteria
searchSource?.invalidate() searchSource?.invalidate()
} }
fun filterGenre(criteria: FilterUio?) { fun filterGenre(criteria: FilterUio?) {
genre = criteria genre = criteria
searchSource?.invalidate() searchSource?.invalidate()
} }
fun filterLanguage(criteria: FilterUio?) { fun filterLanguage(criteria: FilterUio?) {
language = criteria language = criteria
searchSource?.invalidate() searchSource?.invalidate()

View file

@ -8,7 +8,7 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.* import androidx.paging.*
import com.pixelized.biblib.model.book.Genre import com.pixelized.biblib.model.book.Genre
import com.pixelized.biblib.repository.search.ISearchRepository import com.pixelized.biblib.repository.search.ISearchRepository
import com.pixelized.biblib.ui.screen.home.page.search.FilterUio import com.pixelized.biblib.ui.screen.home.page.search.bottom.FilterUio
import com.pixelized.biblib.ui.screen.home.page.search.source.GenreSearchSource import com.pixelized.biblib.ui.screen.home.page.search.source.GenreSearchSource
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -1,7 +1,7 @@
package com.pixelized.biblib.ui.screen.home.page.search.viewModel package com.pixelized.biblib.ui.screen.home.page.search.viewModel
import androidx.paging.PagingData import androidx.paging.PagingData
import com.pixelized.biblib.ui.screen.home.page.search.FilterUio import com.pixelized.biblib.ui.screen.home.page.search.bottom.FilterUio
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface IFilterViewModel { interface IFilterViewModel {

View file

@ -8,7 +8,7 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.* import androidx.paging.*
import com.pixelized.biblib.model.book.Language import com.pixelized.biblib.model.book.Language
import com.pixelized.biblib.repository.search.ISearchRepository import com.pixelized.biblib.repository.search.ISearchRepository
import com.pixelized.biblib.ui.screen.home.page.search.FilterUio import com.pixelized.biblib.ui.screen.home.page.search.bottom.FilterUio
import com.pixelized.biblib.ui.screen.home.page.search.source.LanguageSearchSource import com.pixelized.biblib.ui.screen.home.page.search.source.LanguageSearchSource
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -8,7 +8,7 @@ import androidx.lifecycle.viewModelScope
import androidx.paging.* import androidx.paging.*
import com.pixelized.biblib.model.book.Series import com.pixelized.biblib.model.book.Series
import com.pixelized.biblib.repository.search.ISearchRepository import com.pixelized.biblib.repository.search.ISearchRepository
import com.pixelized.biblib.ui.screen.home.page.search.FilterUio import com.pixelized.biblib.ui.screen.home.page.search.bottom.FilterUio
import com.pixelized.biblib.ui.screen.home.page.search.source.SeriesSearchSource import com.pixelized.biblib.ui.screen.home.page.search.source.SeriesSearchSource
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -13,6 +13,7 @@ data class BibLibDimen(
val dp2: Dp = 2.dp, val dp2: Dp = 2.dp,
val dp4: Dp = 4.dp, val dp4: Dp = 4.dp,
val dp8: Dp = 8.dp, val dp8: Dp = 8.dp,
val dp12: Dp = 12.dp,
val dp16: Dp = 16.dp, val dp16: Dp = 16.dp,
val dp32: Dp = 32.dp, val dp32: Dp = 32.dp,
val dp48: Dp = 48.dp, val dp48: Dp = 48.dp,
@ -33,10 +34,11 @@ data class BibLibDimen(
@Stable @Stable
@Immutable @Immutable
data class BookThumbnail( data class BookThumbnail(
val ratio: Float = 1.6f,
val padding: Dp = 16.dp, val padding: Dp = 16.dp,
val arrangement: Dp = 16.dp, val arrangement: Dp = 16.dp,
val cover: DpSize = DpSize(width = 72.dp, height = 115.dp), // ratio 1.6 val cover: DpSize = 72.dp.let { DpSize(width = it, height = it * ratio) },
val micro: DpSize = DpSize(width = 40.dp, height = 64.dp), // ratio 1.6 val micro: DpSize = 48.dp.let { DpSize(width = it, height = it * ratio) },
) )
@Stable @Stable

View file

@ -2,6 +2,7 @@ package com.pixelized.biblib.utils.extention
import androidx.annotation.StringDef import androidx.annotation.StringDef
import com.pixelized.biblib.model.book.Book import com.pixelized.biblib.model.book.Book
import com.pixelized.biblib.model.book.Series
import com.pixelized.biblib.network.client.IBibLibClient import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.ui.screen.home.common.item.LargeBookThumbnailUio import com.pixelized.biblib.ui.screen.home.common.item.LargeBookThumbnailUio
import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnailUio import com.pixelized.biblib.ui.screen.home.common.item.MicroBookThumbnailUio
@ -20,10 +21,10 @@ fun Book.toMicroThumbnailUio(
@CoverUrl coverBaseUrl: String = IBibLibClient.THUMBNAIL_URL @CoverUrl coverBaseUrl: String = IBibLibClient.THUMBNAIL_URL
) = MicroBookThumbnailUio( ) = MicroBookThumbnailUio(
id = id, id = id,
cover = "${coverBaseUrl}/$id.jpg",
title = title, title = title,
author = author.joinToString { it.name }, author = author.joinToString { it.name },
isNew = isNew, isNew = isNew,
cover = "${coverBaseUrl}/$id.jpg",
) )
fun Book.toSmallThumbnailUio( fun Book.toSmallThumbnailUio(
@ -58,7 +59,15 @@ fun Book.toDetailUio(
rating = rating?.toFloat() ?: 0.0f, rating = rating?.toFloat() ?: 0.0f,
language = language?.displayLanguage?.capitalize() ?: "", language = language?.displayLanguage?.capitalize() ?: "",
date = releaseDate.shortDate(), date = releaseDate.shortDate(),
series = series?.name, series = series.toLabel(),
description = synopsis ?: "", description = synopsis ?: "",
cover = "${coverBaseUrl}/$id.jpg", cover = "${coverBaseUrl}/$id.jpg",
) )
fun Series?.toLabel(): String? {
return when {
this != null && index != null -> "$name - $index"
this != null -> name
else -> null
}
}

View file

@ -44,11 +44,14 @@
<string name="search_title">Rechercher sur Biblib</string> <string name="search_title">Rechercher sur Biblib</string>
<string name="search_filter_title">Rechercher</string> <string name="search_filter_title">Rechercher</string>
<string name="search_filter_param">%1$s : %2$s</string>
<string name="search_filter_author">Auteur</string> <string name="search_filter_author">Auteur</string>
<string name="search_filter_serie">Série</string> <string name="search_filter_series">Série</string>
<string name="search_filter_genre">Genre</string> <string name="search_filter_genre">Genre</string>
<string name="search_filter_language">Langue</string> <string name="search_filter_language">Langue</string>
<string name="search_sort_by">Trié par : %1$s</string>
<string name="profile_title">Profile</string> <string name="profile_title">Profile</string>
</resources> </resources>

View file

@ -50,10 +50,13 @@
<string name="search_title">Search on Biblib</string> <string name="search_title">Search on Biblib</string>
<string name="search_filter_title">Search</string> <string name="search_filter_title">Search</string>
<string name="search_filter_author">Author</string> <string name="search_filter_param">%1$s: %2$s</string>
<string name="search_filter_serie">Serie</string> <string name="search_filter_author">Authors</string>
<string name="search_filter_genre">Genre</string> <string name="search_filter_series">Series</string>
<string name="search_filter_language">Language</string> <string name="search_filter_genre">Genres</string>
<string name="search_filter_language">Languages</string>
<string name="search_sort_by">Sort by: %1$s</string>
<string name="profile_title">Profil</string> <string name="profile_title">Profil</string>