Refacotor and animate the Search bar.

This commit is contained in:
Thomas Andres Gomez 2022-07-05 11:21:32 +02:00
parent 1c9f856e65
commit d56daf926d
11 changed files with 203 additions and 112 deletions

View file

@ -3,7 +3,6 @@ package com.pixelized.biblib.ui.composable
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
@ -17,10 +16,11 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.pixelized.biblib.R
@ -36,6 +36,7 @@ import com.skydoves.landscapist.glide.GlideImage
fun Search(
modifier: Modifier = Modifier,
state: SearchScaffoldState = rememberSearchScaffoldState(),
focusRequester: FocusRequester = remember { FocusRequester() },
avatar: String?,
onAvatar: () -> Unit = default(),
onSearch: () -> Unit = default(),
@ -47,6 +48,7 @@ fun Search(
}
)
var search by remember { mutableStateOf("") }
Row(
modifier = Modifier
.clickable(onClick = onSearch)
@ -62,7 +64,9 @@ fun Search(
contentDescription = null
)
TextField(
modifier = Modifier.weight(1f),
modifier = Modifier
.focusRequester(focusRequester = focusRequester)
.weight(1f),
label = {
Text(
color = MaterialTheme.colors.onSurface,
@ -71,8 +75,8 @@ fun Search(
},
value = if (state.isCollapsed()) "" else search,
singleLine = true,
enabled = state.isCollapsed().not(),
readOnly = state.isCollapsed(),
enabled = state.content == SearchScaffoldState.ContentState.SEARCH,
readOnly = state.content != SearchScaffoldState.ContentState.SEARCH,
colors = TextFieldDefaults.outlinedTextFieldColors(
focusedBorderColor = Color.Transparent,
unfocusedBorderColor = Color.Transparent,
@ -87,14 +91,18 @@ fun Search(
) {
if (avatar != null) {
GlideImage(
modifier = Modifier.clip(CircleShape).size(32.dp),
modifier = Modifier
.clip(CircleShape)
.size(32.dp),
previewPlaceholder = R.drawable.ic_google,
contentScale = ContentScale.Fit,
imageModel = avatar,
)
} else {
Icon(
modifier = Modifier.clip(CircleShape).size(32.dp),
modifier = Modifier
.clip(CircleShape)
.size(32.dp),
tint = MaterialTheme.colors.onSurface,
imageVector = Icons.Default.Person,
contentDescription = null,

View file

@ -11,7 +11,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import com.pixelized.biblib.ui.screen.authentication.AuthenticationScreen
import com.pixelized.biblib.ui.screen.home.HomeScreen
import com.pixelized.biblib.ui.screen.profile.ProfileScreen
import com.pixelized.biblib.ui.screen.home.page.profile.ProfilePage
val LocalScreenNavHostController = compositionLocalOf<NavHostController> {
error("LocalFullScreenNavHostController is not ready yet.")
@ -39,7 +39,7 @@ fun ScreenNavHost(
HomeScreen()
}
composable(Screen.Profile.route) {
ProfileScreen()
ProfilePage()
}
}
}

View file

@ -68,9 +68,11 @@ fun SearchScaffold(
}
) { measurables, constraints ->
val cardBox = measurables.first { it.layoutId == "cardBox" }.measure(constraints)
val contentBox = measurables.first { it.layoutId == "contentBox" }.measure(constraints.copy(
maxHeight = constraints.maxHeight - initialCardBoxHeight
))
val contentBox = measurables.first { it.layoutId == "contentBox" }.measure(
constraints.copy(
maxHeight = constraints.maxHeight - initialCardBoxHeight
)
)
layout(constraints.maxWidth, constraints.maxHeight) {
if (initialCardBoxHeight == 0) {
@ -97,14 +99,19 @@ class SearchScaffoldState(
) {
private var expended: Boolean by mutableStateOf(expended)
var content: ContentState by mutableStateOf(ContentState.INITIAL)
private set
fun isCollapsed(): Boolean = expended.not()
fun expand() {
fun expand(state: ContentState) {
content = state
expended = true
}
fun collapse() {
expended = false
content = ContentState.INITIAL
}
companion object {
@ -113,4 +120,12 @@ class SearchScaffoldState(
restore = { SearchScaffoldState(it) },
)
}
@Stable
@Immutable
enum class ContentState {
INITIAL,
SEARCH,
PROFILE,
}
}

View file

@ -38,8 +38,8 @@ import com.pixelized.biblib.ui.composable.isSuccessful
import com.pixelized.biblib.ui.scaffold.BottomDetailState
import com.pixelized.biblib.ui.scaffold.LocalBottomDetailState
import com.pixelized.biblib.ui.screen.home.common.uio.BookUio
import com.pixelized.biblib.ui.screen.profile.ProfileViewModel
import com.pixelized.biblib.ui.screen.profile.UserUio
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

View file

@ -1,7 +1,11 @@
package com.pixelized.biblib.ui.screen.home
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.*
import androidx.compose.animation.*
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.ScrollableTabRow
import androidx.compose.material.Tab
@ -9,7 +13,9 @@ import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.SoftwareKeyboardController
import androidx.compose.ui.res.stringResource
@ -23,67 +29,106 @@ import com.pixelized.biblib.ui.composable.Search
import com.pixelized.biblib.ui.scaffold.BottomDetailScaffold
import com.pixelized.biblib.ui.scaffold.SearchScaffold
import com.pixelized.biblib.ui.scaffold.SearchScaffoldState
import com.pixelized.biblib.ui.scaffold.SearchScaffoldState.ContentState
import com.pixelized.biblib.ui.scaffold.rememberSearchScaffoldState
import com.pixelized.biblib.ui.screen.connectivity.ConnectivityViewModel
import com.pixelized.biblib.ui.screen.home.common.composable.ConnectivityHeader
import com.pixelized.biblib.ui.screen.home.page.Page
import com.pixelized.biblib.ui.screen.home.page.SearchPage
import com.pixelized.biblib.ui.screen.home.page.books.BooksPage
import com.pixelized.biblib.ui.screen.home.page.news.NewsPage
import com.pixelized.biblib.ui.screen.profile.ProfileScreen
import com.pixelized.biblib.ui.screen.home.page.profile.ProfilePage
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@OptIn(ExperimentalComposeUiApi::class)
@OptIn(ExperimentalComposeUiApi::class, ExperimentalAnimationApi::class)
@Composable
fun HomeScreen(
accountViewModel: HomeViewModel = hiltViewModel(),
connectivityViewModel: ConnectivityViewModel = hiltViewModel(),
keyboard: SoftwareKeyboardController? = LocalSoftwareKeyboardController.current,
searchScaffoldState: SearchScaffoldState = rememberSearchScaffoldState(),
state: SearchScaffoldState = rememberSearchScaffoldState(),
) {
var pouet: String by remember { mutableStateOf("aucun") }
val scope = rememberCoroutineScope()
val focusManager: FocusManager = LocalFocusManager.current
val focusRequester: FocusRequester = remember { FocusRequester() }
BottomDetailScaffold {
SearchScaffold(
modifier = Modifier.statusBarsPadding(),
state = searchScaffoldState,
state = state,
topBar = {
Search(
state = searchScaffoldState,
state = state,
avatar = accountViewModel.avatar,
focusRequester = focusRequester,
onSearch = {
if (searchScaffoldState.isCollapsed()) {
pouet = "search"
searchScaffoldState.expand()
if (state.content != ContentState.SEARCH || state.isCollapsed()) {
state.expand(ContentState.SEARCH)
scope.launch {
delay(100)
focusRequester.requestFocus()
}
} else {
focusManager.clearFocus(force = true)
keyboard?.hide()
searchScaffoldState.collapse()
state.collapse()
}
},
onAvatar = {
if (searchScaffoldState.isCollapsed()) {
pouet = "profile"
searchScaffoldState.expand()
if (state.content != ContentState.PROFILE || state.isCollapsed()) {
focusManager.clearFocus(force = true)
keyboard?.hide()
state.expand(ContentState.PROFILE)
} else {
searchScaffoldState.collapse()
state.collapse()
}
}
)
},
search = {
Box(modifier = Modifier.fillMaxSize()) {
when (pouet) {
"profile" -> ProfileScreen()
"search" -> Unit
AnimatedContent(
targetState = state.content,
transitionSpec = {
when {
targetState == ContentState.INITIAL -> {
EnterTransition.None with fadeOut()
}
targetState.ordinal < initialState.ordinal -> {
val enter = slideInHorizontally { -it } + fadeIn()
val exit = slideOutHorizontally { +it } + fadeOut()
enter with exit
}
else -> {
val enter = slideInHorizontally { +it } + fadeIn()
val exit = slideOutHorizontally { -it } + fadeOut()
enter with exit
}
}
}
) {
when (it) {
ContentState.SEARCH -> SearchPage()
ContentState.PROFILE -> ProfilePage()
else -> Unit
}
}
}
},
content = {
HomeScreenContent()
HomeScreenContent(
isNetworkAvailable = { connectivityViewModel.isNetworkAvailable },
pages = Page.all
)
},
)
BackHandler(searchScaffoldState.isCollapsed().not()) {
searchScaffoldState.collapse()
BackHandler(state.isCollapsed().not()) {
state.collapse()
}
}
}
@ -91,6 +136,7 @@ fun HomeScreen(
@OptIn(ExperimentalPagerApi::class)
@Composable
fun HomeScreenContent(
isNetworkAvailable: () -> State<Boolean>,
pages: List<Page> = Page.all
) {
val scope: CoroutineScope = rememberCoroutineScope()
@ -109,6 +155,11 @@ fun HomeScreenContent(
)
}
}
ConnectivityHeader(
isNetworkAvailable = isNetworkAvailable,
)
HorizontalPager(
modifier = Modifier.fillMaxSize(),
state = pagerState,
@ -129,6 +180,10 @@ fun HomeScreenContent(
@Preview(showBackground = true)
private fun HomeScreenContentPreview() {
BibLibTheme {
HomeScreenContent()
val isNetworkAvailable = { mutableStateOf(true) }
HomeScreenContent(
isNetworkAvailable = isNetworkAvailable,
pages = Page.all,
)
}
}

View file

@ -0,0 +1,44 @@
package com.pixelized.biblib.ui.screen.home.page
import android.content.res.Configuration.UI_MODE_NIGHT_NO
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
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
@Composable
fun SearchPage() {
SearchPageContent(
modifier = Modifier
.fillMaxWidth()
.padding(MaterialTheme.bibLib.dimen.dp16)
)
}
@Composable
private fun SearchPageContent(
modifier: Modifier = Modifier,
) {
Box(modifier = modifier) {
Text(
style = MaterialTheme.typography.h6,
text = "SearchPageContent"
)
}
}
@Composable
@Preview(uiMode = UI_MODE_NIGHT_NO)
@Preview(uiMode = UI_MODE_NIGHT_YES)
private fun SearchPageContentPreview() {
BibLibTheme {
SearchPageContent()
}
}

View file

@ -7,17 +7,12 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.compose.LazyPagingItems
import androidx.paging.compose.items
import com.pixelized.biblib.ui.scaffold.LocalBottomDetailState
import com.pixelized.biblib.ui.screen.connectivity.ConnectivityViewModel
import com.pixelized.biblib.ui.screen.home.common.composable.ConnectivityHeader
import com.pixelized.biblib.ui.screen.home.common.composable.bookPreviewResources
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
import com.pixelized.biblib.ui.theme.BibLibTheme
@ -27,60 +22,49 @@ import com.pixelized.biblib.utils.extention.navigationBarsHeight
@Composable
fun BooksPage(
connectivityViewModel: ConnectivityViewModel = hiltViewModel(),
newsViewModel: BooksViewModel = hiltViewModel()
) {
val books = newsViewModel.books
BooksPageContent(
isNetworkAvailable = { connectivityViewModel.isNetworkAvailable },
books = books,
)
}
@Composable
private fun BooksPageContent(
isNetworkAvailable: () -> State<Boolean>,
books: LazyPagingItems<BookThumbnailUio>,
) {
val bottomDetailState = LocalBottomDetailState.current
Column {
ConnectivityHeader(
isNetworkAvailable = isNetworkAvailable,
)
LazyColumn(
contentPadding = PaddingValues(
top = MaterialTheme.bibLib.dimen.thumbnail.padding,
bottom = MaterialTheme.bibLib.dimen.thumbnail.padding + navigationBarsHeight(),
),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
) {
items(
items = books,
key = { it.id },
) { thumbnail ->
BookThumbnail(
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.thumbnail.padding),
thumbnail = thumbnail,
onClick = {
bottomDetailState.expandBookDetail(id = it.id)
},
)
}
LazyColumn(
contentPadding = PaddingValues(
top = MaterialTheme.bibLib.dimen.thumbnail.padding,
bottom = MaterialTheme.bibLib.dimen.thumbnail.padding + navigationBarsHeight(),
),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
) {
items(
items = books,
key = { it.id },
) { thumbnail ->
BookThumbnail(
modifier = Modifier.padding(horizontal = MaterialTheme.bibLib.dimen.thumbnail.padding),
thumbnail = thumbnail,
onClick = {
bottomDetailState.expandBookDetail(id = it.id)
},
)
}
}
}
@Composable
@Preview
private fun BooksPagePreview() {
val isNetworkAvailable = remember { mutableStateOf(false) }
BibLibTheme {
Column {
BooksPageContent(
isNetworkAvailable = { isNetworkAvailable },
books = bookPreviewResources()
)
}

View file

@ -7,17 +7,13 @@ import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.paging.compose.LazyPagingItems
import com.pixelized.biblib.ui.scaffold.BottomDetailState
import com.pixelized.biblib.ui.scaffold.LocalBottomDetailState
import com.pixelized.biblib.ui.screen.connectivity.ConnectivityViewModel
import com.pixelized.biblib.ui.screen.home.common.composable.ConnectivityHeader
import com.pixelized.biblib.ui.screen.home.common.composable.bookPreviewResources
import com.pixelized.biblib.ui.screen.home.common.uio.BookThumbnailUio
import com.pixelized.biblib.ui.theme.BibLibTheme
@ -26,13 +22,11 @@ import com.pixelized.biblib.utils.extention.navigationBarsHeight
@Composable
fun NewsPage(
connectivityViewModel: ConnectivityViewModel = hiltViewModel(),
booksViewModel: NewsBookViewModel = hiltViewModel()
) {
val books = booksViewModel.news
NewsPageContent(
isNetworkAvailable = { connectivityViewModel.isNetworkAvailable },
books = books,
)
}
@ -40,37 +34,30 @@ fun NewsPage(
@Composable
private fun NewsPageContent(
isNetworkAvailable: () -> State<Boolean>,
books: LazyPagingItems<BookThumbnailUio>,
) {
val bottomDetailState: BottomDetailState = LocalBottomDetailState.current
Column {
ConnectivityHeader(
isNetworkAvailable = isNetworkAvailable,
)
LazyVerticalGrid(
modifier = Modifier.weight(1f),
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(
start = MaterialTheme.bibLib.dimen.thumbnail.padding,
end = MaterialTheme.bibLib.dimen.thumbnail.padding,
top = MaterialTheme.bibLib.dimen.thumbnail.padding,
bottom = MaterialTheme.bibLib.dimen.thumbnail.padding + navigationBarsHeight(),
),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
) {
items(
count = books.itemCount
) { index ->
NewThumbnail(
thumbnail = books[index],
onClick = {
bottomDetailState.expandBookDetail(id = it.id)
},
)
}
LazyVerticalGrid(
columns = GridCells.Fixed(2),
contentPadding = PaddingValues(
start = MaterialTheme.bibLib.dimen.thumbnail.padding,
end = MaterialTheme.bibLib.dimen.thumbnail.padding,
top = MaterialTheme.bibLib.dimen.thumbnail.padding,
bottom = MaterialTheme.bibLib.dimen.thumbnail.padding + navigationBarsHeight(),
),
verticalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.bibLib.dimen.thumbnail.arrangement),
) {
items(
count = books.itemCount
) { index ->
NewThumbnail(
thumbnail = books[index],
onClick = {
bottomDetailState.expandBookDetail(id = it.id)
},
)
}
}
}
@ -83,7 +70,6 @@ private fun NewPagePreview() {
BibLibTheme {
Column {
NewsPageContent(
isNetworkAvailable = { isNetworkAvailable },
books = bookPreviewResources()
)
}

View file

@ -1,4 +1,4 @@
package com.pixelized.biblib.ui.screen.profile
package com.pixelized.biblib.ui.screen.home.page.profile
import android.content.Intent
import android.content.res.Configuration.UI_MODE_NIGHT_NO
@ -18,13 +18,12 @@ import com.pixelized.biblib.network.client.IBibLibClient
import com.pixelized.biblib.ui.composable.StateUio
import com.pixelized.biblib.ui.navigation.screen.LocalScreenNavHostController
import com.pixelized.biblib.ui.navigation.screen.navigateToAuthentication
import com.pixelized.biblib.ui.navigation.screen.navigateToProfile
import com.pixelized.biblib.ui.theme.BibLibTheme
import com.pixelized.biblib.utils.extention.bibLib
import com.pixelized.biblib.utils.extention.default
@Composable
fun ProfileScreen(
fun ProfilePage(
viewModel: ProfileViewModel = hiltViewModel(),
) {
val context = LocalContext.current

View file

@ -1,4 +1,4 @@
package com.pixelized.biblib.ui.screen.profile
package com.pixelized.biblib.ui.screen.home.page.profile
import android.util.Log
import androidx.compose.runtime.getValue

View file

@ -1,4 +1,4 @@
package com.pixelized.biblib.ui.screen.profile
package com.pixelized.biblib.ui.screen.home.page.profile
data class UserUio(