Skip to content

Commit

Permalink
add match cards
Browse files Browse the repository at this point in the history
  • Loading branch information
stslex committed Dec 1, 2023
1 parent cf66d91 commit dc9b7f5
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 52 deletions.
4 changes: 2 additions & 2 deletions composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import cafe.adriel.voyager.navigator.tab.CurrentTab
import cafe.adriel.voyager.navigator.tab.TabNavigator
import com.stslex.core.ui.mvi.setupNavigator
import main_screen.bottom_nav_bar.BottomNavigationBar
import main_screen.bottom_nav_bar.FeedTab
import main_screen.bottom_nav_bar.BottomNavigationTabs

object MainScreen : Screen {

Expand All @@ -20,7 +20,7 @@ object MainScreen : Screen {
setupNavigator()

TabNavigator(
tab = FeedTab,
tab = BottomNavigationTabs.MATCH_FEED.tab,
) { tabNavigator ->
Scaffold(
content = { paddingValues ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,10 @@ object AppDimension {
val medium = 10.dp
val large = 15.dp
}

object Elevation {
val smallest = 2.dp
val small = 4.dp
val medium = 8.dp
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import com.stslex.feature.match_feed.ui.components.FeedScreenContent
import com.stslex.feature.match_feed.ui.components.FeedScreenError
import com.stslex.feature.match_feed.ui.components.FeedScreenLoading
import com.stslex.feature.match_feed.ui.components.MatchFeedScreenContent
import com.stslex.feature.match_feed.ui.components.MatchFeedScreenError
import com.stslex.feature.match_feed.ui.components.MatchFeedScreenLoading
import com.stslex.feature.match_feed.ui.store.MatchFeedStore
import com.stslex.feature.match_feed.ui.store.MatchFeedStoreComponent.Action
import com.stslex.feature.match_feed.ui.store.MatchFeedStoreComponent.Event.ErrorSnackBar
Expand Down Expand Up @@ -55,15 +55,15 @@ private fun MatchFeedScreen(
modifier = modifier.fillMaxSize()
) {
when (val screenState = state.screen) {
is ScreenState.Content -> FeedScreenContent(
is ScreenState.Content -> MatchFeedScreenContent(
loadMore = remember { { sendAction(Action.LoadFilms) } },
films = state.films,
screenState = screenState,
onFilmClick = remember { { sendAction(Action.FilmClick(it)) } },
)

is ScreenState.Error -> FeedScreenError(screenState.message)
ScreenState.Loading -> FeedScreenLoading()
is ScreenState.Error -> MatchFeedScreenError(screenState.message)
ScreenState.Loading -> MatchFeedScreenLoading()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
package com.stslex.feature.match_feed.ui.components

import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.gestures.snapping.rememberSnapFlingBehavior
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.stslex.core.core.Logger
import com.stslex.feature.match_feed.ui.model.FilmUi
Expand All @@ -20,8 +25,9 @@ import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull

@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class)
@Composable
internal fun FeedScreenContent(
internal fun MatchFeedScreenContent(
loadMore: () -> Unit,
films: ImmutableList<FilmUi>,
screenState: ScreenState.Content,
Expand All @@ -45,12 +51,16 @@ internal fun FeedScreenContent(
}
}
}

BoxWithConstraints {
val itemHeight = remember(maxHeight) { maxHeight / 3 }
val screenWidth = remember(maxWidth) { maxWidth }
LazyColumn(
modifier = modifier
.fillMaxSize(),
state = listState
state = listState,
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center,
flingBehavior = rememberSnapFlingBehavior(listState)
) {
items(
count = films.size,
Expand All @@ -61,10 +71,11 @@ internal fun FeedScreenContent(
) { index ->
val film = films.getOrNull(index)
if (film != null) {
FeedScreenFilmItem(
MatchFeedScreenFilmItem(
film = film,
itemHeight = itemHeight,
screenWidth = screenWidth,
onFilmClick = onFilmClick,
listState = listState,
)
}
}
Expand All @@ -77,7 +88,7 @@ internal fun FeedScreenContent(
) {
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = androidx.compose.ui.Alignment.Center
contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

@Composable
internal fun FeedScreenError(
internal fun MatchFeedScreenError(
message: String,
modifier: Modifier = Modifier
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,62 +1,133 @@
package com.stslex.feature.match_feed.ui.components

import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Warning
import androidx.compose.material3.ElevatedCard
import androidx.compose.material.rememberSwipeableState
import androidx.compose.material.swipeable
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import com.stslex.core.ui.base.image.NetworkImage
import com.stslex.core.ui.base.onClickDelay
import com.stslex.core.ui.theme.AppDimension
import com.stslex.feature.match_feed.ui.model.FilmUi
import kotlinx.collections.immutable.ImmutableList
import kotlin.math.abs

@OptIn(ExperimentalMaterial3Api::class)
enum class SwipeDirection {
LEFT,
RIGHT,
NONE
}

@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun FeedScreenFilmItem(
internal fun MatchFeedScreenFilmItem(
modifier: Modifier = Modifier,
itemHeight: Dp,
screenWidth: Dp,
film: FilmUi,
listState: LazyListState,
onFilmClick: (String) -> Unit,
) {
val posterWidth = remember(itemHeight) {
(itemHeight - AppDimension.Padding.medium * 2) / 4 * 3
val swipeableState = rememberSwipeableState(SwipeDirection.NONE)

val posterWidth by remember(screenWidth) {
derivedStateOf { screenWidth - AppDimension.Padding.large * 2 }
}
val posterHeight by remember(posterWidth) {
derivedStateOf { posterWidth * 1.5f }
}
val progress by remember {
derivedStateOf {
swipeableState.offset.value / screenWidth.value
}
}


ElevatedCard(
Box(
modifier = modifier
.fillMaxWidth()
.height(itemHeight)
.padding(AppDimension.Padding.medium),
shape = RoundedCornerShape(AppDimension.Radius.medium),
onClick = onClickDelay {
onFilmClick(film.uuid)
}
.graphicsLayer {
rotationZ = 15f * progress
translationX = swipeableState.offset.value
alpha = 1f - abs(progress * 0.5f)
}
.width(posterWidth)
.height(posterHeight)
.swipeable(
state = swipeableState,
anchors = mapOf(
-screenWidth.value to SwipeDirection.LEFT,
screenWidth.value to SwipeDirection.RIGHT,
0f to SwipeDirection.NONE,
),
orientation = Orientation.Horizontal,
)
.animateItemTop(
listState = listState,
key = film.uuid,
)
) {
MatchFeedScreenFilmItemContent(
modifier = Modifier.fillMaxSize(),
film = film,
onFilmClick = onFilmClick,
)
}
}

@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun MatchFeedScreenFilmItemContent(
modifier: Modifier = Modifier,
film: FilmUi,
onFilmClick: (String) -> Unit,
) {
Column(
modifier = modifier
.fillMaxSize(),
) {
Row(modifier = Modifier.fillMaxSize()) {
Box {
Text(
modifier = Modifier.align(Alignment.CenterHorizontally),
text = film.title,
style = MaterialTheme.typography.titleLarge,
textAlign = TextAlign.Center,
)
Spacer(modifier = Modifier.height(AppDimension.Padding.medium))
Card(
modifier = Modifier,
shape = RoundedCornerShape(AppDimension.Radius.medium),
onClick = onClickDelay {
onFilmClick(film.uuid)
},
) {
Box(modifier = Modifier.fillMaxSize()) {
FeedItemFilmPreview(
modifier = Modifier.width(posterWidth),
modifier = Modifier.fillMaxSize(),
url = film.poster,
description = film.title
)
Expand All @@ -72,23 +143,25 @@ internal fun FeedScreenFilmItem(
text = film.rate,
style = MaterialTheme.typography.titleSmall,
)
}

Spacer(modifier = Modifier.width(AppDimension.Padding.big))
Column {
Text(
text = film.title,
style = MaterialTheme.typography.titleLarge
)
Spacer(modifier = Modifier.height(AppDimension.Padding.medium))
FilmItemGenres(genres = film.genres)
Spacer(modifier = Modifier.height(AppDimension.Padding.big))
Text(
modifier = Modifier.fillMaxSize(),
text = film.description,
style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.width(AppDimension.Padding.big))
Column(
modifier = Modifier
.align(Alignment.BottomCenter)
.background(
color = MaterialTheme.colorScheme.background.copy(alpha = 0.7f),
)
.padding(AppDimension.Padding.medium)
) {
Spacer(modifier = Modifier.height(AppDimension.Padding.medium))
FilmItemGenres(genres = film.genres)
Spacer(modifier = Modifier.height(AppDimension.Padding.big))
Text(
text = film.description,
style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis,
maxLines = 4,
)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

@Composable
internal fun FeedScreenLoading(
internal fun MatchFeedScreenLoading(
modifier: Modifier = Modifier
) {
// TODO add loading screen
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.stslex.feature.match_feed.ui.components

import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.graphicsLayer
import kotlin.math.absoluteValue

fun Modifier.animateItemTop(
listState: LazyListState,
key: Any?,
valueKf: Float = 0.2f
): Modifier = graphicsLayer {
val position = listState.normalizedPositionTop(key)
val value = 1 - (position.absoluteValue * valueKf)
alpha = value
scaleX = value
scaleY = value
}

fun LazyListState.normalizedPositionTop(
key: Any?
): Float = with(layoutInfo) {
visibleItemsInfo.firstOrNull {
it.key == key
}?.let {
1 - (it.size - it.offset.toFloat()) / it.size
} ?: 0F
}

0 comments on commit dc9b7f5

Please sign in to comment.