diff --git a/composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt b/composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt index a9a52d6f..3fcfea45 100644 --- a/composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt +++ b/composeApp/src/commonMain/kotlin/main_screen/MainScreen.kt @@ -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 { @@ -20,7 +20,7 @@ object MainScreen : Screen { setupNavigator() TabNavigator( - tab = FeedTab, + tab = BottomNavigationTabs.MATCH_FEED.tab, ) { tabNavigator -> Scaffold( content = { paddingValues -> diff --git a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/theme/AppDimension.kt b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/theme/AppDimension.kt index 14433a20..e8629a45 100644 --- a/core/ui/src/commonMain/kotlin/com/stslex/core/ui/theme/AppDimension.kt +++ b/core/ui/src/commonMain/kotlin/com/stslex/core/ui/theme/AppDimension.kt @@ -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 + } } \ No newline at end of file diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/MatchFeedScreen.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/MatchFeedScreen.kt index bc0bf789..ca6393b4 100644 --- a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/MatchFeedScreen.kt +++ b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/MatchFeedScreen.kt @@ -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 @@ -55,15 +55,25 @@ 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)) } }, + onItemSwiped = remember { + { direction, id -> + sendAction( + Action.FilmSwiped( + direction = direction, + uuid = id + ) + ) + } + }, ) - is ScreenState.Error -> FeedScreenError(screenState.message) - ScreenState.Loading -> FeedScreenLoading() + is ScreenState.Error -> MatchFeedScreenError(screenState.message) + ScreenState.Loading -> MatchFeedScreenLoading() } } } \ No newline at end of file diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenContent.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenContent.kt deleted file mode 100644 index fac5a0d8..00000000 --- a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenContent.kt +++ /dev/null @@ -1,97 +0,0 @@ -package com.stslex.feature.match_feed.ui.components - -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.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.Modifier -import com.stslex.core.core.Logger -import com.stslex.feature.match_feed.ui.model.FilmUi -import com.stslex.feature.match_feed.ui.store.ScreenState -import kotlinx.collections.immutable.ImmutableList -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.filterNotNull - -@Composable -internal fun FeedScreenContent( - loadMore: () -> Unit, - films: ImmutableList, - screenState: ScreenState.Content, - onFilmClick: (String) -> Unit, - modifier: Modifier = Modifier, -) { - val listState = rememberLazyListState() - LaunchedEffect(listState, films.size) { - snapshotFlow { - listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index - } - .filterNotNull() - .distinctUntilChanged() - .collectLatest { index -> - if ( - screenState != ScreenState.Content.AppendLoading - && index >= films.size - 5 - ) { - Logger.debug("index: $index, films.size: ${films.size}") - loadMore() - } - } - } - BoxWithConstraints { - val itemHeight = remember(maxHeight) { maxHeight / 3 } - LazyColumn( - modifier = modifier - .fillMaxSize(), - state = listState - ) { - items( - count = films.size, - key = films.key, - contentType = { - "film" - } - ) { index -> - val film = films.getOrNull(index) - if (film != null) { - FeedScreenFilmItem( - film = film, - itemHeight = itemHeight, - onFilmClick = onFilmClick, - ) - } - } - - if (screenState is ScreenState.Content.AppendLoading) { - item( - contentType = { - "loading" - } - ) { - Box( - modifier = Modifier.fillMaxWidth(), - contentAlignment = androidx.compose.ui.Alignment.Center - ) { - CircularProgressIndicator() - } - } - } - } - } -} - -private val ImmutableList.key: ((Int) -> Any)? - get() = if (isEmpty()) { - null - } else { - { index -> - get(index).uuid - } - } \ No newline at end of file diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenFilmItem.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenFilmItem.kt deleted file mode 100644 index c752bc06..00000000 --- a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenFilmItem.kt +++ /dev/null @@ -1,155 +0,0 @@ -package com.stslex.feature.match_feed.ui.components - -import androidx.compose.foundation.background -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.shape.RoundedCornerShape -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Warning -import androidx.compose.material3.ElevatedCard -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.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.layout.ContentScale -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 - -@OptIn(ExperimentalMaterial3Api::class) -@Composable -internal fun FeedScreenFilmItem( - modifier: Modifier = Modifier, - itemHeight: Dp, - film: FilmUi, - onFilmClick: (String) -> Unit, -) { - val posterWidth = remember(itemHeight) { - (itemHeight - AppDimension.Padding.medium * 2) / 4 * 3 - } - - ElevatedCard( - modifier = modifier - .fillMaxWidth() - .height(itemHeight) - .padding(AppDimension.Padding.medium), - shape = RoundedCornerShape(AppDimension.Radius.medium), - onClick = onClickDelay { - onFilmClick(film.uuid) - } - ) { - Row(modifier = Modifier.fillMaxSize()) { - Box { - FeedItemFilmPreview( - modifier = Modifier.width(posterWidth), - url = film.poster, - description = film.title - ) - Text( - modifier = Modifier - .align(Alignment.TopStart) - .padding(AppDimension.Padding.small) - .background( - color = MaterialTheme.colorScheme.background.copy(alpha = 0.7f), - shape = RoundedCornerShape(AppDimension.Radius.small), - ) - .padding(AppDimension.Padding.small), - 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 - ) - } - } - } -} - -// TODO: FilmItemGenres -@Composable -fun FilmItemGenres( - genres: ImmutableList, - modifier: Modifier = Modifier, -) { - Text( - modifier = modifier, - text = genres.joinToString(separator = ", "), - style = MaterialTheme.typography.labelSmall, - ) -} - -@Composable -internal fun FeedItemFilmPreview( - modifier: Modifier = Modifier, - url: String, - description: String -) { - NetworkImage( - modifier = modifier, - url = url, - contentDescription = description, - contentScale = ContentScale.Crop, - onLoading = { progress -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(AppDimension.Padding.big), - contentAlignment = Alignment.Center - ) { - Text( - text = "${(progress * 100).toInt()}%", - style = MaterialTheme.typography.titleLarge - ) - } - }, - onFailure = { error -> - Box( - modifier = Modifier - .fillMaxSize() - .padding(AppDimension.Padding.medium), - contentAlignment = Alignment.Center - ) { - Column { - Icon( - imageVector = Icons.Default.Warning, - contentDescription = "Error" - ) - Spacer(modifier = Modifier.height(AppDimension.Padding.medium)) - Text( - text = error.message ?: "Error", - style = MaterialTheme.typography.titleLarge - ) - } - } - } - ) -} \ No newline at end of file diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenContent.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenContent.kt new file mode 100644 index 00000000..63d5143e --- /dev/null +++ b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenContent.kt @@ -0,0 +1,85 @@ +package com.stslex.feature.match_feed.ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.pager.VerticalPager +import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +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 +import com.stslex.feature.match_feed.ui.store.ScreenState +import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.launch + +@OptIn(ExperimentalFoundationApi::class) +@Composable +internal fun MatchFeedScreenContent( + loadMore: () -> Unit, + films: ImmutableList, + screenState: ScreenState.Content, + onFilmClick: (String) -> Unit, + onItemSwiped: (SwipeDirection, String) -> Unit, + modifier: Modifier = Modifier, +) { + val pagerState = rememberPagerState( + initialPage = 0, + initialPageOffsetFraction = 0f + ) { films.size } + + LaunchedEffect(pagerState, films.size) { + snapshotFlow { + pagerState.currentPage + } + .distinctUntilChanged() + .collectLatest { index -> + if ( + screenState != ScreenState.Content.AppendLoading + && index >= films.size - 5 + ) { + Logger.debug("index: $index, films.size: ${films.size}") + loadMore() + } + } + } + + val coroutineScope = rememberCoroutineScope() + + BoxWithConstraints { + val screenWidth = remember(maxWidth) { maxWidth } + + VerticalPager( + state = pagerState, + modifier = modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + key = { page -> + films.getOrNull(page)?.uuid ?: page + } + ) { page -> + val film = films.getOrNull(page) + if (film != null) { + MatchFeedScreenFilmItem( + film = film, + screenWidth = screenWidth, + onFilmClick = onFilmClick, + pagerState = pagerState, + onItemSwiped = { direction, id -> + coroutineScope.launch { + pagerState.animateScrollToPage(page + 1) + onItemSwiped(direction, id) + } + } + ) + } + } + } +} diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenError.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenError.kt similarity index 93% rename from feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenError.kt rename to feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenError.kt index e7a83e79..e37d934a 100644 --- a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenError.kt +++ b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenError.kt @@ -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 ) { diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenFilmItem.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenFilmItem.kt new file mode 100644 index 00000000..e94deb92 --- /dev/null +++ b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenFilmItem.kt @@ -0,0 +1,256 @@ +package com.stslex.feature.match_feed.ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +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.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.pager.PagerState +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.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.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.snapshotFlow +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 kotlinx.coroutines.delay +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull +import kotlin.math.abs + +enum class SwipeDirection { + LEFT, + RIGHT, + NONE +} + +@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class) +@Composable +internal fun MatchFeedScreenFilmItem( + modifier: Modifier = Modifier, + screenWidth: Dp, + film: FilmUi, + pagerState: PagerState, + onFilmClick: (String) -> Unit, + onItemSwiped: (SwipeDirection, String) -> Unit, +) { + 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 + } + } + + LaunchedEffect(swipeableState, film.uuid) { + snapshotFlow { + swipeableState.progress.to.takeIf { + swipeableState.isAnimationRunning.not() && + swipeableState.progress.fraction == 1f + } + } + .filter { + it != SwipeDirection.NONE + } + .filterNotNull() + .distinctUntilChanged() + .collect { value -> + delay(300) + onItemSwiped(value, film.uuid) + } + } + + Box( + modifier = modifier + .fillMaxSize(), + contentAlignment = Alignment.Center, + ) { + Box( + modifier = modifier + .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(pagerState) + ) { + 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(), + ) { + 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.fillMaxSize(), + url = film.poster, + description = film.title + ) + Text( + modifier = Modifier + .align(Alignment.TopStart) + .padding(AppDimension.Padding.small) + .background( + color = MaterialTheme.colorScheme.background.copy(alpha = 0.7f), + shape = RoundedCornerShape(AppDimension.Radius.small), + ) + .padding(AppDimension.Padding.small), + text = film.rate, + style = MaterialTheme.typography.titleSmall, + ) + 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, + ) + } + } + } + } +} + +// TODO: FilmItemGenres +@Composable +fun FilmItemGenres( + genres: ImmutableList, + modifier: Modifier = Modifier, +) { + Text( + modifier = modifier, + text = genres.joinToString(separator = ", "), + style = MaterialTheme.typography.labelSmall, + ) +} + +@Composable +internal fun FeedItemFilmPreview( + modifier: Modifier = Modifier, + url: String, + description: String +) { + NetworkImage( + modifier = modifier, + url = url, + contentDescription = description, + contentScale = ContentScale.Crop, + onLoading = { progress -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(AppDimension.Padding.big), + contentAlignment = Alignment.Center + ) { + Text( + text = "${(progress * 100).toInt()}%", + style = MaterialTheme.typography.titleLarge + ) + } + }, + onFailure = { error -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(AppDimension.Padding.medium), + contentAlignment = Alignment.Center + ) { + Column { + Icon( + imageVector = Icons.Default.Warning, + contentDescription = "Error" + ) + Spacer(modifier = Modifier.height(AppDimension.Padding.medium)) + Text( + text = error.message ?: "Error", + style = MaterialTheme.typography.titleLarge + ) + } + } + } + ) +} \ No newline at end of file diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenLoading.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenLoading.kt similarity index 93% rename from feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenLoading.kt rename to feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenLoading.kt index a663507f..28d3ef06 100644 --- a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/FeedScreenLoading.kt +++ b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/MatchFeedScreenLoading.kt @@ -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 diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/UiExt.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/UiExt.kt new file mode 100644 index 00000000..b926482c --- /dev/null +++ b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/components/UiExt.kt @@ -0,0 +1,19 @@ +package com.stslex.feature.match_feed.ui.components + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.pager.PagerState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import kotlin.math.absoluteValue + +@OptIn(ExperimentalFoundationApi::class) +fun Modifier.animateItemTop( + pagerState: PagerState, + valueKf: Float = 0.2f +): Modifier = graphicsLayer { + val position = pagerState.currentPageOffsetFraction + val value = 1 - (position.absoluteValue * valueKf) + alpha = value + scaleX = value + scaleY = value +} \ No newline at end of file diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/store/MatchFeedStore.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/store/MatchFeedStore.kt index 1c42d197..0403bf3c 100644 --- a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/store/MatchFeedStore.kt +++ b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/store/MatchFeedStore.kt @@ -29,9 +29,14 @@ class MatchFeedStore( Action.Init -> actionInit() Action.LoadFilms -> actionLoadFilms() is Action.FilmClick -> actionFilmClick(action) + is Action.FilmSwiped -> actionFilmSwiped(action) } } + private fun actionFilmSwiped(action: Action.FilmSwiped) { + // TODO send action to backend + } + private fun actionFilmClick(action: Action.FilmClick) { navigate(Navigation.Film(action.uuid)) } diff --git a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/store/MatchFeedStoreComponent.kt b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/store/MatchFeedStoreComponent.kt index 341410c8..d6e5e06e 100644 --- a/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/store/MatchFeedStoreComponent.kt +++ b/feature/match_feed/src/commonMain/kotlin/com/stslex/feature/match_feed/ui/store/MatchFeedStoreComponent.kt @@ -2,6 +2,7 @@ package com.stslex.feature.match_feed.ui.store import androidx.compose.runtime.Stable import com.stslex.core.ui.mvi.Store +import com.stslex.feature.match_feed.ui.components.SwipeDirection import com.stslex.feature.match_feed.ui.model.FilmUi import com.stslex.feature.match_feed.ui.model.MatchUi import kotlinx.collections.immutable.ImmutableList @@ -45,9 +46,16 @@ interface MatchFeedStoreComponent : Store { data object LoadFilms : Action + @Stable data class FilmClick( val uuid: String ) : Action + + @Stable + data class FilmSwiped( + val direction: SwipeDirection, + val uuid: String + ) : Action } }