diff --git a/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt b/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt index 8257be6..41a4bea 100644 --- a/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt +++ b/composeApp/src/androidInstrumentedTest/kotlin/com/randomboxd/feature/random_film/presentation/FilmPosterTest.kt @@ -116,4 +116,54 @@ class FilmPosterTest { assertTrue(clicked) } + + @Test + fun film_poster_hides_results_chip_when_count_is_zero() { + val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888) + setImageLoader( + FakeImageLoaderEngine + .Builder() + .default(bitmap.asImage()) + .build(), + ) + + composeTestRule.setContent { + FilmPoster( + imageUrl = "https://example.com/poster.jpg", + title = "Test Film", + releaseYear = "2020", + onClick = {}, + onRerollClick = {}, + numberOfResults = 0, + ) + } + + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag("results-count-chip").assertDoesNotExist() + } + + @Test + fun film_poster_shows_results_chip_when_count_is_greater_than_zero() { + val bitmap = Bitmap.createBitmap(200, 200, Bitmap.Config.ARGB_8888) + setImageLoader( + FakeImageLoaderEngine + .Builder() + .default(bitmap.asImage()) + .build(), + ) + + composeTestRule.setContent { + FilmPoster( + imageUrl = "https://example.com/poster.jpg", + title = "Test Film", + releaseYear = "2020", + onClick = {}, + onRerollClick = {}, + numberOfResults = 42, + ) + } + + composeTestRule.waitForIdle() + composeTestRule.onNodeWithTag("results-count-chip").assertIsDisplayed() + } } diff --git a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/RandomFilmScreen.kt b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/RandomFilmScreen.kt index 5fe1ffd..a079bc1 100644 --- a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/RandomFilmScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/RandomFilmScreen.kt @@ -61,6 +61,7 @@ fun RandomFilmScreenRoot( val filmSearchMode by stateFlow.map { it.filmSearchMode }.collectAsStateWithLifecycle(initialValue = FilmSearchMode.INTERSECTION) val selectedGenres by stateFlow.map { it.selectedGenres }.collectAsStateWithLifecycle(initialValue = emptySet()) val showGenreBottomSheet by stateFlow.map { it.showGenreBottomSheet }.collectAsStateWithLifecycle(initialValue = false) + val numberOfResults by stateFlow.map { it.numberOfResults }.collectAsStateWithLifecycle(initialValue = 0) val usernameState = remember { TextFieldState(userName) } @@ -99,6 +100,7 @@ fun RandomFilmScreenRoot( filmSearchMode = filmSearchMode, selectedGenres = selectedGenres, showGenreBottomSheet = showGenreBottomSheet, + numberOfResults = numberOfResults, userNameList = viewModel.userNameList, onAction = onAction, ) @@ -114,6 +116,7 @@ fun RandomFilmScreen( filmSearchMode: FilmSearchMode = FilmSearchMode.INTERSECTION, selectedGenres: Set = emptySet(), showGenreBottomSheet: Boolean = false, + numberOfResults: Int = 0, userNameList: StateFlow> = MutableStateFlow(emptyList()), onAction: (RandomFilmAction) -> Unit = {}, ) { @@ -160,7 +163,7 @@ fun RandomFilmScreen( FilmErrorView(it) } resultFilm?.takeIf { !isLoading }?.let { - FilmDisplay(it, onAction) + FilmDisplay(it, onAction, numberOfResults) } ?: LoadingOrPrompt(isLoading) if (resultError == null && resultFilm == null && !isLoading) { diff --git a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/FilmDisplay.kt b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/FilmDisplay.kt index 70a0b3f..74d6cb6 100644 --- a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/FilmDisplay.kt +++ b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/FilmDisplay.kt @@ -14,6 +14,7 @@ import com.nacchofer31.randomboxd.random_film.presentation.viewmodel.RandomFilmA internal fun FilmDisplay( film: Film, onAction: (RandomFilmAction) -> Unit, + numberOfResults: Int = 0, ) = Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.padding(vertical = 20.dp).testTag("test-film-display"), @@ -28,5 +29,6 @@ internal fun FilmDisplay( onRerollClick = { onAction(RandomFilmAction.OnRerollClicked) }, + numberOfResults = numberOfResults, ) } diff --git a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/FilmPoster.kt b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/FilmPoster.kt index 6196ebe..6cae40a 100644 --- a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/FilmPoster.kt +++ b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/FilmPoster.kt @@ -50,6 +50,7 @@ fun FilmPoster( releaseYear: String, onClick: () -> Unit, onRerollClick: () -> Unit, + numberOfResults: Int = 0, ) { var imageLoadResult by remember { mutableStateOf?>(null) @@ -116,38 +117,53 @@ fun FilmPoster( modifier = Modifier .fillMaxWidth() - .aspectRatio(280f / 360f) - .clip(RoundedCornerShape(12.dp)) - .clickable { onClick() }, + .aspectRatio(280f / 360f), ) { - Image( - painter = painter, - contentDescription = "film_image", - modifier = Modifier.fillMaxSize(), - contentScale = - if (result.isSuccess) { - ContentScale.Crop - } else { - ContentScale.Fit - }, - ) Box( modifier = Modifier - .padding(8.dp) - .size(28.dp) - .background( - color = Color.Black.copy(alpha = 0.7f), - shape = RoundedCornerShape(14.dp), - ).align(Alignment.TopStart) + .fillMaxSize() + .clip(RoundedCornerShape(12.dp)) .clickable { onClick() }, - contentAlignment = Alignment.Center, ) { - Icon( - imageVector = Icons.Outlined.Info, - contentDescription = "info_icon", - tint = RandomBoxdColors.White, - modifier = Modifier.size(16.dp), + Image( + painter = painter, + contentDescription = "film_image", + modifier = Modifier.fillMaxSize(), + contentScale = + if (result.isSuccess) { + ContentScale.Crop + } else { + ContentScale.Fit + }, + ) + Box( + modifier = + Modifier + .padding(8.dp) + .size(28.dp) + .background( + color = Color.Black.copy(alpha = 0.7f), + shape = RoundedCornerShape(14.dp), + ).align(Alignment.TopStart) + .clickable { onClick() }, + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = Icons.Outlined.Info, + contentDescription = "info_icon", + tint = RandomBoxdColors.White, + modifier = Modifier.size(16.dp), + ) + } + } + if (numberOfResults > 0) { + ResultsCountChip( + count = numberOfResults, + modifier = + Modifier + .padding(8.dp) + .align(Alignment.TopEnd), ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/ResultsCountChip.kt b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/ResultsCountChip.kt new file mode 100644 index 0000000..78b7332 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/components/ResultsCountChip.kt @@ -0,0 +1,53 @@ +package com.nacchofer31.randomboxd.random_film.presentation.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Movie +import androidx.compose.material3.Icon +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.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.nacchofer31.randomboxd.core.presentation.RandomBoxdColors + +@Composable +fun ResultsCountChip( + count: Int, + modifier: Modifier = Modifier, +) { + Row( + modifier = + modifier + .height(28.dp) + .background( + color = RandomBoxdColors.GreenAccent, + shape = RoundedCornerShape(14.dp), + ).padding(horizontal = 10.dp) + .testTag("results-count-chip"), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + Icon( + imageVector = Icons.Outlined.Movie, + contentDescription = "results_count_icon", + tint = RandomBoxdColors.BackgroundDarkColor, + modifier = Modifier.size(14.dp), + ) + Text( + text = count.toString(), + fontSize = 12.sp, + fontWeight = FontWeight.SemiBold, + color = RandomBoxdColors.BackgroundDarkColor, + ) + } +} diff --git a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/viewmodel/RandomFilmState.kt b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/viewmodel/RandomFilmState.kt index 872e7d6..4bd263d 100644 --- a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/viewmodel/RandomFilmState.kt +++ b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/viewmodel/RandomFilmState.kt @@ -14,4 +14,5 @@ data class RandomFilmState( val filmSearchMode: FilmSearchMode = FilmSearchMode.INTERSECTION, val selectedGenres: Set = emptySet(), val showGenreBottomSheet: Boolean = false, + val numberOfResults: Int = 0, ) diff --git a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/viewmodel/RandomFilmViewModel.kt b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/viewmodel/RandomFilmViewModel.kt index 2bf7712..41e2b1f 100644 --- a/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/viewmodel/RandomFilmViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/nacchofer31/randomboxd/random_film/presentation/viewmodel/RandomFilmViewModel.kt @@ -81,7 +81,7 @@ class RandomFilmViewModel( emit(result) }.onStart { internalState.update { state -> - state.copy(isLoading = true, resultError = null, resultFilm = null) + state.copy(isLoading = true, resultError = null, resultFilm = null, numberOfResults = 0) } }.flowOn(dispatchers.main) }.onEach { result -> @@ -99,6 +99,7 @@ class RandomFilmViewModel( isLoading = false, resultFilm = filmResult.data, resultError = null, + numberOfResults = cachedResultFilms.size, ) } @@ -107,6 +108,7 @@ class RandomFilmViewModel( isLoading = false, resultFilm = null, resultError = filmResult.error, + numberOfResults = 0, ) } } @@ -117,6 +119,7 @@ class RandomFilmViewModel( isLoading = false, resultFilm = null, resultError = result.error, + numberOfResults = 0, ) } } @@ -249,8 +252,8 @@ class RandomFilmViewModel( ) internalState.update { when (filmResult) { - is ResultData.Success -> it.copy(isLoading = false, resultFilm = filmResult.data, resultError = null) - is ResultData.Error -> it.copy(isLoading = false, resultFilm = null, resultError = filmResult.error) + is ResultData.Success -> it.copy(isLoading = false, resultFilm = filmResult.data, resultError = null, numberOfResults = cachedResultFilms.size) + is ResultData.Error -> it.copy(isLoading = false, resultFilm = null, resultError = filmResult.error, numberOfResults = 0) } } } diff --git a/randomboxd.pen b/randomboxd.pen index 6a58564..c8d2f8e 100644 --- a/randomboxd.pen +++ b/randomboxd.pen @@ -1952,6 +1952,44 @@ "fill": "$text-primary" } ] + }, + { + "type": "frame", + "id": "tUtrd", + "x": 131, + "y": 8, + "name": "resultsChip", + "height": 28, + "fill": "$accent-green", + "cornerRadius": 14, + "gap": 4, + "padding": [ + 0, + 10 + ], + "alignItems": "center", + "children": [ + { + "type": "icon", + "id": "JScqq", + "name": "filmIcon", + "width": 14, + "height": 14, + "icon": "film", + "library": "lucide", + "fill": "#14181C" + }, + { + "type": "text", + "id": "SBmEz", + "name": "resultsCount", + "fill": "#14181C", + "content": "247", + "fontFamily": "Inter", + "fontSize": 12, + "fontWeight": "600" + } + ] } ] },