diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0ca010bb5..f6673d20c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -39,7 +39,7 @@ + android:screenOrientation="fullUser"> diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt b/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt new file mode 100644 index 000000000..6b74e196a --- /dev/null +++ b/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceLandscapePreviews.kt @@ -0,0 +1,18 @@ +package org.michaelbel.movies.ui.preview + +import android.content.res.Configuration +import androidx.compose.ui.tooling.preview.Preview + +@Preview( + name = "Day Landscape", + uiMode = Configuration.UI_MODE_NIGHT_NO, + widthDp = 800, + heightDp = 360 +) +@Preview( + name = "Night Landscape", + uiMode = Configuration.UI_MODE_NIGHT_YES, + widthDp = 800, + heightDp = 360 +) +annotation class DeviceLandscapePreviews \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt b/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt index 818d86aa9..b87e3e56b 100644 --- a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt +++ b/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DevicePreviews.kt @@ -4,11 +4,11 @@ import android.content.res.Configuration import androidx.compose.ui.tooling.preview.Preview @Preview( - uiMode = Configuration.UI_MODE_NIGHT_NO, - name = "Day theme" + name = "Day theme", + uiMode = Configuration.UI_MODE_NIGHT_NO ) @Preview( - uiMode = Configuration.UI_MODE_NIGHT_YES, - name = "Night theme" + name = "Night theme", + uiMode = Configuration.UI_MODE_NIGHT_YES ) annotation class DevicePreviews \ No newline at end of file diff --git a/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceUserPreviews.kt b/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceUserPreviews.kt new file mode 100644 index 000000000..c182a9e97 --- /dev/null +++ b/core/ui/src/main/kotlin/org/michaelbel/movies/ui/preview/DeviceUserPreviews.kt @@ -0,0 +1,26 @@ +package org.michaelbel.movies.ui.preview + +import android.content.res.Configuration +import androidx.compose.ui.tooling.preview.Preview + +@Preview( + name = "Day", + uiMode = Configuration.UI_MODE_NIGHT_NO +) +@Preview( + name = "Night", + uiMode = Configuration.UI_MODE_NIGHT_YES +) +@Preview( + name = "Day Landscape", + uiMode = Configuration.UI_MODE_NIGHT_NO, + widthDp = 800, + heightDp = 360 +) +@Preview( + name = "Night Landscape", + uiMode = Configuration.UI_MODE_NIGHT_YES, + widthDp = 800, + heightDp = 360 +) +annotation class DeviceUserPreviews \ No newline at end of file diff --git a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt index 24e006063..5b5757fbe 100644 --- a/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt +++ b/feature/auth-impl/src/main/kotlin/org/michaelbel/movies/auth/ui/AuthScreenContent.kt @@ -5,13 +5,16 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Icon @@ -23,7 +26,6 @@ import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier @@ -85,9 +87,10 @@ internal fun AuthScreenContent( val toolbarColor: Int = MaterialTheme.colorScheme.primary.toArgb() val focusManager: FocusManager = LocalFocusManager.current + val scrollState: ScrollState = rememberScrollState() - var username: String by remember { mutableStateOf("") } - var password: String by remember { mutableStateOf("") } + var username: String by rememberSaveable { mutableStateOf("") } + var password: String by rememberSaveable { mutableStateOf("") } var passwordVisible: Boolean by rememberSaveable { mutableStateOf(false) } ConstraintLayout( @@ -98,6 +101,7 @@ internal fun AuthScreenContent( color = MaterialTheme.colorScheme.primaryContainer, shape = MaterialTheme.shapes.small ) + .verticalScroll(scrollState) ) { val ( toolbar, diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt index 8bd0ca43a..784c4cdd0 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsScreenContent.kt @@ -1,13 +1,16 @@ package org.michaelbel.movies.details.ui import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import java.net.UnknownHostException @@ -50,18 +53,22 @@ private fun DetailsScreenContent( onRetry: () -> Unit, modifier: Modifier = Modifier ) { + val topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + if (networkStatus.isAvailable && detailsState.isFailure && detailsState.throwable is UnknownHostException) { onRetry() } Scaffold( - modifier = modifier, + modifier = modifier + .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), topBar = { DetailsToolbar( movieTitle = detailsState.toolbarTitle, movieUrl = detailsState.movieUrl, onNavigationIconClick = onBackClick, - modifier = Modifier.statusBarsPadding() + topAppBarScrollBehavior = topAppBarScrollBehavior, + modifier = Modifier.fillMaxWidth() ) }, containerColor = MaterialTheme.colorScheme.primaryContainer diff --git a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt index 9e52fe9f5..627c6e3e1 100644 --- a/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt +++ b/feature/details-impl/src/main/kotlin/org/michaelbel/movies/details/ui/DetailsToolbar.kt @@ -9,9 +9,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter @@ -25,6 +25,7 @@ fun DetailsToolbar( movieTitle: String, movieUrl: String?, onNavigationIconClick: () -> Unit, + topAppBarScrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier ) { TopAppBar( @@ -65,8 +66,10 @@ fun DetailsToolbar( } }, colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent - ) + containerColor = MaterialTheme.colorScheme.primaryContainer, + scrolledContainerColor = MaterialTheme.colorScheme.inversePrimary + ), + scrollBehavior = topAppBarScrollBehavior ) } @@ -80,6 +83,7 @@ private fun DetailsToolbarPreview( movieTitle = title, movieUrl = null, onNavigationIconClick = {}, + topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), modifier = Modifier.statusBarsPadding() ) } diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/ConfigurationKtx.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/ConfigurationKtx.kt new file mode 100644 index 000000000..81ef773dc --- /dev/null +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ktx/ConfigurationKtx.kt @@ -0,0 +1,17 @@ +package org.michaelbel.movies.feed.ktx + +import android.content.res.Configuration +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalConfiguration + +private const val FEED_GRID_PORTRAIT_COLUMNS_COUNT = 2 +private const val FEED_GRID_LANDSCAPE_COLUMNS_COUNT = 4 + +val isPortrait: Boolean + @Composable get() { + val configuration = LocalConfiguration.current + return configuration.orientation == Configuration.ORIENTATION_PORTRAIT + } + +val gridColumnsCount: Int + @Composable get() = if (isPortrait) FEED_GRID_PORTRAIT_COLUMNS_COUNT else FEED_GRID_LANDSCAPE_COLUMNS_COUNT \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellLoading.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellLoading.kt deleted file mode 100644 index 741ff5318..000000000 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellLoading.kt +++ /dev/null @@ -1,62 +0,0 @@ -package org.michaelbel.movies.feed.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.ui.placeholder.PlaceholderHighlight -import org.michaelbel.movies.ui.placeholder.material3.fade -import org.michaelbel.movies.ui.placeholder.material3.placeholder -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun FeedCellLoading( - modifier: Modifier = Modifier, - paddingValues: PaddingValues = PaddingValues(), -) { - LazyColumn( - modifier = modifier, - contentPadding = paddingValues, - userScrollEnabled = false - ) { - items(MovieResponse.DEFAULT_PAGE_SIZE.div(2)) { - FeedCellMovieBox( - movie = MovieDb.Empty, - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = 8.dp, - vertical = 4.dp - ) - .placeholder( - visible = true, - color = MaterialTheme.colorScheme.inversePrimary, - shape = MaterialTheme.shapes.small, - highlight = PlaceholderHighlight.fade() - ) - ) - } - } -} - -@Composable -@DevicePreviews -private fun FeedCellLoadingPreview() { - MoviesTheme { - FeedCellLoading( - modifier = Modifier - .fillMaxSize() - .padding(top = 4.dp) - .background(MaterialTheme.colorScheme.background) - ) - } -} \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellMovieBox.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellMovieBox.kt index 152878c35..51241934b 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellMovieBox.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedCellMovieBox.kt @@ -23,8 +23,8 @@ import androidx.constraintlayout.compose.ConstraintLayout import androidx.constraintlayout.compose.Dimension import coil.compose.AsyncImage import coil.request.ImageRequest -import org.michaelbel.movies.network.formatBackdropImage import org.michaelbel.movies.feed_impl.R +import org.michaelbel.movies.network.formatBackdropImage import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.ui.ktx.context import org.michaelbel.movies.ui.ktx.isErrorOrEmpty @@ -35,7 +35,8 @@ import org.michaelbel.movies.ui.theme.MoviesTheme @Composable fun FeedCellMovieBox( movie: MovieDb, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + maxLines: Int = 10 ) { var isNoImageVisible: Boolean by remember { mutableStateOf(false) } @@ -96,7 +97,7 @@ fun FeedCellMovieBox( end.linkTo(parent.end, 16.dp) bottom.linkTo(parent.bottom, 16.dp) }, - maxLines = 10, + maxLines = maxLines, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyLarge.copy( color = MaterialTheme.colorScheme.onPrimaryContainer diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedContent.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedContent.kt index 42ac7cb3c..57b414a79 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedContent.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedContent.kt @@ -9,6 +9,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells @@ -21,6 +24,8 @@ import androidx.paging.compose.LazyPagingItems import androidx.paging.compose.itemContentType import androidx.paging.compose.itemKey import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.feed.ktx.gridColumnsCount +import org.michaelbel.movies.feed.ktx.isPortrait import org.michaelbel.movies.network.isTmdbApiKeyEmpty import org.michaelbel.movies.persistence.database.entity.MovieDb import org.michaelbel.movies.ui.ktx.isNotEmpty @@ -29,129 +34,235 @@ import org.michaelbel.movies.ui.ktx.isPagingLoading @Composable fun FeedContent( - currentFeedView: FeedView, + feedView: FeedView, lazyListState: LazyListState, + lazyGridState: LazyGridState, lazyStaggeredGridState: LazyStaggeredGridState, pagingItems: LazyPagingItems, onMovieClick: (Int) -> Unit, contentPadding: PaddingValues, - modifier: Modifier = Modifier, + modifier: Modifier = Modifier ) { - when (currentFeedView) { + when (feedView) { is FeedView.FeedList -> { - LazyColumn( - modifier = modifier.padding(top = 4.dp), - state = lazyListState, - contentPadding = contentPadding - ) { - items( - count = pagingItems.itemCount, - key = pagingItems.itemKey(), - contentType = pagingItems.itemContentType() - ) { index -> - val movieDb: MovieDb? = pagingItems[index] - if (movieDb != null) { - FeedCellMovieBox( - movie = movieDb, + if (isPortrait) { + FeedContentColumn( + lazyListState = lazyListState, + pagingItems = pagingItems, + onMovieClick = onMovieClick, + contentPadding = contentPadding, + modifier = modifier + ) + } else { + FeedContentGrid( + lazyGridState = lazyGridState, + pagingItems = pagingItems, + onMovieClick = onMovieClick, + contentPadding = contentPadding + ) + } + } + is FeedView.FeedGrid -> { + FeedContentStaggeredGrid( + lazyStaggeredGridState = lazyStaggeredGridState, + pagingItems = pagingItems, + onMovieClick = onMovieClick, + contentPadding = contentPadding, + modifier = modifier + ) + } + } +} + +@Composable +private fun FeedContentColumn( + lazyListState: LazyListState, + pagingItems: LazyPagingItems, + onMovieClick: (Int) -> Unit, + contentPadding: PaddingValues, + modifier: Modifier = Modifier +) { + LazyColumn( + modifier = modifier.padding(top = 4.dp), + state = lazyListState, + contentPadding = contentPadding + ) { + items( + count = pagingItems.itemCount, + key = pagingItems.itemKey(), + contentType = pagingItems.itemContentType() + ) { index -> + val movieDb: MovieDb? = pagingItems[index] + if (movieDb != null) { + FeedCellMovieBox( + movie = movieDb, + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 8.dp, + vertical = 4.dp + ) + .clip(MaterialTheme.shapes.small) + .background(MaterialTheme.colorScheme.inversePrimary) + .clickable { + onMovieClick(movieDb.movieId) + } + ) + } + } + if (isTmdbApiKeyEmpty && pagingItems.isNotEmpty) { + item { + FeedApiKeyBox( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 16.dp) + ) + } + } + pagingItems.apply { + when { + isPagingLoading -> { + item { + FeedLoadingBox( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + ) + } + } + isPagingFailure -> { + item { + FeedErrorBox( modifier = Modifier .fillMaxWidth() - .padding( - horizontal = 8.dp, - vertical = 4.dp - ) + .height(80.dp) + .padding(start = 8.dp, top = 4.dp, end = 8.dp) .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.inversePrimary) - .clickable { - onMovieClick(movieDb.movieId) - } + .clickable { retry() } ) } } - if (isTmdbApiKeyEmpty && pagingItems.isNotEmpty) { + } + } + } +} + +@Composable +private fun FeedContentGrid( + lazyGridState: LazyGridState, + pagingItems: LazyPagingItems, + onMovieClick: (Int) -> Unit, + contentPadding: PaddingValues, + modifier: Modifier = Modifier +) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = modifier.padding(start = 8.dp, top = 4.dp, end = 8.dp), + state = lazyGridState, + contentPadding = contentPadding, + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + items( + count = pagingItems.itemCount, + key = pagingItems.itemKey(), + contentType = pagingItems.itemContentType() + ) { index -> + val movieDb: MovieDb? = pagingItems[index] + if (movieDb != null) { + FeedCellMovieBox( + movie = movieDb, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .clip(MaterialTheme.shapes.small) + .background(MaterialTheme.colorScheme.inversePrimary) + .clickable { onMovieClick(movieDb.movieId) } + ) + } + } + pagingItems.apply { + when { + isPagingLoading -> { item { - FeedApiKeyBox( + FeedLoadingBox( modifier = Modifier .fillMaxWidth() - .padding(vertical = 16.dp) + .height(80.dp) + ) } } - pagingItems.apply { - when { - isPagingLoading -> { - item { - FeedLoadingBox( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - ) - } - } - isPagingFailure -> { - item { - FeedErrorBox( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - .padding(start = 8.dp, top = 4.dp, end = 8.dp) - .clip(MaterialTheme.shapes.small) - .clickable { retry() } - ) - } - } + isPagingFailure -> { + item { + FeedErrorBox( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + .clip(MaterialTheme.shapes.small) + .clickable { retry() } + ) } } } } - is FeedView.FeedGrid -> { - LazyVerticalStaggeredGrid( - columns = StaggeredGridCells.Fixed(2), - modifier = modifier.padding(start = 8.dp, top = 8.dp, end = 8.dp), - state = lazyStaggeredGridState, - contentPadding = contentPadding, - verticalItemSpacing = 8.dp, - horizontalArrangement = Arrangement.spacedBy(8.dp), - ) { - items( - count = pagingItems.itemCount, - key = pagingItems.itemKey(), - contentType = pagingItems.itemContentType() - ) { index -> - val movieDb: MovieDb? = pagingItems[index] - if (movieDb != null) { - FeedGridMovieBox( - movie = movieDb, + } +} + +@Composable +private fun FeedContentStaggeredGrid( + lazyStaggeredGridState: LazyStaggeredGridState, + pagingItems: LazyPagingItems, + onMovieClick: (Int) -> Unit, + contentPadding: PaddingValues, + modifier: Modifier = Modifier +) { + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Fixed(gridColumnsCount), + modifier = modifier.padding(start = 8.dp, top = 8.dp, end = 8.dp), + state = lazyStaggeredGridState, + contentPadding = contentPadding, + verticalItemSpacing = 8.dp, + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + items( + count = pagingItems.itemCount, + key = pagingItems.itemKey(), + contentType = pagingItems.itemContentType() + ) { index -> + val movieDb: MovieDb? = pagingItems[index] + if (movieDb != null) { + FeedGridMovieBox( + movie = movieDb, + modifier = Modifier + .fillMaxWidth() + .clip(MaterialTheme.shapes.small) + .background(MaterialTheme.colorScheme.inversePrimary) + .clickable { onMovieClick(movieDb.movieId) } + ) + } + } + pagingItems.apply { + when { + isPagingLoading -> { + item { + FeedLoadingBox( modifier = Modifier .fillMaxWidth() - .clip(MaterialTheme.shapes.small) - .background(MaterialTheme.colorScheme.inversePrimary) - .clickable { onMovieClick(movieDb.movieId) } + .height(80.dp) + ) } } - pagingItems.apply { - when { - isPagingLoading -> { - item { - FeedLoadingBox( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - - ) - } - } - isPagingFailure -> { - item { - FeedErrorBox( - modifier = Modifier - .fillMaxWidth() - .height(80.dp) - .clip(MaterialTheme.shapes.small) - .clickable { retry() } - ) - } - } + isPagingFailure -> { + item { + FeedErrorBox( + modifier = Modifier + .fillMaxWidth() + .height(80.dp) + .clip(MaterialTheme.shapes.small) + .clickable { retry() } + ) } } } diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedGridLoading.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedGridLoading.kt deleted file mode 100644 index 1e2d8eec0..000000000 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedGridLoading.kt +++ /dev/null @@ -1,63 +0,0 @@ -package org.michaelbel.movies.feed.ui - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid -import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells -import androidx.compose.material3.MaterialTheme -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import org.michaelbel.movies.network.model.MovieResponse -import org.michaelbel.movies.persistence.database.entity.MovieDb -import org.michaelbel.movies.ui.placeholder.PlaceholderHighlight -import org.michaelbel.movies.ui.placeholder.material3.fade -import org.michaelbel.movies.ui.placeholder.placeholder -import org.michaelbel.movies.ui.preview.DevicePreviews -import org.michaelbel.movies.ui.theme.MoviesTheme - -@Composable -fun FeedGridLoading( - modifier: Modifier = Modifier, - paddingValues: PaddingValues = PaddingValues(), -) { - LazyVerticalStaggeredGrid( - columns = StaggeredGridCells.Fixed(2), - modifier = modifier, - contentPadding = paddingValues, - verticalItemSpacing = 8.dp, - horizontalArrangement = Arrangement.spacedBy(8.dp), - userScrollEnabled = false - ) { - items(MovieResponse.DEFAULT_PAGE_SIZE.div(2)) { - FeedGridMovieBox( - movie = MovieDb.Empty, - modifier = Modifier - .fillMaxWidth() - .placeholder( - visible = true, - color = MaterialTheme.colorScheme.inversePrimary, - shape = MaterialTheme.shapes.small, - highlight = PlaceholderHighlight.fade() - ) - ) - } - } -} - -@Composable -@DevicePreviews -private fun FeedGridLoadingPreview() { - MoviesTheme { - FeedGridLoading( - modifier = Modifier - .fillMaxSize() - .padding(start = 8.dp, top = 8.dp, end = 8.dp) - .background(MaterialTheme.colorScheme.background) - ) - } -} \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoading.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoading.kt new file mode 100644 index 000000000..18c8b0332 --- /dev/null +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedLoading.kt @@ -0,0 +1,182 @@ +package org.michaelbel.movies.feed.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.grid.GridCells +import androidx.compose.foundation.lazy.grid.LazyVerticalGrid +import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid +import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.michaelbel.movies.common.appearance.FeedView +import org.michaelbel.movies.feed.ktx.gridColumnsCount +import org.michaelbel.movies.feed.ktx.isPortrait +import org.michaelbel.movies.network.model.MovieResponse +import org.michaelbel.movies.persistence.database.entity.MovieDb +import org.michaelbel.movies.ui.placeholder.PlaceholderHighlight +import org.michaelbel.movies.ui.placeholder.material3.fade +import org.michaelbel.movies.ui.placeholder.placeholder +import org.michaelbel.movies.ui.preview.DeviceLandscapePreviews +import org.michaelbel.movies.ui.preview.DevicePreviews +import org.michaelbel.movies.ui.preview.DeviceUserPreviews +import org.michaelbel.movies.ui.theme.MoviesTheme + +@Composable +fun FeedLoading( + feedView: FeedView, + modifier: Modifier = Modifier, + paddingValues: PaddingValues = PaddingValues() +) { + when (feedView) { + is FeedView.FeedList -> { + if (isPortrait) { + FeedLoadingColumn( + modifier = modifier, + paddingValues = paddingValues + ) + } else { + FeedLoadingGrid( + modifier = modifier, + paddingValues = paddingValues + ) + } + } + is FeedView.FeedGrid -> { + FeedLoadingStaggeredGrid( + modifier = modifier, + paddingValues = paddingValues + ) + } + } +} + +@Composable +private fun FeedLoadingColumn( + modifier: Modifier = Modifier, + paddingValues: PaddingValues = PaddingValues() +) { + LazyColumn( + modifier = modifier.padding(top = 4.dp), + contentPadding = paddingValues, + userScrollEnabled = false + ) { + items(MovieResponse.DEFAULT_PAGE_SIZE.div(2)) { + FeedCellMovieBox( + movie = MovieDb.Empty, + modifier = Modifier + .fillMaxWidth() + .padding( + horizontal = 8.dp, + vertical = 4.dp + ) + .placeholder( + visible = true, + color = MaterialTheme.colorScheme.inversePrimary, + shape = MaterialTheme.shapes.small, + highlight = PlaceholderHighlight.fade() + ) + ) + } + } +} + +@Composable +private fun FeedLoadingGrid( + modifier: Modifier = Modifier, + paddingValues: PaddingValues = PaddingValues() +) { + LazyVerticalGrid( + columns = GridCells.Fixed(2), + modifier = modifier.padding(start = 8.dp, top = 4.dp, end = 8.dp), + contentPadding = paddingValues, + horizontalArrangement = Arrangement.spacedBy(8.dp), + userScrollEnabled = false + ) { + items(MovieResponse.DEFAULT_PAGE_SIZE.div(2)) { + FeedCellMovieBox( + movie = MovieDb.Empty, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + .placeholder( + visible = true, + color = MaterialTheme.colorScheme.inversePrimary, + shape = MaterialTheme.shapes.small, + highlight = PlaceholderHighlight.fade() + ) + ) + } + } +} + +@Composable +private fun FeedLoadingStaggeredGrid( + modifier: Modifier = Modifier, + paddingValues: PaddingValues = PaddingValues() +) { + LazyVerticalStaggeredGrid( + columns = StaggeredGridCells.Fixed(gridColumnsCount), + modifier = modifier.padding(start = 8.dp, top = 8.dp, end = 8.dp), + contentPadding = paddingValues, + verticalItemSpacing = 8.dp, + horizontalArrangement = Arrangement.spacedBy(8.dp), + userScrollEnabled = false + ) { + items(MovieResponse.DEFAULT_PAGE_SIZE.div(2)) { + FeedGridMovieBox( + movie = MovieDb.Empty, + modifier = Modifier + .fillMaxWidth() + .placeholder( + visible = true, + color = MaterialTheme.colorScheme.inversePrimary, + shape = MaterialTheme.shapes.small, + highlight = PlaceholderHighlight.fade() + ) + ) + } + } +} + +@Composable +@DevicePreviews +private fun FeedLoadingColumnPreview() { + MoviesTheme { + FeedLoadingColumn( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) + } +} + +@Composable +@DeviceLandscapePreviews +private fun FeedLoadingGridPreview() { + MoviesTheme { + FeedLoadingGrid( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) + } +} + +@Composable +@DeviceUserPreviews +private fun FeedLoadingStaggeredGridPreview() { + MoviesTheme { + FeedLoadingStaggeredGrid( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + ) + } +} \ No newline at end of file diff --git a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt index 7e3471752..13abda3e0 100644 --- a/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt +++ b/feature/feed-impl/src/main/kotlin/org/michaelbel/movies/feed/ui/FeedScreenContent.kt @@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState @@ -117,6 +119,7 @@ private fun FeedScreenContent( val context: Context = LocalContext.current val scope: CoroutineScope = rememberCoroutineScope() val lazyListState: LazyListState = rememberLazyListState() + val lazyGridState: LazyGridState = rememberLazyGridState() val lazyStaggeredGridState: LazyStaggeredGridState = rememberLazyStaggeredGridState() val snackbarHostState: SnackbarHostState = remember { SnackbarHostState() } val notificationBottomSheetScaffoldState = rememberBottomSheetScaffoldState( @@ -146,6 +149,9 @@ private fun FeedScreenContent( scope.launch { lazyListState.animateScrollToItem(0) } + scope.launch { + lazyGridState.animateScrollToItem(0) + } scope.launch { lazyStaggeredGridState.animateScrollToItem(0) } @@ -226,24 +232,11 @@ private fun FeedScreenContent( ) { paddingValues -> when { pagingItems.isLoading -> { - when (currentFeedView) { - is FeedView.FeedList -> { - FeedCellLoading( - modifier = Modifier - .fillMaxSize() - .padding(top = 4.dp), - paddingValues = paddingValues - ) - } - is FeedView.FeedGrid -> { - FeedGridLoading( - modifier = Modifier - .fillMaxSize() - .padding(start = 8.dp, top = 8.dp, end = 8.dp), - paddingValues = paddingValues - ) - } - } + FeedLoading( + feedView = currentFeedView, + modifier = Modifier.fillMaxSize(), + paddingValues = paddingValues + ) } pagingItems.isFailure -> { FeedFailure( @@ -262,8 +255,9 @@ private fun FeedScreenContent( } else -> { FeedContent( - currentFeedView = currentFeedView, + feedView = currentFeedView, lazyListState = lazyListState, + lazyGridState = lazyGridState, lazyStaggeredGridState = lazyStaggeredGridState, pagingItems = pagingItems, onMovieClick = onNavigateToDetails, diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceDialog.kt index a70ebdcbb..e14517222 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceDialog.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsAppearanceDialog.kt @@ -1,12 +1,15 @@ package org.michaelbel.movies.settings.ui +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon @@ -95,8 +98,10 @@ private fun SettingAppearanceDialogContent( onFeedViewSelect: (FeedView) -> Unit, modifier: Modifier = Modifier ) { + val scrollState: ScrollState = rememberScrollState() + Column( - modifier = modifier + modifier = modifier.verticalScroll(scrollState) ) { FeedView.VALUES.forEach { feedView: FeedView -> Row( diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialog.kt index 23753e7ea..639ff12e5 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialog.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsLanguageDialog.kt @@ -1,12 +1,15 @@ package org.michaelbel.movies.settings.ui +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon @@ -95,8 +98,10 @@ private fun SettingLanguageDialogContent( onLanguageSelect: (AppLanguage) -> Unit, modifier: Modifier = Modifier ) { + val scrollState: ScrollState = rememberScrollState() + Column( - modifier = modifier + modifier = modifier.verticalScroll(scrollState) ) { AppLanguage.VALUES.forEach { language: AppLanguage -> Row( diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListDialog.kt index 1038968c2..635fd0a70 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListDialog.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsMovieListDialog.kt @@ -1,12 +1,15 @@ package org.michaelbel.movies.settings.ui +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon @@ -101,9 +104,10 @@ private fun SettingMovieListDialogContent( MovieList.TopRated, MovieList.Upcoming ) + val scrollState: ScrollState = rememberScrollState() Column( - modifier = modifier + modifier = modifier.verticalScroll(scrollState) ) { movieLists.forEach { movieList -> Row( diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt index d9db47960..26260f49d 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsScreenContent.kt @@ -4,24 +4,28 @@ import android.app.Activity import android.content.Context import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.SnackbarDuration import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarResult +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.LayoutDirection @@ -44,6 +48,7 @@ import org.michaelbel.movies.settings.ktx.iconSnackbarText import org.michaelbel.movies.settings_impl.BuildConfig import org.michaelbel.movies.settings_impl.R import org.michaelbel.movies.ui.ktx.appNotificationSettingsIntent +import org.michaelbel.movies.ui.ktx.clickableWithoutRipple import org.michaelbel.movies.ui.R as UiR @Composable @@ -119,6 +124,8 @@ private fun SettingsScreenContent( val snackbarHostState: SnackbarHostState = remember { SnackbarHostState() } val reviewManager: ReviewManager = rememberReviewManager() val reviewInfo: ReviewInfo? = rememberReviewTask(reviewManager) + val topAppBarScrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() + val lazyListState: LazyListState = rememberLazyListState() val resultContract = rememberLauncherForActivityResult( ActivityResultContracts.StartActivityForResult() @@ -146,6 +153,12 @@ private fun SettingsScreenContent( } } + val onScrollToTop: () -> Unit = { + scope.launch { + lazyListState.animateScrollToItem(0) + } + } + fun onLaunchReviewFlow() { when { !isPlayServicesAvailable -> { @@ -163,10 +176,14 @@ private fun SettingsScreenContent( } Scaffold( - modifier = modifier, + modifier = modifier + .nestedScroll(topAppBarScrollBehavior.nestedScrollConnection), topBar = { SettingsToolbar( - modifier = Modifier.statusBarsPadding(), + topAppBarScrollBehavior = topAppBarScrollBehavior, + modifier = Modifier + .fillMaxWidth() + .clickableWithoutRipple { onScrollToTop() }, onNavigationIconClick = onBackClick ) }, @@ -176,6 +193,7 @@ private fun SettingsScreenContent( modifier = Modifier .navigationBarsPadding() .fillMaxWidth() + .background(MaterialTheme.colorScheme.primaryContainer) ) }, snackbarHost = { @@ -185,97 +203,110 @@ private fun SettingsScreenContent( }, containerColor = MaterialTheme.colorScheme.primaryContainer ) { paddingValues -> - Column( - modifier = Modifier.padding(paddingValues) + LazyColumn( + modifier = Modifier.navigationBarsPadding(), + state = lazyListState, + contentPadding = paddingValues ) { - SettingsLanguageBox( - currentLanguage = currentLanguage, - onLanguageSelect = onLanguageSelect, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - ) - - SettingsThemeBox( - currentTheme = currentTheme, - onThemeSelect = onThemeSelect, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - ) - - SettingsAppearanceBox( - currentFeedView = currentFeedView, - onFeedViewSelect = onFeedViewSelect, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - ) - - SettingsMovieListBox( - currentMovieList = currentMovieList, - onMovieListSelect = onMovieListSelect, - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - ) - - if (isDynamicColorsFeatureEnabled) { - SettingsDynamicColorsBox( - isDynamicColorsEnabled = dynamicColors, + item { + SettingsLanguageBox( + currentLanguage = currentLanguage, + onLanguageSelect = onLanguageSelect, modifier = Modifier .fillMaxWidth() .height(52.dp) - .clickable { - onSetDynamicColors(!dynamicColors) - } ) } - - if (isRtlFeatureEnabled) { - SettingsRtlBox( - isRtlEnabled = isRtlEnabled, + item { + SettingsThemeBox( + currentTheme = currentTheme, + onThemeSelect = onThemeSelect, modifier = Modifier .fillMaxWidth() .height(52.dp) - .clickable { - onEnableRtlChanged(!isRtlEnabled) - } ) } - - if (isPostNotificationsFeatureEnabled) { - SettingsPostNotificationsBox( + item { + SettingsAppearanceBox( + currentFeedView = currentFeedView, + onFeedViewSelect = onFeedViewSelect, modifier = Modifier .fillMaxWidth() - .height(52.dp), - onShowPermissionSnackbar = onShowPermissionSnackbar + .height(52.dp) ) } - - SettingsReviewBox( - modifier = Modifier - .fillMaxWidth() - .height(52.dp) - .clickable { - onLaunchReviewFlow() - } - ) - - if (BuildConfig.DEBUG) { - SettingsNetworkRequestDelayBox( - delay = networkRequestDelay, - onDelayChangeFinished = onDelayChangeFinished, + item { + SettingsMovieListBox( + currentMovieList = currentMovieList, + onMovieListSelect = onMovieListSelect, + modifier = Modifier + .fillMaxWidth() + .height(52.dp) + ) + } + item { + if (isDynamicColorsFeatureEnabled) { + SettingsDynamicColorsBox( + isDynamicColorsEnabled = dynamicColors, + modifier = Modifier + .fillMaxWidth() + .height(52.dp) + .clickable { + onSetDynamicColors(!dynamicColors) + } + ) + } + } + item { + if (isRtlFeatureEnabled) { + SettingsRtlBox( + isRtlEnabled = isRtlEnabled, + modifier = Modifier + .fillMaxWidth() + .height(52.dp) + .clickable { + onEnableRtlChanged(!isRtlEnabled) + } + ) + } + } + item { + if (isPostNotificationsFeatureEnabled) { + SettingsPostNotificationsBox( + modifier = Modifier + .fillMaxWidth() + .height(52.dp), + onShowPermissionSnackbar = onShowPermissionSnackbar + ) + } + } + item { + SettingsReviewBox( + modifier = Modifier + .fillMaxWidth() + .height(52.dp) + .clickable { + onLaunchReviewFlow() + } + ) + } + item { + if (BuildConfig.DEBUG) { + SettingsNetworkRequestDelayBox( + delay = networkRequestDelay, + onDelayChangeFinished = onDelayChangeFinished, + modifier = Modifier.fillMaxWidth() + ) + } + } + item { + SettingsAppIconBox( + onAppIconChanged = { iconAlias -> + onShowSnackbar(context.getString(R.string.settings_app_launcher_icon_changed_to, iconAlias.iconSnackbarText(context))) + }, modifier = Modifier.fillMaxWidth() ) } - - SettingsAppIconBox( - onAppIconChanged = { iconAlias -> - onShowSnackbar(context.getString(R.string.settings_app_launcher_icon_changed_to, iconAlias.iconSnackbarText(context))) - }, - modifier = Modifier.fillMaxWidth() - ) } } } \ No newline at end of file diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt index 66137cf79..4bf0afd9b 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsThemeDialog.kt @@ -1,12 +1,15 @@ package org.michaelbel.movies.settings.ui +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.Text import androidx.compose.material3.AlertDialog import androidx.compose.material3.Icon @@ -96,8 +99,10 @@ private fun SettingThemeDialogContent( onThemeSelect: (AppTheme) -> Unit, modifier: Modifier = Modifier ) { + val scrollState: ScrollState = rememberScrollState() + Column( - modifier = modifier + modifier = modifier.verticalScroll(scrollState) ) { AppTheme.VALUES.forEach { theme: AppTheme -> Row( diff --git a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt index 8bd9ebd10..5d49a1bd1 100644 --- a/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt +++ b/feature/settings-impl/src/main/kotlin/org/michaelbel/movies/settings/ui/SettingsToolbar.kt @@ -7,9 +7,9 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.material3.TopAppBarScrollBehavior import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource @@ -21,8 +21,9 @@ import org.michaelbel.movies.ui.theme.MoviesTheme @Composable internal fun SettingsToolbar( + topAppBarScrollBehavior: TopAppBarScrollBehavior, modifier: Modifier = Modifier, - onNavigationIconClick: () -> Unit + onNavigationIconClick: () -> Unit, ) { TopAppBar( title = { @@ -51,8 +52,10 @@ internal fun SettingsToolbar( } }, colors = TopAppBarDefaults.topAppBarColors( - containerColor = Color.Transparent - ) + containerColor = MaterialTheme.colorScheme.primaryContainer, + scrolledContainerColor = MaterialTheme.colorScheme.inversePrimary + ), + scrollBehavior = topAppBarScrollBehavior ) } @@ -62,7 +65,8 @@ private fun SettingsToolbarPreview() { MoviesTheme { SettingsToolbar( modifier = Modifier.statusBarsPadding(), - onNavigationIconClick = {} + onNavigationIconClick = {}, + topAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior() ) } } \ No newline at end of file diff --git a/readme.md b/readme.md index 1564b09e7..a6c4f6ead 100644 --- a/readme.md +++ b/readme.md @@ -99,13 +99,13 @@ TMDB_API_KEY=your_own_tmdb_api_key - [x] [Predictive Back Gesture](https://d.android.com/guide/navigation/custom-back/predictive-back-gesture) - [x] [Codebeat Automated Code Review](https://codebeat.co/projects/github-com-michaelbel-movies-develop) - [x] [Codacy Static Code Analysis](https://app.codacy.com/gh/michaelbel/movies/dashboard) +- [x] [Support Landscape Orientation](https://d.android.com/guide/topics/large-screens/support-different-screen-sizes) - [ ] [Unit Tests](https://d.android.com/training/testing/local-tests) - [ ] [UI Tests](https://d.android.com/training/testing/instrumented-tests/ui-tests) - [ ] [Baseline Profiles](https://d.android.com/topic/performance/baselineprofiles/overview) - [ ] [Tablet and large screen support](https://d.android.com/about/versions/13/features/large-screens) - [ ] OAuth - [ ] [Animations](https://d.android.com/develop/ui/views/animations) -- [ ] Landscape Orientation - [ ] [Support layout mirroring](https://d.android.com/training/basics/supporting-devices/languages#SupportLayoutMirroring) - [ ] Upload Bundle to Google Play Console - [ ] [ExoPlayer](https://d.android.com/guide/topics/media/exoplayer)