diff --git a/app/schemas/com.pixelized.biblib.database.BibLibDatabase/1.json b/app/schemas/com.pixelized.biblib.database.BibLibDatabase/1.json index 8326e57..11efb60 100644 --- a/app/schemas/com.pixelized.biblib.database.BibLibDatabase/1.json +++ b/app/schemas/com.pixelized.biblib.database.BibLibDatabase/1.json @@ -2,16 +2,16 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "eb9a9ac9ba58cccd34d5fa89bcd4151c", + "identityHash": "3f612998c4903a247d1c955da20b272d", "entities": [ { "tableName": "AUTHOR", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`AUTHOR_ID` TEXT NOT NULL, `AUTHOR_NAME` TEXT NOT NULL, `AUTHOR_SORT` TEXT NOT NULL, PRIMARY KEY(`AUTHOR_ID`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`AUTHOR_ID` INTEGER NOT NULL, `AUTHOR_NAME` TEXT NOT NULL, `AUTHOR_SORT` TEXT NOT NULL, PRIMARY KEY(`AUTHOR_ID`))", "fields": [ { "fieldPath": "id", "columnName": "AUTHOR_ID", - "affinity": "TEXT", + "affinity": "INTEGER", "notNull": true }, { @@ -38,7 +38,7 @@ }, { "tableName": "BOOK", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`BOOK_ID` INTEGER NOT NULL, `BOOK_TITLE` TEXT NOT NULL, `BOOK_SORT` TEXT NOT NULL, `BOOK_HAVE_COVER` INTEGER NOT NULL, `BOOK_RELEASE_DATE` INTEGER NOT NULL, `BOOK_LANGUAGE_ID` TEXT, `BOOK_RATING` INTEGER, `BOOK_SERIES_ID` INTEGER, `BOOK_SYNOPSIS` TEXT, `BOOK_ISNEW` INTEGER NOT NULL, `BOOK_NEW_ORDER` INTEGER, PRIMARY KEY(`BOOK_ID`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`BOOK_ID` INTEGER NOT NULL, `BOOK_TITLE` TEXT NOT NULL, `BOOK_SORT` TEXT NOT NULL, `BOOK_HAVE_COVER` INTEGER NOT NULL, `BOOK_RELEASE_DATE` INTEGER NOT NULL, `BOOK_LANGUAGE_ID` INTEGER, `BOOK_RATING` INTEGER, `BOOK_SERIES_ID` INTEGER, `BOOK_SYNOPSIS` TEXT, `BOOK_ISNEW` INTEGER NOT NULL, `BOOK_NEW_ORDER` INTEGER, PRIMARY KEY(`BOOK_ID`))", "fields": [ { "fieldPath": "id", @@ -73,7 +73,7 @@ { "fieldPath": "language", "columnName": "BOOK_LANGUAGE_ID", - "affinity": "TEXT", + "affinity": "INTEGER", "notNull": false }, { @@ -144,12 +144,12 @@ }, { "tableName": "LANGUAGE", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LANGUAGE_ID` TEXT NOT NULL, `LANGUAGE_NAME` TEXT NOT NULL, PRIMARY KEY(`LANGUAGE_ID`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`LANGUAGE_ID` INTEGER NOT NULL, `LANGUAGE_NAME` TEXT NOT NULL, PRIMARY KEY(`LANGUAGE_ID`))", "fields": [ { "fieldPath": "id", "columnName": "LANGUAGE_ID", - "affinity": "TEXT", + "affinity": "INTEGER", "notNull": true }, { @@ -208,7 +208,7 @@ }, { "tableName": "BookAuthorCrossRef", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`BOOK_ID` INTEGER NOT NULL, `AUTHOR_ID` TEXT NOT NULL, PRIMARY KEY(`BOOK_ID`, `AUTHOR_ID`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`BOOK_ID` INTEGER NOT NULL, `AUTHOR_ID` INTEGER NOT NULL, PRIMARY KEY(`BOOK_ID`, `AUTHOR_ID`))", "fields": [ { "fieldPath": "bookId", @@ -219,7 +219,7 @@ { "fieldPath": "authorId", "columnName": "AUTHOR_ID", - "affinity": "TEXT", + "affinity": "INTEGER", "notNull": true } ], @@ -284,7 +284,7 @@ "views": [], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'eb9a9ac9ba58cccd34d5fa89bcd4151c')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3f612998c4903a247d1c955da20b272d')" ] } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/database/crossref/BookAuthorCrossRef.kt b/app/src/main/java/com/pixelized/biblib/database/crossref/BookAuthorCrossRef.kt index c4a543b..7e0cca8 100644 --- a/app/src/main/java/com/pixelized/biblib/database/crossref/BookAuthorCrossRef.kt +++ b/app/src/main/java/com/pixelized/biblib/database/crossref/BookAuthorCrossRef.kt @@ -10,5 +10,5 @@ data class BookAuthorCrossRef( @ColumnInfo(name = BookDbo.ID) val bookId: Int, @ColumnInfo(name = AuthorDbo.ID, index = true) - val authorId: String + val authorId: Int, ) diff --git a/app/src/main/java/com/pixelized/biblib/database/data/AuthorDbo.kt b/app/src/main/java/com/pixelized/biblib/database/data/AuthorDbo.kt index fa91a63..0f26fcd 100644 --- a/app/src/main/java/com/pixelized/biblib/database/data/AuthorDbo.kt +++ b/app/src/main/java/com/pixelized/biblib/database/data/AuthorDbo.kt @@ -8,7 +8,7 @@ import androidx.room.PrimaryKey data class AuthorDbo( @PrimaryKey @ColumnInfo(name = ID) - val id: String, + val id: Int, @ColumnInfo(name = NAME) val name: String, @ColumnInfo(name = SORT) diff --git a/app/src/main/java/com/pixelized/biblib/database/data/BookDbo.kt b/app/src/main/java/com/pixelized/biblib/database/data/BookDbo.kt index 5f7b40c..e8c3f6d 100644 --- a/app/src/main/java/com/pixelized/biblib/database/data/BookDbo.kt +++ b/app/src/main/java/com/pixelized/biblib/database/data/BookDbo.kt @@ -19,7 +19,7 @@ data class BookDbo( @ColumnInfo(name = RELEASE_DATE) val releaseDate: Date, @ColumnInfo(name = LANGUAGE_ID) - val language: String? = null, // one-to-many + val language: Int? = null, // one-to-many @ColumnInfo(name = RATING) val rating: Int? = null, // details diff --git a/app/src/main/java/com/pixelized/biblib/database/data/LanguageDbo.kt b/app/src/main/java/com/pixelized/biblib/database/data/LanguageDbo.kt index 5f58a18..410ef37 100644 --- a/app/src/main/java/com/pixelized/biblib/database/data/LanguageDbo.kt +++ b/app/src/main/java/com/pixelized/biblib/database/data/LanguageDbo.kt @@ -8,7 +8,7 @@ import androidx.room.PrimaryKey data class LanguageDbo( @PrimaryKey @ColumnInfo(name = ID) - val id: String, + val id: Int, @ColumnInfo(name = NAME) val code: String, ) { diff --git a/app/src/main/java/com/pixelized/biblib/model/book/Author.kt b/app/src/main/java/com/pixelized/biblib/model/book/Author.kt index a13810d..ee6f4a6 100644 --- a/app/src/main/java/com/pixelized/biblib/model/book/Author.kt +++ b/app/src/main/java/com/pixelized/biblib/model/book/Author.kt @@ -1,7 +1,7 @@ package com.pixelized.biblib.model.book data class Author( - val id: String, + val id: Int, val name: String, val sort: String, ) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/model/book/Language.kt b/app/src/main/java/com/pixelized/biblib/model/book/Language.kt index ca61aa1..c2b417f 100644 --- a/app/src/main/java/com/pixelized/biblib/model/book/Language.kt +++ b/app/src/main/java/com/pixelized/biblib/model/book/Language.kt @@ -2,8 +2,8 @@ package com.pixelized.biblib.model.book import java.util.* -data class Language ( - val id : String, +data class Language( + val id: Int, val code: String, ) { val displayLanguage: String by lazy { Locale(code).displayLanguage } diff --git a/app/src/main/java/com/pixelized/biblib/model/book/Series.kt b/app/src/main/java/com/pixelized/biblib/model/book/Series.kt index 0370561..e807d40 100644 --- a/app/src/main/java/com/pixelized/biblib/model/book/Series.kt +++ b/app/src/main/java/com/pixelized/biblib/model/book/Series.kt @@ -1,7 +1,7 @@ package com.pixelized.biblib.model.book data class Series( - val id: Int?, + val id: Int, val name: String, val sort: String, val index: Int?, diff --git a/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt b/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt index 2f7c0e7..58e2a73 100644 --- a/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt +++ b/app/src/main/java/com/pixelized/biblib/network/factory/BookFactory.kt @@ -23,7 +23,7 @@ class BookFactory { val id: Int? = response.book_id val title: String? = response.book_title val sort: String? = response.book_sort - val authorId: List? = response.author_id + val authorId: List? = response.author_id?.mapNotNull { it.toIntOrNull() } val authorName: List? = response.author_name val authorSort: List? = response.author_sort val authorIdSize = authorId?.size ?: 0 @@ -54,7 +54,7 @@ class BookFactory { } val cover: Boolean? = response.book_has_cover?.toBoolean() val releaseDate = response.book_date?.let { parser.parse(it) } - val languageId = response.lang_id + val languageId = response.lang_id?.toIntOrNull() val languageCode = response.lang_code val language = if (languageId != null && languageCode != null) { Language(languageId, languageCode) @@ -91,7 +91,7 @@ class BookFactory { val id: Int? = response.book_id val title: String? = response.book_title val sort: String? = response.book_sort - val authorId: List? = response.author_id + val authorId: List? = response.author_id?.mapNotNull { it.toIntOrNull() } val authorName: List? = response.author_name val authorSort: List? = response.author_sort val authorIdSize = authorId?.size @@ -111,7 +111,7 @@ class BookFactory { } val cover: Boolean? = response.book_has_cover?.toBoolean() val releaseDate = response.book_date?.let { parser.parse(it) } - val languageId = response.lang_id + val languageId = response.lang_id?.toIntOrNull() val languageCode = response.lang_code val language = if (languageId != null && languageCode != null) { Language(languageId, languageCode) diff --git a/app/src/main/java/com/pixelized/biblib/network/factory/SeriesFactory.kt b/app/src/main/java/com/pixelized/biblib/network/factory/SeriesFactory.kt index e1dd4d5..db9bf71 100644 --- a/app/src/main/java/com/pixelized/biblib/network/factory/SeriesFactory.kt +++ b/app/src/main/java/com/pixelized/biblib/network/factory/SeriesFactory.kt @@ -16,7 +16,7 @@ class SeriesFactory { response.series?.forEachIndexed { index, data -> // build the Series items val series = Series( - id = data.series_id, + id = data.series_id ?: throw error("id"), name = data.series_name ?: throw error("name"), sort = data.series_sort ?: throw error("sort"), index = index, diff --git a/app/src/main/java/com/pixelized/biblib/repository/book/IBookRepository.kt b/app/src/main/java/com/pixelized/biblib/repository/book/IBookRepository.kt index c096bd6..44bfa4d 100644 --- a/app/src/main/java/com/pixelized/biblib/repository/book/IBookRepository.kt +++ b/app/src/main/java/com/pixelized/biblib/repository/book/IBookRepository.kt @@ -15,11 +15,11 @@ interface IBookRepository { fun getAuthorsSource() : DataSource.Factory - fun getSeriesSource() : DataSource.Factory - - fun getGenresSource() : DataSource.Factory + fun getSeriesSource(): DataSource.Factory - fun getLanguagesSource() : DataSource.Factory + fun getGenresSource(): DataSource.Factory + + fun getLanguagesSource(): DataSource.Factory suspend fun update(data: List) } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/composable/Search.kt b/app/src/main/java/com/pixelized/biblib/ui/composable/Search.kt index 866bd4d..a700ed5 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/composable/Search.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/composable/Search.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import com.pixelized.biblib.R import com.pixelized.biblib.ui.scaffold.SearchScaffoldState import com.pixelized.biblib.ui.scaffold.SearchScaffoldState.ContentState @@ -38,10 +37,12 @@ fun Search( modifier: Modifier = Modifier, state: SearchScaffoldState = rememberSearchScaffoldState(), focusRequester: FocusRequester = remember { FocusRequester() }, - avatar: String?, - onClose: () -> Unit = default(), - onAvatar: () -> Unit = default(), - onSearch: () -> Unit = default(), + avatar: String? = null, + searchValue: String = "", + onSearchValueChange: (String) -> Unit = default(), + onCloseTap: () -> Unit = default(), + onAvatarTap: () -> Unit = default(), + onSearchTap: () -> Unit = default(), ) { val horizontalPadding by animateDpAsState( targetValue = when (state.isCollapsed()) { @@ -49,17 +50,16 @@ fun Search( else -> MaterialTheme.bibLib.dimen.dp4 } ) - var search by remember { mutableStateOf("") } Row( modifier = Modifier - .clickable(onClick = onSearch) + .clickable(onClick = onSearchTap) .then(modifier), verticalAlignment = Alignment.CenterVertically ) { IconButton( modifier = Modifier.padding(start = horizontalPadding), - onClick = if (state.content != ContentState.INITIAL) onClose else onSearch, + onClick = if (state.content != ContentState.INITIAL) onCloseTap else onSearchTap, ) { Icon( imageVector = when (state.content) { @@ -84,7 +84,7 @@ fun Search( } ) }, - value = if (state.content == ContentState.SEARCH) search else "", + value = if (state.content == ContentState.SEARCH) searchValue else "", singleLine = true, enabled = state.content == ContentState.SEARCH, readOnly = state.content != ContentState.SEARCH, @@ -93,12 +93,12 @@ fun Search( unfocusedBorderColor = Color.Transparent, disabledBorderColor = Color.Transparent, ), - onValueChange = { search = it } + onValueChange = onSearchValueChange ) IconButton( modifier = Modifier.padding(end = horizontalPadding), - onClick = onAvatar, + onClick = onAvatarTap, ) { val imageModifier = Modifier .clip(CircleShape) diff --git a/app/src/main/java/com/pixelized/biblib/ui/scaffold/DetailBottomSheet.kt b/app/src/main/java/com/pixelized/biblib/ui/scaffold/DetailBottomSheet.kt index 39c66c3..b8f7b8c 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/scaffold/DetailBottomSheet.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/scaffold/DetailBottomSheet.kt @@ -14,9 +14,9 @@ import androidx.compose.ui.platform.LocalContext import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.biblib.R import com.pixelized.biblib.ui.composable.StateUio -import com.pixelized.biblib.ui.screen.detail.BookDetailViewModel -import com.pixelized.biblib.ui.screen.detail.DetailScreen -import com.pixelized.biblib.ui.screen.home.common.uio.BookUio +import com.pixelized.biblib.ui.screen.home.detail.BookDetailViewModel +import com.pixelized.biblib.ui.screen.home.detail.DetailScreen +import com.pixelized.biblib.ui.screen.home.detail.BookUio import com.pixelized.biblib.utils.extention.showToast import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch diff --git a/app/src/main/java/com/pixelized/biblib/ui/scaffold/SearchBottomSheet.kt b/app/src/main/java/com/pixelized/biblib/ui/scaffold/SearchBottomSheet.kt index 30e1617..f031375 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/scaffold/SearchBottomSheet.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/scaffold/SearchBottomSheet.kt @@ -50,10 +50,10 @@ fun SearchBottomSheet( filter = state.filter, 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("") + is SearchFilter.Author -> searchViewModel.authors.clear() + is SearchFilter.Series -> searchViewModel.series.clear() + is SearchFilter.Genre -> searchViewModel.genre.clear() + is SearchFilter.Language -> searchViewModel.language.clear() null -> Unit } state.collapse() diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt index cef73e1..b65bd03 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/HomeScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material.* import androidx.compose.runtime.* +import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusManager @@ -22,6 +23,7 @@ import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.PagerState import com.google.accompanist.pager.rememberPagerState +import com.pixelized.biblib.R import com.pixelized.biblib.ui.composable.Search import com.pixelized.biblib.ui.scaffold.* import com.pixelized.biblib.ui.scaffold.SearchScaffoldState.ContentState @@ -38,7 +40,9 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay import kotlinx.coroutines.launch -@OptIn(ExperimentalComposeUiApi::class, ExperimentalAnimationApi::class, +@OptIn( + ExperimentalComposeUiApi::class, + ExperimentalAnimationApi::class, ExperimentalMaterialApi::class ) @Composable @@ -58,16 +62,22 @@ fun HomeScreen( modifier = Modifier.statusBarsPadding(), state = state, topBar = { + val viewModel = LocalSearchViewModel.current + val search by viewModel.filterFlow.collectAsState(initial = "") Search( state = state, avatar = accountViewModel.avatar, focusRequester = focusRequester, - onClose = { + onCloseTap = { focusManager.clearFocus(force = true) keyboard?.hide() state.collapse() }, - onSearch = { + searchValue = search, + onSearchValueChange = { + viewModel.filter(criteria = it) + }, + onSearchTap = { if (state.content != ContentState.SEARCH || state.isCollapsed()) { state.expand(ContentState.SEARCH) scope.launch { @@ -80,7 +90,7 @@ fun HomeScreen( state.collapse() } }, - onAvatar = { + onAvatarTap = { if (state.content != ContentState.PROFILE || state.isCollapsed()) { focusManager.clearFocus(force = true) keyboard?.hide() @@ -131,7 +141,9 @@ fun HomeScreen( val bottomSearchState = LocalSearchBottomSheetState.current - BackHandler(state.isCollapsed().not() && bottomSearchState.bottomSheetState.isVisible.not()) { + BackHandler( + state.isCollapsed().not() && bottomSearchState.bottomSheetState.isVisible.not() + ) { state.collapse() } } @@ -173,14 +185,28 @@ fun HomeScreenContent( when (pages[it]) { is Page.News -> NewsPage() is Page.Books -> BooksPage() - is Page.Author -> Box(modifier = Modifier) - is Page.Series -> Box(modifier = Modifier) - is Page.Tag -> Box(modifier = Modifier) + is Page.Author -> NotYetImplemented() + is Page.Series -> NotYetImplemented() + is Page.Tag -> NotYetImplemented() } } } } +@Composable +private fun NotYetImplemented() { + Box( + modifier = Modifier.fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Text( + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.onSurface, + text = stringResource(id = R.string.not_implemented_yet) + ) + } +} + @Composable @Preview(showBackground = true) private fun HomeScreenContentPreview() { diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/detail/BookDetailViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookDetailViewModel.kt similarity index 94% rename from app/src/main/java/com/pixelized/biblib/ui/screen/detail/BookDetailViewModel.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookDetailViewModel.kt index 7265483..4f46a53 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/detail/BookDetailViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookDetailViewModel.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.screen.detail +package com.pixelized.biblib.ui.screen.home.detail import android.util.Log import androidx.lifecycle.ViewModel @@ -6,7 +6,6 @@ import com.pixelized.biblib.model.book.Book import com.pixelized.biblib.network.client.IBibLibClient import com.pixelized.biblib.network.factory.BookFactory import com.pixelized.biblib.ui.composable.StateUio -import com.pixelized.biblib.ui.screen.home.common.uio.BookUio import com.pixelized.biblib.utils.extention.capitalize import com.pixelized.biblib.utils.extention.shortDate import dagger.hilt.android.lifecycle.HiltViewModel diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/uio/BookUio.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookUio.kt similarity index 83% rename from app/src/main/java/com/pixelized/biblib/ui/screen/home/common/uio/BookUio.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookUio.kt index 10c9f0a..09ad166 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/common/uio/BookUio.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/BookUio.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.screen.home.common.uio +package com.pixelized.biblib.ui.screen.home.detail import java.io.Serializable diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/detail/DetailScreen.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt similarity index 97% rename from app/src/main/java/com/pixelized/biblib/ui/screen/detail/DetailScreen.kt rename to app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt index d24770e..ffc553e 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/detail/DetailScreen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/detail/DetailScreen.kt @@ -1,4 +1,4 @@ -package com.pixelized.biblib.ui.screen.detail +package com.pixelized.biblib.ui.screen.home.detail import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES @@ -37,13 +37,13 @@ import com.pixelized.biblib.ui.composable.animation.AnimatedOffset import com.pixelized.biblib.ui.composable.isSuccessful import com.pixelized.biblib.ui.scaffold.DetailBottomSheetState import com.pixelized.biblib.ui.scaffold.LocalDetailBottomSheetState -import com.pixelized.biblib.ui.screen.home.common.uio.BookUio import com.pixelized.biblib.ui.screen.home.page.profile.ProfileViewModel import com.pixelized.biblib.ui.screen.home.page.profile.UserUio import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.default import com.skydoves.landscapist.CircularReveal +import com.skydoves.landscapist.ShimmerParams import com.skydoves.landscapist.glide.GlideImage import kotlinx.coroutines.launch @@ -129,6 +129,13 @@ private fun DetailScreenContent( .fillMaxWidth() .padding(vertical = MaterialTheme.bibLib.dimen.dp16) .height(MaterialTheme.bibLib.dimen.detail.cover), + loading = { + Box(modifier = Modifier.matchParentSize()) { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center) + ) + } + }, previewPlaceholder = R.drawable.ic_launcher_foreground, circularReveal = CircularReveal(duration = 1000), contentScale = ContentScale.FillHeight, diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BookThumbnail.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BookThumbnail.kt index 1e4da48..cb6ff2b 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BookThumbnail.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/books/BookThumbnail.kt @@ -1,8 +1,6 @@ package com.pixelized.biblib.ui.screen.home.page.books import android.content.res.Configuration -import androidx.compose.animation.* -import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.material.Card diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/news/NewThumbnail.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/news/NewThumbnail.kt index 8b4ef53..2301b2b 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/news/NewThumbnail.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/news/NewThumbnail.kt @@ -1,7 +1,6 @@ package com.pixelized.biblib.ui.screen.home.page.news import android.content.res.Configuration -import androidx.compose.animation.* import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.aspectRatio diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/BookSearchUio.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/BookSearchUio.kt new file mode 100644 index 0000000..d7d6b5f --- /dev/null +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/BookSearchUio.kt @@ -0,0 +1,10 @@ +package com.pixelized.biblib.ui.screen.home.page.search + +data class BookSearchUio( + val id: Int, + val title: String, + val author: List, + val language: String? = null, + val genre: List? = null, + val series: String? = null, +) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/CategorySearchPage.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/CategorySearchPage.kt index 35d103d..9b35852 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/CategorySearchPage.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/CategorySearchPage.kt @@ -88,9 +88,9 @@ fun CategorySearchPageContent( focusRequester: FocusRequester = FocusRequester(), filter: SearchFilter?, searchFlow: () -> Flow, - dataFlow: () -> Flow> = { emptyFlow() }, + dataFlow: () -> Flow> = { emptyFlow() }, onSearchChange: (String) -> Unit = default(), - onData: (String) -> Unit = default(), + onData: (SearchViewModel.FilterUio?) -> Unit = default(), onClose: () -> Unit = default(), ) { val data = dataFlow().collectAsLazyPagingItems() @@ -142,15 +142,15 @@ fun CategorySearchPageContent( LazyColumn( modifier = Modifier.fillMaxSize() ) { - items(items = data, key = { it }) { + items(items = data, key = { it.id }) { Text( modifier = Modifier - .clickable { onData(it ?: "") } + .clickable { onData(it) } .fillMaxWidth() .padding(all = MaterialTheme.bibLib.dimen.dp16), style = MaterialTheme.typography.body1, color = MaterialTheme.colors.onSurface, - text = it ?: "" + text = it?.label ?: "" ) } } diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/SearchPage.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/SearchPage.kt index 74f2554..a47199c 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/SearchPage.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/SearchPage.kt @@ -2,29 +2,40 @@ 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_YES +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.spring +import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState import androidx.compose.material.* import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown -import androidx.compose.runtime.Composable -import androidx.compose.runtime.derivedStateOf -import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember +import androidx.compose.material.icons.filled.NavigateNext +import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview +import androidx.paging.LoadState +import androidx.paging.PagingData +import androidx.paging.compose.LazyPagingItems +import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.compose.items +import com.pixelized.biblib.ui.scaffold.LocalDetailBottomSheetState import com.pixelized.biblib.ui.scaffold.LocalSearchBottomSheetState import com.pixelized.biblib.ui.scaffold.LocalSearchViewModel import com.pixelized.biblib.ui.scaffold.SearchFilter import com.pixelized.biblib.ui.theme.BibLibTheme import com.pixelized.biblib.utils.extention.bibLib import com.pixelized.biblib.utils.extention.default +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow @Composable fun SearchPage( @@ -35,6 +46,7 @@ fun SearchPage( SearchPageContent( modifier = Modifier.fillMaxWidth(), + search = searchViewModel.search, filters = filters, onFilter = { bottomSearchState.expandSearch(it) @@ -45,9 +57,20 @@ fun SearchPage( @Composable private fun SearchPageContent( modifier: Modifier = Modifier, + search: Flow> = emptyFlow(), filters: List = SearchFilter.all, onFilter: (filter: SearchFilter) -> Unit = default(), ) { + val items = search.collectAsLazyPagingItems() + val detail = LocalDetailBottomSheetState.current + + SearchLoader( + modifier = Modifier + .fillMaxWidth() + .height(MaterialTheme.bibLib.dimen.dp2), + isLoading = { items.isLoading }, + ) + LazyColumn( modifier = modifier, verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.dp8), @@ -59,6 +82,60 @@ private fun SearchPageContent( onFilter = onFilter, ) } + items(items = items, key = { it.id }) { + if (it != null) { + SearchItem( + item = it, + onClick = { + detail.expandBookDetail(it.id) + } + ) + } + } + } +} + +@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 +private fun SearchItem( + modifier: Modifier = Modifier, + item: BookSearchUio, + onClick: () -> Unit = default() +) { + Row( + modifier = Modifier + .clickable(onClick = onClick) + .fillMaxWidth() + .padding(all = MaterialTheme.bibLib.dimen.dp16) + .then(modifier) + ) { + Text( + modifier = Modifier.weight(1f), + style = MaterialTheme.typography.body1, + color = MaterialTheme.colors.onSurface, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + text = item.title + ) + Icon( + imageVector = Icons.Default.NavigateNext, + contentDescription = null + ) } } @@ -74,13 +151,13 @@ private fun SearchFilter( .then(modifier) ) { filters.forEachIndexed { index, filter -> - val modifier = if (index != filters.lastIndex) { + val chipModifier = if (index != filters.lastIndex) { Modifier.padding(end = MaterialTheme.bibLib.dimen.dp8) } else { Modifier } SearchChipFilter( - modifier = modifier, + modifier = chipModifier, selected = filter.isSelected, label = stringResource(id = filter.label), value = filter.value, @@ -124,25 +201,46 @@ private fun SearchChipFilter( private fun rememberSearchFilter( searchViewModel: SearchViewModel = LocalSearchViewModel.current ): List { - 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.Series(value = series), - SearchFilter.Genre(value = genre), - SearchFilter.Language(value = language) + SearchFilter.Author(value = searchViewModel.authors.search?.label), + SearchFilter.Series(value = searchViewModel.series.search?.label), + SearchFilter.Genre(value = searchViewModel.genre.search?.label), + SearchFilter.Language(value = searchViewModel.language.search?.label) ) } } - return filters } +private val LazyPagingItems<*>.isLoading: Boolean + get() { + val isLoading by derivedStateOf { + loadState.refresh is LoadState.Loading || loadState.append is LoadState.Loading + } + return isLoading + } + + +@Composable +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) +@Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) +private fun SearchItemPreview() { + BibLibTheme { + SearchItem( + item = BookSearchUio( + id = 0, + title = "Fondation", + author = listOf("Issac Asimov"), + language = "Fr", + genre = listOf("SF, Classic"), + series = "Fondation", + ) + ) + } +} + @Composable @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_NO) @Preview(showBackground = true, uiMode = UI_MODE_NIGHT_YES) diff --git a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/SearchViewModel.kt b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/SearchViewModel.kt index e6df22b..24e20dc 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/SearchViewModel.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/screen/home/page/search/SearchViewModel.kt @@ -1,9 +1,12 @@ package com.pixelized.biblib.ui.screen.home.page.search -import androidx.compose.runtime.* +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import androidx.paging.* +import com.pixelized.biblib.model.book.Book import com.pixelized.biblib.repository.book.IBookRepository import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.CoroutineScope @@ -11,6 +14,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import javax.inject.Inject @@ -19,70 +23,122 @@ class SearchViewModel @Inject constructor( bookRepository: IBookRepository, ) : ViewModel() { - var search: String by mutableStateOf("") - private set - - val authors = FilterManager( + val authors = CategoryFilterManager( scope = viewModelScope, source = bookRepository.getAuthorsSource() - .map { it.name } - .asPagingSourceFactory(Dispatchers.IO) + .map { FilterUio(it.id, it.name) } + .asPagingSourceFactory(Dispatchers.IO), + sourceFilter = { data, filter -> data.label.contains(filter, ignoreCase = true) } ) - val series = FilterManager( + val series = CategoryFilterManager( scope = viewModelScope, source = bookRepository.getSeriesSource() - .map { it.name } - .asPagingSourceFactory(Dispatchers.IO) + .map { FilterUio(it.id, it.name) } + .asPagingSourceFactory(Dispatchers.IO), + sourceFilter = { data, filter -> data.label.contains(filter, ignoreCase = true) } ) - val genre = FilterManager( + val genre = CategoryFilterManager( scope = viewModelScope, source = bookRepository.getGenresSource() - .map { it.name } - .asPagingSourceFactory(Dispatchers.IO) + .map { FilterUio(it.id, it.name) } + .asPagingSourceFactory(Dispatchers.IO), + sourceFilter = { data, filter -> data.label.contains(filter, ignoreCase = true) } ) - val language = FilterManager( + val language = CategoryFilterManager( scope = viewModelScope, source = bookRepository.getLanguagesSource() - .map { it.displayLanguage } - .asPagingSourceFactory(Dispatchers.IO) + .map { FilterUio(it.id, it.displayLanguage) } + .asPagingSourceFactory(Dispatchers.IO), + sourceFilter = { data, filter -> data.label.contains(filter, ignoreCase = true) } ) - fun search(criteria: String) { + private val _filterFlow = MutableStateFlow(value = "") + val filterFlow: Flow get() = _filterFlow + fun filter(criteria: String) { + viewModelScope.launch { _filterFlow.emit(criteria) } } - class FilterManager( - private val scope: CoroutineScope, - source: () -> PagingSource, - ) { - private val _searchFlow = MutableStateFlow("") - val searchFlow: Flow get() = _searchFlow + fun clear() { + viewModelScope.launch { _filterFlow.emit("") } + } - private val _filterFlow = MutableStateFlow("") + val search = Pager( + config = PagingConfig(pageSize = SEARCH_PAGE_SIZE), + pagingSourceFactory = bookRepository.getBooksSource() + .asPagingSourceFactory(Dispatchers.IO) + ).flow.cachedIn(viewModelScope) + .combine(filterFlow) { paging, filter -> + paging.filter { it.title.contains(filter, ignoreCase = true) } + } + .combine(authors.confirmFlow) { paging, filter -> + paging.filter { filter == null || it.author.isEmpty() || it.author.any { author -> author.id == filter.id } } + } + .combine(series.confirmFlow) { paging, filter -> + paging.filter { filter == null || it.series == null || it.series.id == filter.id } + } + .combine(genre.confirmFlow) { paging, filter -> + paging.filter { filter == null || it.genre.isNullOrEmpty() || it.genre.any { author -> author.id == filter.id } } + } + .combine(language.confirmFlow) { paging, filter -> + paging.filter { filter == null || it.language == null || it.language.id == filter.id } + } + .map { paging -> paging.map { it.toBookSearchUio() } } + + data class FilterUio( + val id: Int, + val label: String, + ) + + class CategoryFilterManager( + private val scope: CoroutineScope, + source: () -> PagingSource, + sourceFilter: (T, String) -> Boolean, + ) { + private val _filterFlow = MutableStateFlow(value = "") val filterFlow: Flow get() = _filterFlow - val dataFlow: Flow> = Pager( + private val _confirmFlow = MutableStateFlow(value = null) + val confirmFlow: Flow get() = _confirmFlow + + val dataFlow: Flow> = Pager( config = PagingConfig(pageSize = 30), pagingSourceFactory = source, ).flow.cachedIn(scope).combine(this._filterFlow) { paging, filter -> - paging.filter { it.contains(filter, ignoreCase = true) } + paging.filter { sourceFilter(it, filter) } } - val filter: State - @Composable get() = _filterFlow.collectAsState(initial = "") - - val search: State - @Composable get() = _searchFlow.collectAsState(initial = "") + var search: T? by mutableStateOf(null) + private set fun filter(criteria: String) { scope.launch { _filterFlow.emit(criteria) } } - fun confirm(criteria: String) { - scope.launch { _searchFlow.emit(criteria) } + fun clear() { + search = null + scope.launch { _confirmFlow.emit(null) } + } + + fun confirm(criteria: T?) { + search = criteria + scope.launch { _confirmFlow.emit(criteria) } } } + + private fun Book.toBookSearchUio() = BookSearchUio( + id = id, + title = title, + author = author.map { it.name }, + language = language?.displayLanguage, + genre = genre?.map { it.name }, + series = series?.name, + ) + + companion object { + private const val SEARCH_PAGE_SIZE = 15 + } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/biblib/ui/theme/dimen/BibLibDimen.kt b/app/src/main/java/com/pixelized/biblib/ui/theme/dimen/BibLibDimen.kt index 3a129ee..a5b80be 100644 --- a/app/src/main/java/com/pixelized/biblib/ui/theme/dimen/BibLibDimen.kt +++ b/app/src/main/java/com/pixelized/biblib/ui/theme/dimen/BibLibDimen.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.unit.dp @Immutable data class BibLibDimen( val default: Dp = 0.dp, + val dp2: Dp = 2.dp, val dp4: Dp = 4.dp, val dp8: Dp = 8.dp, val dp16: Dp = 16.dp,