From e2555640655f10064392cadb7a4c30e287ce7540 Mon Sep 17 00:00:00 2001 From: Israa Mohamed Date: Thu, 21 Aug 2025 22:22:14 +0300 Subject: [PATCH 1/4] fix: padding in movie card --- .../cineverse/designSystem/component/card/InfoCard.kt | 1 + .../screen/collection_details/CollectionDetailsScreen.kt | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/design_system/src/main/java/com/moscow/cineverse/designSystem/component/card/InfoCard.kt b/design_system/src/main/java/com/moscow/cineverse/designSystem/component/card/InfoCard.kt index 363ab4106..daf36b3ec 100644 --- a/design_system/src/main/java/com/moscow/cineverse/designSystem/component/card/InfoCard.kt +++ b/design_system/src/main/java/com/moscow/cineverse/designSystem/component/card/InfoCard.kt @@ -30,6 +30,7 @@ fun InfoCard( ) { Box( modifier = modifier + .padding(horizontal = 16.dp) .fillMaxWidth() .background( color = Theme.colors.background.card, diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/collection_details/CollectionDetailsScreen.kt b/presentation/src/main/java/com/moscow/cineverse/screen/collection_details/CollectionDetailsScreen.kt index 0b9afc46b..48c40f37d 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/collection_details/CollectionDetailsScreen.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/collection_details/CollectionDetailsScreen.kt @@ -130,8 +130,6 @@ private fun CollectionDetailsScreenContent( LazyColumn( modifier = Modifier.padding( top = 16.dp, - start = 16.dp, - end = 16.dp ) ) { if (uiState.showTip) { @@ -171,7 +169,9 @@ private fun CollectionDetailsScreenContent( }, showTitle = true, showBackdrop = true, - enableBlur = uiState.enableBlur + enableBlur = uiState.enableBlur, + modifier = Modifier.padding(horizontal = 16.dp) + ) } } From 6ebfec73df80eecc25bc9fd74f9b86e48fad2faa Mon Sep 17 00:00:00 2001 From: Israa Mohamed Date: Thu, 21 Aug 2025 22:43:33 +0300 Subject: [PATCH 2/4] fix: date numbers in arabic --- .../cineverse/component/MediaPosterCard.kt | 25 +++-- .../moscow/cineverse/mapper/CommonMapper.kt | 24 +++- .../components/ActorMainDetailsSection.kt | 3 +- .../screen/details/common/MediaHeader.kt | 3 +- .../details/movie_details/MovieScreenState.kt | 16 ++- .../component/MovieReviewsSection.kt | 3 +- .../composable/MatchCarouselAnimation.kt | 103 ++++++++++++------ .../cineverse/screen/reviews/ReviewsScreen.kt | 3 +- 8 files changed, 122 insertions(+), 58 deletions(-) diff --git a/presentation/src/main/java/com/moscow/cineverse/component/MediaPosterCard.kt b/presentation/src/main/java/com/moscow/cineverse/component/MediaPosterCard.kt index 1d31de48c..e4846b0fc 100644 --- a/presentation/src/main/java/com/moscow/cineverse/component/MediaPosterCard.kt +++ b/presentation/src/main/java/com/moscow/cineverse/component/MediaPosterCard.kt @@ -30,14 +30,12 @@ import androidx.compose.material3.Icon import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -195,9 +193,9 @@ private fun GridMovieCard( interactionSource = remember { MutableInteractionSource() } ) { - onMovieClick(movie.id) + onMovieClick(movie.id) - }, + }, isBlurEnabled = enableBlur, placeholderContent = { RemoteImagePlaceholder( @@ -276,7 +274,7 @@ private fun GridMovieCard( indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { - onMovieClick(movie.id) + onMovieClick(movie.id) } ) ) @@ -290,7 +288,8 @@ private fun GridMovieCard( ) { with(sharedTransitionScope) { DurationAndDateSection( - releaseDate = movie.releaseDate?.formatDate() ?: "", + releaseDate = movie.releaseDate?.formatDate(context = LocalContext.current) + ?: "", modifier = Modifier.sharedElement( sharedContentState = rememberSharedContentState(key = detailsKey), animatedVisibilityScope = this@AnimatedVisibility, @@ -331,8 +330,8 @@ private fun GridMovieCard( indication = null, interactionSource = remember { MutableInteractionSource() } ) { - onMovieClick(movie.id) - }, + onMovieClick(movie.id) + }, isBlurEnabled = enableBlur, placeholderContent = { RemoteImagePlaceholder( @@ -400,7 +399,7 @@ private fun GridMovieCard( indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = { - onMovieClick(movie.id) + onMovieClick(movie.id) } ) ) @@ -504,7 +503,8 @@ private fun ListMovieCard( ) DurationAndDateSection( - releaseDate = movie.releaseDate?.formatDate() ?: "", + releaseDate = movie.releaseDate?.formatDate(context = LocalContext.current) + ?: "", modifier = Modifier.sharedElement( sharedContentState = rememberSharedContentState(key = detailsKey), animatedVisibilityScope = animatedVisibilityScope, @@ -612,7 +612,8 @@ private fun ListMovieCard( ) DurationAndDateSection( - releaseDate = movie.releaseDate?.formatDate() ?: "" + releaseDate = movie.releaseDate?.formatDate(context = LocalContext.current) + ?: "" ) } } diff --git a/presentation/src/main/java/com/moscow/cineverse/mapper/CommonMapper.kt b/presentation/src/main/java/com/moscow/cineverse/mapper/CommonMapper.kt index 677134d4e..2123f9d9e 100644 --- a/presentation/src/main/java/com/moscow/cineverse/mapper/CommonMapper.kt +++ b/presentation/src/main/java/com/moscow/cineverse/mapper/CommonMapper.kt @@ -1,12 +1,13 @@ package com.moscow.cineverse.mapper +import android.content.Context import androidx.compose.runtime.Composable import androidx.compose.ui.res.stringResource import com.moscow.cineverse.common_ui_state.DurationUiState import com.moscow.cineverse.design_system.R import kotlinx.datetime.LocalDate -import kotlinx.datetime.toJavaLocalDate import java.time.format.DateTimeFormatter +import java.time.format.DecimalStyle import java.util.Locale @@ -20,11 +21,24 @@ fun DurationUiState.formatDuration(): String { } } -fun LocalDate.formatDate(): String { +fun LocalDate.formatDate(context: Context): String { return try { - val formatter = DateTimeFormatter.ofPattern("yyyy, MMM dd", Locale.getDefault()) - val dateString = this.toJavaLocalDate().format(formatter) - dateString + val appLocale = context.resources.configuration.locales[0] + val javaLocalDate = java.time.LocalDate.of(this.year, this.monthNumber, this.dayOfMonth) + val isArabicApp = appLocale.language == "ar" + + val formatter = if (isArabicApp) { + val arabicLocale = Locale.Builder() + .setLanguage("ar") + .setRegion("SA") + .build() + val arabicDecimalStyle = DecimalStyle.of(arabicLocale) + DateTimeFormatter.ofPattern("yyyy, MMM dd", appLocale) + .withDecimalStyle(arabicDecimalStyle) + } else { + DateTimeFormatter.ofPattern("yyyy, MMM dd", appLocale) + } + javaLocalDate.format(formatter) } catch (_: Exception) { "" } diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/cast_detials/components/ActorMainDetailsSection.kt b/presentation/src/main/java/com/moscow/cineverse/screen/cast_detials/components/ActorMainDetailsSection.kt index 49d194e53..cf51c0d0c 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/cast_detials/components/ActorMainDetailsSection.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/cast_detials/components/ActorMainDetailsSection.kt @@ -25,6 +25,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.moscow.cineverse.designSystem.component.blur.OnBlurContent @@ -50,7 +51,7 @@ fun ActorMainDetailsSection( name = actor.name, date = stringResource( R.string.born_on, - actor.birthDate?.formatDate()?:"" + actor.birthDate?.formatDate(context = LocalContext.current) ?: "" ), location = actor.placeOfBirth, scrollState = null, diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/details/common/MediaHeader.kt b/presentation/src/main/java/com/moscow/cineverse/screen/details/common/MediaHeader.kt index d4362cd9d..cbf52321a 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/details/common/MediaHeader.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/details/common/MediaHeader.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.moscow.cineverse.common_ui_state.DurationUiState @@ -273,7 +274,7 @@ fun MediaHeader( releaseDate?.let { date -> InfoTextWithIcon( R.drawable.due_tone_calendar, - date.formatDate(), + date.formatDate(context = LocalContext.current), Theme.colors.shade.secondary ) } diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/details/movie_details/MovieScreenState.kt b/presentation/src/main/java/com/moscow/cineverse/screen/details/movie_details/MovieScreenState.kt index 336b82cdb..8ef1d3f26 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/details/movie_details/MovieScreenState.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/details/movie_details/MovieScreenState.kt @@ -4,6 +4,7 @@ import com.moscow.cineverse.common_ui_state.CastUiState import com.moscow.cineverse.common_ui_state.DurationUiState import com.moscow.cineverse.common_ui_state.MediaItemUiState import com.moscow.cineverse.common_ui_state.ReviewUiState +import com.moscow.cineverse.screen.details.movie_details.MovieScreenState.MovieDetailsUiState import kotlinx.datetime.LocalDate @@ -39,4 +40,17 @@ data class MovieScreenState( val duration: DurationUiState, val description: String ) -} \ No newline at end of file +} + + +fun MovieDetailsUiState.toMediaItem() = + MediaItemUiState( + id = this.id, + title = this.title, + posterPath = this.posterUrl, + rating = this.rating.toFloatOrNull() ?: 0.0f, // Convert string rating to float + genres = this.genres, + releaseDate = this.releaseDate, + backdropPath = this.posterUrl, // Using posterUrl as backdrop (you may want to add a separate backdropUrl field) + mediaType = MediaItemUiState.MediaType.Movie + ) \ No newline at end of file diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/details/movie_details/component/MovieReviewsSection.kt b/presentation/src/main/java/com/moscow/cineverse/screen/details/movie_details/component/MovieReviewsSection.kt index ed2d6c4a5..f36c4818d 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/details/movie_details/component/MovieReviewsSection.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/details/movie_details/component/MovieReviewsSection.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import coil3.compose.rememberAsyncImagePainter import com.moscow.cineverse.common_ui_state.ReviewUiState @@ -37,7 +38,7 @@ fun MovieReviewsSection( review.username, review.reviewContent, review.rate, - review.date?.formatDate() ?: "", + review.date?.formatDate(context = LocalContext.current) ?: "", if (review.userImageUrl.isEmpty()) null else rememberAsyncImagePainter(model = review.userImageUrl), modifier = Modifier.padding( start = 16.dp, diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/match/composable/MatchCarouselAnimation.kt b/presentation/src/main/java/com/moscow/cineverse/screen/match/composable/MatchCarouselAnimation.kt index 6ab650d55..410c473a0 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/match/composable/MatchCarouselAnimation.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/match/composable/MatchCarouselAnimation.kt @@ -1,8 +1,11 @@ package com.moscow.cineverse.screen.match.composable +import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring +import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -11,7 +14,6 @@ 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.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.pager.HorizontalPager @@ -19,11 +21,18 @@ import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape 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.rememberCoroutineScope import androidx.compose.ui.Alignment +import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -31,18 +40,19 @@ import androidx.compose.ui.unit.lerp import androidx.compose.ui.util.lerp import androidx.compose.ui.zIndex import com.moscow.cineverse.component.EmptyState -import com.moscow.cineverse.designSystem.component.blur.OnBlurContent -import com.moscow.cineverse.designSystem.component.blur.RemoteImagePlaceholder +import com.moscow.cineverse.component.MediaPosterCard import com.moscow.cineverse.designSystem.component.button.MovieButton import com.moscow.cineverse.designSystem.theme.Theme -import com.moscow.cineverse.image_viewer.component.SafeImageViewer import com.moscow.cineverse.mapper.formatDate import com.moscow.cineverse.screen.details.common.DetailCard import com.moscow.cineverse.screen.details.movie_details.MovieScreenState +import com.moscow.cineverse.screen.details.movie_details.toMediaItem +import com.moscow.cineverse.screen.home.components.AnimatedRatingDisplaySection import com.moscow.cinverse.presentation.R import kotlinx.coroutines.delay import kotlin.math.absoluteValue +@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun MatchCarouselAnimation( movies: List, @@ -58,6 +68,27 @@ fun MatchCarouselAnimation( pageCount = { movies.size } ) + val currentItem by remember { + derivedStateOf { movies[pagerState.currentPage] } + } + + val animatedRating by animateFloatAsState( + targetValue = currentItem.rating.toFloat(), + animationSpec = tween(durationMillis = 500), + label = "ratingAnimation" + ) + + val pageOffset by remember { + derivedStateOf { pagerState.currentPageOffsetFraction.absoluteValue.coerceIn(0f, 0.5f) } + } + val normalizedOffset = pageOffset * 2f + val factor = (1f - normalizedOffset).coerceIn(0f, 1f) + val density = LocalDensity.current + val configuration = LocalConfiguration.current + val scope = rememberCoroutineScope() + + val screenWidth = with(density) { configuration.screenWidthDp.dp } + val width = screenWidth - 48.dp LaunchedEffect(pagerState) { while (true) { delay(3000) @@ -96,52 +127,52 @@ fun MatchCarouselAnimation( ) { HorizontalPager( state = pagerState, - modifier = Modifier.fillMaxSize(), - contentPadding = PaddingValues(horizontal = 60.dp), - pageSpacing = (-80).dp, + modifier = Modifier + .fillMaxWidth(), + contentPadding = PaddingValues(horizontal = 32.dp), + pageSpacing = (-20).dp, verticalAlignment = Alignment.CenterVertically, - beyondViewportPageCount = 2 + beyondViewportPageCount = 1 ) { page -> val pageOffset = ((pagerState.currentPage - page) + pagerState.currentPageOffsetFraction) .absoluteValue.coerceIn(0f, 1f) - val animatedHeight = lerp(320.dp, 267.dp, pageOffset) - val animatedWidth = lerp(240.dp, 200.dp, pageOffset) + val animatedHeight = lerp(230.dp, 200.dp, 1f - pageOffset) + val animatedWidth = lerp(360.dp, 312.dp, 1f - pageOffset) - val cardAlpha = lerp(1f, 0.6f, pageOffset) - val scale = lerp(1f, 0.85f, pageOffset) + val cardAlpha = lerp(0.6f, 1f, 1f - pageOffset) + val textAlpha = 1f - pageOffset * 3f Box( modifier = Modifier - .fillMaxSize() + .height(270.dp) .zIndex(1f - pageOffset) - .graphicsLayer { - scaleX = scale - scaleY = scale - }, - contentAlignment = Alignment.Center ) { - SafeImageViewer( - imageUrl = movies[page].posterUrl, + MediaPosterCard( + mediaItem = movies[page].toMediaItem(), + showRating = false, modifier = Modifier + .fillMaxWidth() + .align(BiasAlignment(0f, pageOffset - 1f)) .alpha(cardAlpha) .size(width = animatedWidth, height = animatedHeight) - .clip(RoundedCornerShape(Theme.radius.extraLarge)) - .offset( - x = when { - page < pagerState.currentPage -> 40.dp * pageOffset - page > pagerState.currentPage -> ((-40).dp) * pageOffset - else -> 0.dp - } - ), - isBlurEnabled = isBlurEnabled, - placeholderContent = { RemoteImagePlaceholder() }, - errorContent = { RemoteImagePlaceholder() }, - onBlurContent = { - OnBlurContent() - } + .clip(RoundedCornerShape(Theme.radius.extraLarge)), + onMediaItemClick = { }, + showBackdrop = true, + showTitle = false, + enableBlur = isBlurEnabled, + useFixedHeight = true ) + if (textAlpha > 0) { + AnimatedRatingDisplaySection( + rating = animatedRating, + alpha = 1f, + modifier = Modifier + .align(Alignment.TopEnd) + + ) + } } } } @@ -163,7 +194,7 @@ fun MatchCarouselAnimation( genres = movies[pagerState.currentPage].genres.joinToString(", "), rating = movies[pagerState.currentPage].rating, duration = if (movies[pagerState.currentPage].duration.hours == 0 && movies[pagerState.currentPage].duration.minutes == 0) "null" else movies[pagerState.currentPage].duration.toString(), - releaseDate = movies[pagerState.currentPage].releaseDate?.formatDate() + releaseDate = movies[pagerState.currentPage].releaseDate?.formatDate(context = LocalContext.current) ?: "", type = stringResource(R.string.movie), onSaveClick = { onSaveClick(movies[pagerState.currentPage].id) }, diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/reviews/ReviewsScreen.kt b/presentation/src/main/java/com/moscow/cineverse/screen/reviews/ReviewsScreen.kt index a74598616..3046c8ee5 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/reviews/ReviewsScreen.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/reviews/ReviewsScreen.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel @@ -81,7 +82,7 @@ fun ReviewsContent( username = review.username, reviewText = review.reviewContent, rating = review.rate, - date = if (review.date != null) review.date.formatDate() else "", + date = if (review.date != null) review.date.formatDate(context = LocalContext.current) else "", avatar = if (review.userImageUrl.isEmpty()) null else rememberAsyncImagePainter( model = review.userImageUrl ), From d23fb3fd20c418781c65a3d8a2026aa1c4951228 Mon Sep 17 00:00:00 2001 From: Israa Mohamed Date: Thu, 21 Aug 2025 23:46:45 +0300 Subject: [PATCH 3/4] fix: match screen image slider --- .../composable/MatchCarouselAnimation.kt | 120 ++++++++---------- 1 file changed, 50 insertions(+), 70 deletions(-) diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/match/composable/MatchCarouselAnimation.kt b/presentation/src/main/java/com/moscow/cineverse/screen/match/composable/MatchCarouselAnimation.kt index 410c473a0..b9ace0703 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/match/composable/MatchCarouselAnimation.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/match/composable/MatchCarouselAnimation.kt @@ -1,38 +1,33 @@ package com.moscow.cineverse.screen.match.composable -import androidx.compose.animation.ExperimentalSharedTransitionApi +import android.util.Log import androidx.compose.animation.animateContentSize import androidx.compose.animation.core.Spring -import androidx.compose.animation.core.animateFloatAsState import androidx.compose.animation.core.spring -import androidx.compose.animation.core.tween +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.foundation.shape.RoundedCornerShape 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.rememberCoroutineScope import androidx.compose.ui.Alignment -import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp @@ -40,19 +35,18 @@ import androidx.compose.ui.unit.lerp import androidx.compose.ui.util.lerp import androidx.compose.ui.zIndex import com.moscow.cineverse.component.EmptyState -import com.moscow.cineverse.component.MediaPosterCard +import com.moscow.cineverse.designSystem.component.blur.OnBlurContent +import com.moscow.cineverse.designSystem.component.blur.RemoteImagePlaceholder import com.moscow.cineverse.designSystem.component.button.MovieButton import com.moscow.cineverse.designSystem.theme.Theme +import com.moscow.cineverse.image_viewer.component.SafeImageViewer import com.moscow.cineverse.mapper.formatDate import com.moscow.cineverse.screen.details.common.DetailCard import com.moscow.cineverse.screen.details.movie_details.MovieScreenState -import com.moscow.cineverse.screen.details.movie_details.toMediaItem -import com.moscow.cineverse.screen.home.components.AnimatedRatingDisplaySection import com.moscow.cinverse.presentation.R import kotlinx.coroutines.delay import kotlin.math.absoluteValue -@OptIn(ExperimentalSharedTransitionApi::class) @Composable fun MatchCarouselAnimation( movies: List, @@ -67,28 +61,8 @@ fun MatchCarouselAnimation( initialPage = 0, pageCount = { movies.size } ) - - val currentItem by remember { - derivedStateOf { movies[pagerState.currentPage] } - } - - val animatedRating by animateFloatAsState( - targetValue = currentItem.rating.toFloat(), - animationSpec = tween(durationMillis = 500), - label = "ratingAnimation" - ) - - val pageOffset by remember { - derivedStateOf { pagerState.currentPageOffsetFraction.absoluteValue.coerceIn(0f, 0.5f) } - } - val normalizedOffset = pageOffset * 2f - val factor = (1f - normalizedOffset).coerceIn(0f, 1f) - val density = LocalDensity.current - val configuration = LocalConfiguration.current - val scope = rememberCoroutineScope() - - val screenWidth = with(density) { configuration.screenWidthDp.dp } - val width = screenWidth - 48.dp + val screenWidth = LocalConfiguration.current.screenWidthDp + val paddingHorizontal = (screenWidth - screenWidth * .70f) / 2 LaunchedEffect(pagerState) { while (true) { delay(3000) @@ -106,7 +80,12 @@ fun MatchCarouselAnimation( } if (movies.isEmpty()) { - Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Log.e("hdhdhd", "MatchCarouselAnimation: mmmmmmm") + Box( + modifier = modifier + .fillMaxSize() + .background(Theme.colors.background.screen), contentAlignment = Alignment.Center + ) { EmptyState( icon = painterResource(R.drawable.ic_search), title = stringResource(R.string.nothing_found), @@ -122,57 +101,58 @@ fun MatchCarouselAnimation( Box( modifier = modifier .fillMaxWidth() - .height(350.dp), + .fillMaxHeight(.45f), contentAlignment = Alignment.Center ) { HorizontalPager( state = pagerState, - modifier = Modifier - .fillMaxWidth(), - contentPadding = PaddingValues(horizontal = 32.dp), - pageSpacing = (-20).dp, + modifier = Modifier.fillMaxWidth(), + contentPadding = PaddingValues(horizontal = paddingHorizontal.dp), + pageSpacing = (-80).dp, verticalAlignment = Alignment.CenterVertically, - beyondViewportPageCount = 1 + beyondViewportPageCount = 2 ) { page -> val pageOffset = ((pagerState.currentPage - page) + pagerState.currentPageOffsetFraction) .absoluteValue.coerceIn(0f, 1f) - val animatedHeight = lerp(230.dp, 200.dp, 1f - pageOffset) - val animatedWidth = lerp(360.dp, 312.dp, 1f - pageOffset) + val animatedHeight = lerp(320.dp, 267.dp, pageOffset) + val animatedWidth = lerp(240.dp, 200.dp, pageOffset) - val cardAlpha = lerp(0.6f, 1f, 1f - pageOffset) - val textAlpha = 1f - pageOffset * 3f + val cardAlpha = lerp(1f, 0.6f, pageOffset) + val scale = lerp(1f, 0.85f, pageOffset) Box( modifier = Modifier - .height(270.dp) + .fillMaxSize() .zIndex(1f - pageOffset) + .graphicsLayer { + scaleX = scale + scaleY = scale + }, + contentAlignment = Alignment.Center ) { - MediaPosterCard( - mediaItem = movies[page].toMediaItem(), - showRating = false, + SafeImageViewer( + imageUrl = movies[page].posterUrl, modifier = Modifier - .fillMaxWidth() - .align(BiasAlignment(0f, pageOffset - 1f)) .alpha(cardAlpha) - .size(width = animatedWidth, height = animatedHeight) - .clip(RoundedCornerShape(Theme.radius.extraLarge)), - onMediaItemClick = { }, - showBackdrop = true, - showTitle = false, - enableBlur = isBlurEnabled, - useFixedHeight = true + .fillMaxWidth() + .fillMaxHeight() + .clip(RoundedCornerShape(Theme.radius.extraLarge)) + .offset( + x = when { + page < pagerState.currentPage -> 40.dp * pageOffset + page > pagerState.currentPage -> ((-40).dp) * pageOffset + else -> 0.dp + } + ), + isBlurEnabled = isBlurEnabled, + placeholderContent = { RemoteImagePlaceholder() }, + errorContent = { RemoteImagePlaceholder() }, + onBlurContent = { + OnBlurContent() + } ) - if (textAlpha > 0) { - AnimatedRatingDisplaySection( - rating = animatedRating, - alpha = 1f, - modifier = Modifier - .align(Alignment.TopEnd) - - ) - } } } } @@ -194,7 +174,7 @@ fun MatchCarouselAnimation( genres = movies[pagerState.currentPage].genres.joinToString(", "), rating = movies[pagerState.currentPage].rating, duration = if (movies[pagerState.currentPage].duration.hours == 0 && movies[pagerState.currentPage].duration.minutes == 0) "null" else movies[pagerState.currentPage].duration.toString(), - releaseDate = movies[pagerState.currentPage].releaseDate?.formatDate(context = LocalContext.current) + releaseDate = movies[pagerState.currentPage].releaseDate?.formatDate(LocalContext.current) ?: "", type = stringResource(R.string.movie), onSaveClick = { onSaveClick(movies[pagerState.currentPage].id) }, From c35617631e034f9f6d67187dfd600aa01bc62315 Mon Sep 17 00:00:00 2001 From: nourelhodaahmed Date: Fri, 22 Aug 2025 00:36:36 +0300 Subject: [PATCH 4/4] refactor: make float and int be arabic when language is arabic --- .../cineverse/component/MediaPosterCard.kt | 9 +-- .../moscow/cineverse/mapper/CommonMapper.kt | 65 ++++++++++++++++++- .../screen/details/common/InfoSection.kt | 4 +- .../series_seasons/SeasonCard.kt | 10 ++- 4 files changed, 79 insertions(+), 9 deletions(-) diff --git a/presentation/src/main/java/com/moscow/cineverse/component/MediaPosterCard.kt b/presentation/src/main/java/com/moscow/cineverse/component/MediaPosterCard.kt index e4846b0fc..2d9a0bdd8 100644 --- a/presentation/src/main/java/com/moscow/cineverse/component/MediaPosterCard.kt +++ b/presentation/src/main/java/com/moscow/cineverse/component/MediaPosterCard.kt @@ -46,6 +46,7 @@ import com.moscow.cineverse.designSystem.component.blur.OnBlurContent import com.moscow.cineverse.designSystem.theme.Theme import com.moscow.cineverse.image_viewer.component.SafeImageViewer import com.moscow.cineverse.mapper.formatDate +import com.moscow.cineverse.mapper.formatRating import com.moscow.cineverse.screen.details.common.InfoSection import com.moscow.cineverse.utlis.ViewMode import com.moscow.cinverse.presentation.R @@ -234,7 +235,7 @@ private fun GridMovieCard( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "%.1f".format(movie.rating), + text = movie.rating.formatRating(LocalContext.current), color = Theme.colors.shade.primary, style = Theme.textStyle.label.medium.medium ) @@ -364,7 +365,7 @@ private fun GridMovieCard( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "%.1f".format(movie.rating), + text = movie.rating.formatRating(LocalContext.current), color = Theme.colors.shade.primary, style = Theme.textStyle.label.medium.medium ) @@ -530,7 +531,7 @@ private fun ListMovieCard( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "%.1f".format(movie.rating), + text = movie.rating.formatRating(LocalContext.current), color = Theme.colors.shade.primary, style = Theme.textStyle.label.small.medium ) @@ -630,7 +631,7 @@ private fun ListMovieCard( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "%.1f".format(movie.rating), + text = movie.rating.formatRating(LocalContext.current), color = Theme.colors.shade.primary, style = Theme.textStyle.label.small.medium ) diff --git a/presentation/src/main/java/com/moscow/cineverse/mapper/CommonMapper.kt b/presentation/src/main/java/com/moscow/cineverse/mapper/CommonMapper.kt index 2123f9d9e..b37385f7d 100644 --- a/presentation/src/main/java/com/moscow/cineverse/mapper/CommonMapper.kt +++ b/presentation/src/main/java/com/moscow/cineverse/mapper/CommonMapper.kt @@ -6,9 +6,12 @@ import androidx.compose.ui.res.stringResource import com.moscow.cineverse.common_ui_state.DurationUiState import com.moscow.cineverse.design_system.R import kotlinx.datetime.LocalDate +import java.text.NumberFormat +import java.text.DecimalFormat +import java.text.DecimalFormatSymbols +import java.util.Locale import java.time.format.DateTimeFormatter import java.time.format.DecimalStyle -import java.util.Locale @Composable @@ -21,6 +24,66 @@ fun DurationUiState.formatDuration(): String { } } +fun Float.formatRating(context: Context, decimalPlaces: Int = 1): String { + return try { + val appLocale = context.resources.configuration.locales[0] + val isArabicApp = appLocale.language == "ar" + + val formatter = if (isArabicApp) { + val arabicLocale = Locale.Builder() + .setLanguage("ar") + .setRegion("SA") + .build() + val arabicDecimalStyle = DecimalStyle.of(arabicLocale) + (NumberFormat.getNumberInstance(arabicLocale) as DecimalFormat).apply { + minimumFractionDigits = decimalPlaces + maximumFractionDigits = decimalPlaces + decimalFormatSymbols = DecimalFormatSymbols(arabicLocale).apply { + decimalSeparator = arabicDecimalStyle.decimalSeparator + zeroDigit = arabicDecimalStyle.zeroDigit + } + } + } else { + NumberFormat.getNumberInstance(appLocale).apply { + minimumFractionDigits = decimalPlaces + maximumFractionDigits = decimalPlaces + } + } + formatter.format(this) + } catch (e: Exception) { + this.toString() + } +} + +fun Int.formatInt(context: Context): String { + return try { + val appLocale = context.resources.configuration.locales[0] + val isArabicApp = appLocale.language == "ar" + + val formatter = if (isArabicApp) { + val arabicLocale = Locale.Builder() + .setLanguage("ar") + .setRegion("SA") + .build() + (NumberFormat.getNumberInstance(arabicLocale) as DecimalFormat).apply { + minimumFractionDigits = 0 + maximumFractionDigits = 0 + decimalFormatSymbols = DecimalFormatSymbols(arabicLocale).apply { + zeroDigit = DecimalStyle.of(arabicLocale).zeroDigit + } + } + } else { + NumberFormat.getNumberInstance(appLocale).apply { + minimumFractionDigits = 0 + maximumFractionDigits = 0 + } + } + formatter.format(this) + } catch (e: Exception) { + this.toString() + } +} + fun LocalDate.formatDate(context: Context): String { return try { val appLocale = context.resources.configuration.locales[0] diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/details/common/InfoSection.kt b/presentation/src/main/java/com/moscow/cineverse/screen/details/common/InfoSection.kt index 642838a88..997620b84 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/details/common/InfoSection.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/details/common/InfoSection.kt @@ -11,6 +11,7 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -19,6 +20,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.moscow.cineverse.designSystem.theme.Theme import com.moscow.cineverse.design_system.R +import com.moscow.cineverse.mapper.formatRating @Composable fun InfoSection( @@ -56,7 +58,7 @@ fun InfoSection( verticalAlignment = Alignment.CenterVertically ) { Text( - text = "%.1f".format(rating), + text = rating.formatRating(LocalContext.current), color = Theme.colors.shade.primary, style = Theme.textStyle.label.medium.medium, fontSize = 12.sp diff --git a/presentation/src/main/java/com/moscow/cineverse/screen/details/series_details/series_seasons/SeasonCard.kt b/presentation/src/main/java/com/moscow/cineverse/screen/details/series_details/series_seasons/SeasonCard.kt index 256616cf3..52295aed2 100644 --- a/presentation/src/main/java/com/moscow/cineverse/screen/details/series_details/series_seasons/SeasonCard.kt +++ b/presentation/src/main/java/com/moscow/cineverse/screen/details/series_details/series_seasons/SeasonCard.kt @@ -23,6 +23,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow @@ -32,6 +33,8 @@ import com.moscow.cineverse.designSystem.component.blur.RemoteImagePlaceholder import com.moscow.cineverse.designSystem.component.wrapper.MovieText import com.moscow.cineverse.designSystem.theme.Theme import com.moscow.cineverse.image_viewer.component.SafeImageViewer +import com.moscow.cineverse.mapper.formatInt +import com.moscow.cineverse.mapper.formatRating import com.moscow.cinverse.presentation.R import kotlinx.datetime.LocalDate @@ -119,25 +122,26 @@ fun SeasonCard( verticalAlignment = Alignment.CenterVertically ) { if (rate != 0f) { + val movie = null SeasonInfo( icon = R.drawable.due_tone_star, iconTint = Theme.colors.additional.primary.yellow, iconDescription = stringResource(R.string.rate_icon), - title = rate.toString() + title = rate.formatRating(LocalContext.current) ) } SeasonInfo( icon = R.drawable.due_tone_video_library, iconTint = Theme.colors.shade.secondary, iconDescription = stringResource(R.string.episodes_icon), - title = episodeCount.toString() + " " + stringResource(R.string.episodes) + title = episodeCount.formatInt(LocalContext.current) + " " + stringResource(R.string.episodes) ) airDate?.let { SeasonInfo( icon = R.drawable.due_tone_calendar, iconTint = Theme.colors.shade.secondary, iconDescription = stringResource(R.string.air_date_icon), - title = airDate.year.toString() + title = airDate.year.formatInt(LocalContext.current) ) } }