Skip to content

Commit e79da50

Browse files
authored
Improve details screen (#394)
1 parent 014376d commit e79da50

File tree

12 files changed

+153
-14
lines changed

12 files changed

+153
-14
lines changed

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/data/MovieDto.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ import kotlinx.serialization.Serializable
77
data class MovieDto(
88
val id: Long,
99
val title: String,
10+
val overview: String,
11+
@SerialName("vote_average")
12+
val voteAverage: Float,
1013
@SerialName("poster_path")
1114
val posterPath: String,
1215
)

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/data/movies.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ suspend fun TheMovieDbService.movieDetails(id: MovieId): MovieDetails {
2525
private fun MovieDto.toDomain(): Movie = Movie.create(
2626
id = id,
2727
name = title,
28+
overview = overview,
29+
voteAverage = voteAverage,
2830
posterPath = posterPath,
2931
)
3032

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/domain/Movie.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ typealias MovieId = Long
88
data class Movie(
99
val id: MovieId,
1010
val name: String,
11+
val overview: String,
12+
val voteAverage: Float,
1113
val thumbnail: HttpUrl,
1214
) {
1315

@@ -16,10 +18,14 @@ data class Movie(
1618
fun create(
1719
id: Long,
1820
name: String,
21+
overview: String,
22+
voteAverage: Float,
1923
posterPath: String,
2024
): Movie = Movie(
2125
id = id,
2226
name = name,
27+
overview = overview,
28+
voteAverage = voteAverage,
2329
thumbnail = "https://image.tmdb.org/t/p/w500$posterPath".toHttpUrl(),
2430
)
2531
}

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/moviedetails/MovieDetailsScreen.kt

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,43 @@
11
package io.github.lordraydenmk.themoviedbapp.movies.moviedetails
22

3+
import androidx.compose.foundation.layout.Arrangement
4+
import androidx.compose.foundation.layout.Box
35
import androidx.compose.foundation.layout.Column
46
import androidx.compose.foundation.layout.aspectRatio
57
import androidx.compose.foundation.layout.fillMaxSize
68
import androidx.compose.foundation.layout.fillMaxWidth
9+
import androidx.compose.foundation.layout.padding
10+
import androidx.compose.foundation.layout.size
711
import androidx.compose.foundation.layout.statusBarsPadding
12+
import androidx.compose.foundation.rememberScrollState
13+
import androidx.compose.foundation.verticalScroll
814
import androidx.compose.material.icons.Icons
915
import androidx.compose.material.icons.automirrored.filled.ArrowBack
16+
import androidx.compose.material3.CircularProgressIndicator
1017
import androidx.compose.material3.ExperimentalMaterial3Api
1118
import androidx.compose.material3.Icon
1219
import androidx.compose.material3.IconButton
20+
import androidx.compose.material3.MaterialTheme
1321
import androidx.compose.material3.Text
1422
import androidx.compose.material3.TopAppBar
1523
import androidx.compose.runtime.Composable
1624
import androidx.compose.runtime.getValue
1725
import androidx.compose.ui.Alignment
1826
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.draw.clip
1928
import androidx.compose.ui.layout.ContentScale
2029
import androidx.compose.ui.platform.testTag
30+
import androidx.compose.ui.text.style.TextAlign
31+
import androidx.compose.ui.tooling.preview.Preview
32+
import androidx.compose.ui.unit.dp
2133
import androidx.lifecycle.compose.collectAsStateWithLifecycle
2234
import coil3.compose.AsyncImage
2335
import io.github.lordraydenmk.themoviedbapp.movies.domain.MovieId
2436
import io.github.lordraydenmk.themoviedbapp.movies.ui.common.MovieLoading
2537
import io.github.lordraydenmk.themoviedbapp.movies.ui.common.MovieProblem
2638
import kotlinx.coroutines.channels.Channel
2739
import kotlinx.coroutines.flow.Flow
40+
import okhttp3.HttpUrl.Companion.toHttpUrl
2841

2942
@OptIn(ExperimentalMaterial3Api::class)
3043
@Composable
@@ -69,18 +82,67 @@ private fun MovieDetailsAppBar(
6982
@Composable
7083
fun MovieContent(content: Content) {
7184
Column(
72-
horizontalAlignment = Alignment.CenterHorizontally,
7385
modifier = Modifier
7486
.fillMaxSize()
75-
.testTag("MovieDetailsContent")
87+
.verticalScroll(rememberScrollState())
88+
.padding(16.dp)
89+
.testTag("MovieDetailsContent"),
90+
horizontalAlignment = Alignment.CenterHorizontally,
91+
verticalArrangement = Arrangement.spacedBy(16.dp)
7692
) {
7793
AsyncImage(
7894
model = content.movie.thumbnail.toString(),
7995
contentScale = ContentScale.Crop,
8096
contentDescription = content.movie.name,
8197
modifier = Modifier
82-
.aspectRatio(1f)
83-
.fillMaxWidth()
98+
.fillMaxWidth(0.8f)
99+
.aspectRatio(2f / 3f)
100+
.clip(MaterialTheme.shapes.medium)
101+
)
102+
103+
Text(
104+
text = content.movie.name,
105+
style = MaterialTheme.typography.headlineLarge,
106+
textAlign = TextAlign.Center
107+
)
108+
Text(
109+
text = content.movie.overview,
110+
style = MaterialTheme.typography.bodyLarge
111+
)
112+
RatingIndicator(
113+
voteAverage = content.movie.voteAverage,
114+
modifier = Modifier.align(Alignment.End)
115+
)
116+
}
117+
}
118+
119+
@Composable
120+
private fun RatingIndicator(voteAverage: VoteAverage, modifier: Modifier = Modifier) {
121+
Box(
122+
contentAlignment = Alignment.Center,
123+
modifier = modifier.size(64.dp)
124+
) {
125+
CircularProgressIndicator(
126+
progress = { voteAverage.progress },
127+
modifier = Modifier.fillMaxSize(),
128+
strokeWidth = 4.dp,
129+
trackColor = MaterialTheme.colorScheme.surfaceVariant,
130+
)
131+
Text(
132+
text = voteAverage.text,
133+
style = MaterialTheme.typography.labelLarge,
84134
)
85135
}
86-
}
136+
}
137+
138+
@Preview
139+
@Composable
140+
private fun MovieDetailsPreview() {
141+
val movie = MovieDetailsViewEntity(
142+
"Awesome Moview",
143+
"This is some awesome moview overview. Bla bla bka",
144+
VoteAverage(0.745f, "7.5"),
145+
"https://image.tmdb.org/t/p/w500".toHttpUrl(),
146+
)
147+
MovieContent(Content(movie))
148+
}

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/moviedetails/MovieDetailsViewState.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ import okhttp3.HttpUrl
55

66
data class MovieDetailsViewEntity(
77
val name: String,
8+
val overview: String,
9+
val voteAverage: VoteAverage,
810
val thumbnail: HttpUrl,
911
)
1012

13+
data class VoteAverage(val progress: Float, val text: String)
14+
1115
sealed class MovieDetailsViewState {
1216

1317
val title: String
@@ -18,4 +22,4 @@ object Loading : MovieDetailsViewState()
1822

1923
data class Content(val movie: MovieDetailsViewEntity) : MovieDetailsViewState()
2024

21-
data class Problem(val stringId: TextRes) : MovieDetailsViewState()
25+
data class Problem(val stringId: TextRes) : MovieDetailsViewState()

app/src/main/java/io/github/lordraydenmk/themoviedbapp/movies/moviedetails/movieDetails.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,15 @@ private fun Throwable.toProblem(): Problem = when (this) {
5858
else -> throw this
5959
}
6060

61-
private fun Movie.toViewEntity(): MovieDetailsViewEntity =
62-
MovieDetailsViewEntity(
61+
private fun Movie.toViewEntity(): MovieDetailsViewEntity {
62+
val voteAverageData = VoteAverage(
63+
progress = voteAverage / 10f,
64+
text = "%.1f".format(voteAverage)
65+
)
66+
return MovieDetailsViewEntity(
6367
name = name,
68+
overview = overview,
69+
voteAverage = voteAverageData,
6470
thumbnail = thumbnail,
6571
)
72+
}

app/src/paparazzi/kotlin/io/github/lordraydenmk/themoviedbapp/movies/moviedetails/MovieDetailsScreenTest.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,12 @@ class MovieDetailsScreenTest {
5555
@Test
5656
fun contentState() {
5757
val url = "http://i.annihil.us/u/prod/marvel/i/mg/c/e0/535fecbbb9784.jpg".toHttpUrl()
58-
val viewState = Content(MovieDetailsViewEntity("Hulk", url))
58+
val viewState = Content(MovieDetailsViewEntity(
59+
"Hulk",
60+
"Movie overview",
61+
VoteAverage(0.745f, "7.5"),
62+
url
63+
))
5964

6065
paparazzi.snapshot {
6166
MovieDetailsScreen(emptyFlow(), viewState, movieId = 0, actions = Channel())

app/src/test/java/io/github/lordraydenmk/themoviedbapp/movies/data/MovieKtTest.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ class MovieKtTest {
2525
val hulkDto = MovieDto(
2626
42,
2727
"Hulk",
28+
"Movie overview",
29+
7.45f,
2830
"/poster.jpg",
2931
)
3032
val service = testMovieDbService(listOf(hulkDto))
@@ -33,6 +35,8 @@ class MovieKtTest {
3335
val hulk = Movie(
3436
42,
3537
"Hulk",
38+
"Movie overview",
39+
7.45f,
3640
"https://image.tmdb.org/t/p/w500/poster.jpg".toHttpUrl(),
3741
)
3842
service.popularMovies() shouldBe PopularMovies(listOf(hulk))
@@ -70,6 +74,8 @@ class MovieKtTest {
7074
val hulkDto = MovieDto(
7175
42,
7276
"Hulk",
77+
"Movie overview",
78+
7.45f,
7379
"/poster.jpg",
7480
)
7581
val service = testMovieDbService(listOf(hulkDto))
@@ -78,6 +84,8 @@ class MovieKtTest {
7884
val hulk = Movie(
7985
42,
8086
"Hulk",
87+
"Movie overview",
88+
7.45f,
8189
"https://image.tmdb.org/t/p/w500/poster.jpg".toHttpUrl(),
8290
)
8391
service.movieDetails(42) shouldBe MovieDetails(hulk)

app/src/test/java/io/github/lordraydenmk/themoviedbapp/movies/domain/MovieTest.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,19 @@ class MovieTest {
88

99
@Test
1010
fun `create valid movie`() {
11-
val actual = Movie.create(42, "Ant Man", "/poster.jpg")
11+
val actual = Movie.create(
12+
42,
13+
"Ant Man",
14+
"Movie overview",
15+
7.45f,
16+
"/poster.jpg"
17+
)
1218

1319
actual shouldBe Movie(
1420
42,
1521
"Ant Man",
22+
"Movie overview",
23+
7.45f,
1624
"https://image.tmdb.org/t/p/w500/poster.jpg".toHttpUrl(),
1725
)
1826
}

app/src/test/java/io/github/lordraydenmk/themoviedbapp/movies/moviedetails/MovieDetailsKtTest.kt

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,15 @@ class MovieDetailsKtTest {
2222
fun movieDto(
2323
id: Long,
2424
name: String,
25+
overview: String,
26+
voteAverage: Float,
2527
thumbnailPath: String,
2628
): MovieDto =
2729
MovieDto(
2830
id,
2931
name,
32+
overview,
33+
voteAverage,
3034
thumbnailPath,
3135
)
3236

@@ -39,7 +43,13 @@ class MovieDetailsKtTest {
3943

4044
@Test
4145
fun `FirstLoad - service with success - Movie`() = runTest {
42-
val hulkDto = movieDto(42, "Hulk", "/poster.jpg")
46+
val hulkDto = movieDto(
47+
42,
48+
"Hulk",
49+
"Movie overview",
50+
7.45f,
51+
"/poster.jpg"
52+
)
4353
val service = testMovieDbService(listOf(hulkDto))
4454

4555
val viewModel = TestViewModel<MovieDetailsViewState, MovieDetailsEffect>()
@@ -50,6 +60,8 @@ class MovieDetailsKtTest {
5060

5161
val hulk = MovieDetailsViewEntity(
5262
"Hulk",
63+
"Movie overview",
64+
VoteAverage(0.745f, "7.4"),
5365
"https://image.tmdb.org/t/p/w500/poster.jpg".toHttpUrl(),
5466
)
5567
viewModel.viewState.test {
@@ -80,7 +92,13 @@ class MovieDetailsKtTest {
8092

8193
@Test
8294
fun `Action Up - NavigateUp Effect`() = runTest {
83-
val hulkDto = movieDto(42, "Hulk", "/poster.jpg")
95+
val hulkDto = movieDto(
96+
42,
97+
"Hulk",
98+
"Movie overview",
99+
7.45f,
100+
"/poster.jpg"
101+
)
84102
val viewModel = TestViewModel<MovieDetailsViewState, MovieDetailsEffect>()
85103
val module = module(testMovieDbService(listOf(hulkDto)), viewModel)
86104

0 commit comments

Comments
 (0)