Catergorie Search basic impl

This commit is contained in:
Thomas Andres Gomez 2022-07-05 18:43:25 +02:00
parent 42b4e414a0
commit c7603ca71e
12 changed files with 230 additions and 26 deletions

View file

@ -1,5 +1,6 @@
package com.pixelized.biblib.database.dao package com.pixelized.biblib.database.dao
import androidx.paging.DataSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
@ -8,6 +9,9 @@ import com.pixelized.biblib.database.data.AuthorDbo
@Dao @Dao
interface AuthorDao { interface AuthorDao {
@Query("SELECT * FROM ${AuthorDbo.TABLE}")
fun getAll(): DataSource.Factory<Int, AuthorDbo>
@Query("SELECT * FROM ${AuthorDbo.TABLE} WHERE ${AuthorDbo.ID} LIKE :id") @Query("SELECT * FROM ${AuthorDbo.TABLE} WHERE ${AuthorDbo.ID} LIKE :id")
fun get(id: String?): AuthorDbo? fun get(id: String?): AuthorDbo?

View file

@ -1,13 +1,18 @@
package com.pixelized.biblib.database.dao package com.pixelized.biblib.database.dao
import androidx.paging.DataSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.pixelized.biblib.database.data.AuthorDbo
import com.pixelized.biblib.database.data.GenreDbo import com.pixelized.biblib.database.data.GenreDbo
@Dao @Dao
interface GenreDao { interface GenreDao {
@Query("SELECT * FROM ${GenreDbo.TABLE}")
fun getAll(): DataSource.Factory<Int, GenreDbo>
@Query("SELECT * FROM ${GenreDbo.TABLE} WHERE ${GenreDbo.ID} LIKE :id") @Query("SELECT * FROM ${GenreDbo.TABLE} WHERE ${GenreDbo.ID} LIKE :id")
fun get(id: String?): GenreDbo? fun get(id: String?): GenreDbo?

View file

@ -1,13 +1,18 @@
package com.pixelized.biblib.database.dao package com.pixelized.biblib.database.dao
import androidx.paging.DataSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.pixelized.biblib.database.data.GenreDbo
import com.pixelized.biblib.database.data.LanguageDbo import com.pixelized.biblib.database.data.LanguageDbo
@Dao @Dao
interface LanguageDao { interface LanguageDao {
@Query("SELECT * FROM ${LanguageDbo.TABLE}")
fun getAll(): DataSource.Factory<Int, LanguageDbo>
@Query("SELECT * FROM ${LanguageDbo.TABLE} WHERE ${LanguageDbo.ID} LIKE :id") @Query("SELECT * FROM ${LanguageDbo.TABLE} WHERE ${LanguageDbo.ID} LIKE :id")
fun get(id: String?): LanguageDbo? fun get(id: String?): LanguageDbo?

View file

@ -1,13 +1,18 @@
package com.pixelized.biblib.database.dao package com.pixelized.biblib.database.dao
import androidx.paging.DataSource
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Insert import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import com.pixelized.biblib.database.data.LanguageDbo
import com.pixelized.biblib.database.data.SeriesDbo import com.pixelized.biblib.database.data.SeriesDbo
@Dao @Dao
interface SeriesDao { interface SeriesDao {
@Query("SELECT * FROM ${SeriesDbo.TABLE}")
fun getAll(): DataSource.Factory<Int, SeriesDbo>
@Query("SELECT * FROM ${SeriesDbo.TABLE} WHERE ${SeriesDbo.ID} LIKE :id") @Query("SELECT * FROM ${SeriesDbo.TABLE} WHERE ${SeriesDbo.ID} LIKE :id")
fun get(id: String?): SeriesDbo? fun get(id: String?): SeriesDbo?

View file

@ -23,6 +23,18 @@ class BookRepository @Inject constructor(
override fun getBooksSource(): DataSource.Factory<Int, Book> = override fun getBooksSource(): DataSource.Factory<Int, Book> =
database.bookDao().getBook().map { it.toBook() } database.bookDao().getBook().map { it.toBook() }
override fun getAuthorsSource(): DataSource.Factory<Int, Author> =
database.authorDao().getAll().map { it.toAuthor() }
override fun getSeriesSource(): DataSource.Factory<Int, Series> =
database.seriesDao().getAll().map { it.toSeries() }
override fun getGenresSource(): DataSource.Factory<Int, Genre> =
database.genreDao().getAll().map { it.toGenre() }
override fun getLanguagesSource(): DataSource.Factory<Int, Language> =
database.languageDao().getAll().map { it.toLanguage() }
override suspend fun update(data: List<Book>) { override suspend fun update(data: List<Book>) {
val authors = mutableSetOf<AuthorDbo>() val authors = mutableSetOf<AuthorDbo>()
val genres = mutableSetOf<GenreDbo>() val genres = mutableSetOf<GenreDbo>()

View file

@ -1,7 +1,7 @@
package com.pixelized.biblib.repository.book package com.pixelized.biblib.repository.book
import androidx.paging.DataSource import androidx.paging.DataSource
import com.pixelized.biblib.model.book.Book import com.pixelized.biblib.model.book.*
interface IBookRepository { interface IBookRepository {
@ -11,5 +11,13 @@ interface IBookRepository {
fun getBooksSource(): DataSource.Factory<Int, Book> fun getBooksSource(): DataSource.Factory<Int, Book>
fun getAuthorsSource() : DataSource.Factory<Int, Author>
fun getSeriesSource() : DataSource.Factory<Int, Series>
fun getGenresSource() : DataSource.Factory<Int, Genre>
fun getLanguagesSource() : DataSource.Factory<Int, Language>
suspend fun update(data: List<Book>) suspend fun update(data: List<Book>)
} }

View file

@ -49,6 +49,13 @@ fun BottomSearchScaffold(
focusRequester = state.focusRequester, focusRequester = state.focusRequester,
filter = state.filter, filter = state.filter,
onClose = { onClose = {
when(state.filter) {
is SearchFilter.Author -> searchViewModel.authors.confirm("")
is SearchFilter.Series -> searchViewModel.series.confirm("")
is SearchFilter.Genre -> searchViewModel.genre.confirm("")
is SearchFilter.Language -> searchViewModel.language.confirm("")
null -> Unit
}
state.collapse() state.collapse()
} }
) )
@ -125,6 +132,13 @@ sealed class SearchFilter(
value = value, value = value,
) )
class Series(
value: String? = null,
): SearchFilter(
label = R.string.search_filter_serie,
value = value,
)
class Genre( class Genre(
value: String? = null, value: String? = null,
) : SearchFilter( ) : SearchFilter(

View file

@ -2,14 +2,14 @@ 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.foundation.layout.Column import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.layout.imePadding
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.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
@ -18,10 +18,17 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems
import androidx.paging.compose.items
import com.pixelized.biblib.ui.scaffold.LocalBottomSearchState import com.pixelized.biblib.ui.scaffold.LocalBottomSearchState
import com.pixelized.biblib.ui.scaffold.SearchFilter import com.pixelized.biblib.ui.scaffold.SearchFilter
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.default import com.pixelized.biblib.utils.extention.default
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flow
@Composable @Composable
fun CategorySearchPage( fun CategorySearchPage(
@ -31,26 +38,48 @@ fun CategorySearchPage(
onClose: () -> Unit = default(), onClose: () -> Unit = default(),
) { ) {
val bottomSearchState = LocalBottomSearchState.current val bottomSearchState = LocalBottomSearchState.current
CategorySearchPageContent( CategorySearchPageContent(
focusRequester = focusRequester, focusRequester = focusRequester,
filter = filter, filter = filter,
onClose = onClose, onClose = onClose,
searchValue = { searchFlow = {
when (bottomSearchState.filter) { when (bottomSearchState.filter) {
is SearchFilter.Author -> searchViewModel.author is SearchFilter.Author -> searchViewModel.authors.filterFlow
is SearchFilter.Genre -> searchViewModel.genre is SearchFilter.Series -> searchViewModel.series.filterFlow
is SearchFilter.Language -> searchViewModel.language is SearchFilter.Genre -> searchViewModel.genre.filterFlow
null -> "" is SearchFilter.Language -> searchViewModel.language.filterFlow
null -> emptyFlow()
}
},
dataFlow = {
when (bottomSearchState.filter) {
is SearchFilter.Author -> searchViewModel.authors.dataFlow
is SearchFilter.Series -> searchViewModel.series.dataFlow
is SearchFilter.Genre -> searchViewModel.genre.dataFlow
is SearchFilter.Language -> searchViewModel.language.dataFlow
null -> emptyFlow()
} }
}, },
onSearchChange = { onSearchChange = {
when (bottomSearchState.filter) { when (bottomSearchState.filter) {
is SearchFilter.Author -> searchViewModel.filterAuthor(it) is SearchFilter.Author -> searchViewModel.authors.filter(it)
is SearchFilter.Genre -> searchViewModel.filterGenre(it) is SearchFilter.Series -> searchViewModel.series.filter(it)
is SearchFilter.Language -> searchViewModel.filterLanguage(it) is SearchFilter.Genre -> searchViewModel.genre.filter(it)
is SearchFilter.Language -> searchViewModel.language.filter(it)
null -> Unit null -> Unit
} }
}, },
onData = {
when (bottomSearchState.filter) {
is SearchFilter.Author -> searchViewModel.authors.confirm(it)
is SearchFilter.Series -> searchViewModel.series.confirm(it)
is SearchFilter.Genre -> searchViewModel.genre.confirm(it)
is SearchFilter.Language -> searchViewModel.language.confirm(it)
null -> Unit
}
bottomSearchState.collapse()
}
) )
} }
@ -58,10 +87,13 @@ fun CategorySearchPage(
fun CategorySearchPageContent( fun CategorySearchPageContent(
focusRequester: FocusRequester = FocusRequester(), focusRequester: FocusRequester = FocusRequester(),
filter: SearchFilter?, filter: SearchFilter?,
searchValue: () -> String, searchFlow: () -> Flow<String>,
dataFlow: () -> Flow<PagingData<String>> = { emptyFlow() },
onSearchChange: (String) -> Unit = default<String>(), onSearchChange: (String) -> Unit = default<String>(),
onData: (String) -> Unit = default<String>(),
onClose: () -> Unit = default(), onClose: () -> Unit = default(),
) { ) {
val data = dataFlow().collectAsLazyPagingItems()
Column( Column(
modifier = Modifier modifier = Modifier
.imePadding() .imePadding()
@ -98,7 +130,7 @@ fun CategorySearchPageContent(
text = "Rechercher" text = "Rechercher"
) )
}, },
value = searchValue(), value = searchFlow().collectAsState(initial = "").value,
singleLine = true, singleLine = true,
colors = TextFieldDefaults.outlinedTextFieldColors( colors = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Transparent, focusedBorderColor = Color.Transparent,
@ -107,6 +139,21 @@ fun CategorySearchPageContent(
), ),
onValueChange = onSearchChange onValueChange = onSearchChange
) )
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
items(items = data, key = { it }) {
Text(
modifier = Modifier
.clickable { onData(it ?: "") }
.fillMaxWidth()
.padding(all = MaterialTheme.bibLib.dimen.dp16),
style = MaterialTheme.typography.body1,
color = MaterialTheme.colors.onSurface,
text = it ?: ""
)
}
}
} }
} }
@ -117,7 +164,7 @@ private fun CategorySearchPageContentPreview() {
BibLibTheme { BibLibTheme {
CategorySearchPageContent( CategorySearchPageContent(
filter = SearchFilter.Author(), filter = SearchFilter.Author(),
searchValue = { "Asimov" }, searchFlow = { flow { "Asimov" } },
) )
} }
} }

View file

@ -26,21 +26,12 @@ 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
@Composable @Composable
fun SearchPage( fun SearchPage(
searchViewModel: SearchViewModel = LocalSearchViewModel.current searchViewModel: SearchViewModel = LocalSearchViewModel.current
) { ) {
val bottomSearchState = LocalBottomSearchState.current val bottomSearchState = LocalBottomSearchState.current
val filters by remember { val filters = rememberSearchFilter()
derivedStateOf {
listOf(
SearchFilter.Author(value = searchViewModel.author),
SearchFilter.Genre(value = searchViewModel.genre),
SearchFilter.Language(value = searchViewModel.language),
)
}
}
SearchPageContent( SearchPageContent(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@ -129,6 +120,29 @@ private fun SearchChipFilter(
} }
} }
@Composable
private fun rememberSearchFilter(
searchViewModel: SearchViewModel = LocalSearchViewModel.current
): List<SearchFilter> {
val authors by searchViewModel.authors.search
val genre by searchViewModel.genre.search
val language by searchViewModel.language.search
val series by searchViewModel.series.search
val filters by remember {
derivedStateOf {
listOf(
SearchFilter.Author(value = authors),
SearchFilter.Genre(value = genre),
SearchFilter.Language(value = language),
SearchFilter.Series(value = series)
)
}
}
return filters
}
@Composable @Composable
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO)
@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES)

View file

@ -0,0 +1,88 @@
package com.pixelized.biblib.ui.screen.home.page.search
import androidx.compose.runtime.*
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.*
import com.pixelized.biblib.repository.book.IBookRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class SearchViewModel @Inject constructor(
bookRepository: IBookRepository,
) : ViewModel() {
var search: String by mutableStateOf("")
private set
val authors = FilterManager(
scope = viewModelScope,
source = bookRepository.getAuthorsSource()
.map { it.name }
.asPagingSourceFactory(Dispatchers.IO)
)
val series = FilterManager(
scope = viewModelScope,
source = bookRepository.getSeriesSource()
.map { it.name }
.asPagingSourceFactory(Dispatchers.IO)
)
val genre = FilterManager(
scope = viewModelScope,
source = bookRepository.getGenresSource()
.map { it.name }
.asPagingSourceFactory(Dispatchers.IO)
)
val language = FilterManager(
scope = viewModelScope,
source = bookRepository.getLanguagesSource()
.map { it.displayLanguage }
.asPagingSourceFactory(Dispatchers.IO)
)
fun search(criteria: String) {
}
class FilterManager(
private val scope: CoroutineScope,
source: () -> PagingSource<Int, String>,
) {
private val _searchFlow = MutableStateFlow("")
val searchFlow: Flow<String> get() = _searchFlow
private val _filterFlow = MutableStateFlow("")
val filterFlow: Flow<String> get() = _filterFlow
val dataFlow: Flow<PagingData<String>> = Pager(
config = PagingConfig(pageSize = 30),
pagingSourceFactory = source,
).flow.cachedIn(scope).combine(this._filterFlow) { paging, filter ->
paging.filter { it.contains(filter, ignoreCase = true) }
}
val filter: State<String>
@Composable get() = _filterFlow.collectAsState(initial = "")
val search: State<String>
@Composable get() = _searchFlow.collectAsState(initial = "")
fun filter(criteria: String) {
scope.launch { _filterFlow.emit(criteria) }
}
fun confirm(criteria: String) {
scope.launch { _searchFlow.emit(criteria) }
}
}
}

View file

@ -42,6 +42,7 @@
<string name="detail_emails_title">Envoyer cet eBook à :</string> <string name="detail_emails_title">Envoyer cet eBook à :</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_genre">Genre</string> <string name="search_filter_genre">Genre</string>
<string name="search_filter_language">Langue</string> <string name="search_filter_language">Langue</string>

View file

@ -48,6 +48,7 @@
<string name="detail_emails_title">Send this eBook to:</string> <string name="detail_emails_title">Send this eBook to:</string>
<string name="search_filter_author">Author</string> <string name="search_filter_author">Author</string>
<string name="search_filter_serie">Serie</string>
<string name="search_filter_genre">Genre</string> <string name="search_filter_genre">Genre</string>
<string name="search_filter_language">Language</string> <string name="search_filter_language">Language</string>