1212
1313package com.theupnextapp.ui.episodeDetail
1414
15+ import androidx.compose.foundation.background
1516import androidx.compose.foundation.layout.Arrangement
1617import androidx.compose.foundation.layout.Box
1718import androidx.compose.foundation.layout.Column
1819import androidx.compose.foundation.layout.Row
1920import androidx.compose.foundation.layout.Spacer
21+ import androidx.compose.foundation.layout.WindowInsets
22+ import androidx.compose.foundation.layout.asPaddingValues
2023import androidx.compose.foundation.layout.fillMaxSize
2124import androidx.compose.foundation.layout.fillMaxWidth
2225import androidx.compose.foundation.layout.height
2326import androidx.compose.foundation.layout.padding
27+ import androidx.compose.foundation.layout.statusBars
2428import androidx.compose.foundation.rememberScrollState
2529import androidx.compose.foundation.verticalScroll
2630import androidx.compose.material.icons.Icons
2731import androidx.compose.material.icons.filled.ArrowBack
2832import androidx.compose.material.icons.filled.Star
2933import androidx.compose.material3.CircularProgressIndicator
34+ import androidx.compose.material3.ElevatedCard
3035import androidx.compose.material3.ExperimentalMaterial3Api
3136import androidx.compose.material3.Icon
3237import androidx.compose.material3.IconButton
3338import androidx.compose.material3.MaterialTheme
3439import androidx.compose.material3.Scaffold
3540import androidx.compose.material3.Text
3641import androidx.compose.material3.TopAppBar
42+ import androidx.compose.material3.TopAppBarDefaults
3743import androidx.compose.runtime.Composable
3844import androidx.compose.runtime.collectAsState
3945import androidx.compose.runtime.getValue
4046import androidx.compose.ui.Alignment
4147import androidx.compose.ui.Modifier
48+ import androidx.compose.ui.graphics.Brush
49+ import androidx.compose.ui.graphics.Color
50+ import androidx.compose.ui.layout.ContentScale
51+ import androidx.compose.ui.platform.LocalContext
4252import androidx.compose.ui.res.stringResource
4353import androidx.compose.ui.text.font.FontWeight
4454import androidx.compose.ui.unit.dp
4555import androidx.hilt.navigation.compose.hiltViewModel
4656import androidx.navigation.NavController
57+ import coil.compose.AsyncImage
58+ import coil.request.ImageRequest
4759import com.theupnextapp.R
48- import com.theupnextapp.domain.EpisodeDetail
4960import com.theupnextapp.domain.EpisodeDetailArg
5061import java.text.SimpleDateFormat
5162import java.util.Locale
5263
5364@OptIn(ExperimentalMaterial3Api ::class )
65+ @Suppress(" MagicNumber" )
5466@Composable
5567fun EpisodeDetailScreen (
5668 episodeDetailArg : EpisodeDetailArg ? ,
5769 viewModel : EpisodeDetailViewModel = hiltViewModel(),
5870 navController : NavController ,
5971) {
6072 val uiState by viewModel.uiState.collectAsState()
73+ val scrollState = rememberScrollState()
6174
6275 Scaffold (
6376 topBar = {
@@ -71,108 +84,151 @@ fun EpisodeDetailScreen(
7184 )
7285 }
7386 },
87+ colors =
88+ TopAppBarDefaults .topAppBarColors(
89+ containerColor = Color .Transparent ,
90+ scrolledContainerColor = MaterialTheme .colorScheme.surface.copy(alpha = 0.95f ),
91+ ),
7492 )
7593 },
7694 ) { paddingValues ->
7795 Box (
78- modifier =
79- Modifier
80- .fillMaxSize()
81- .padding(paddingValues),
96+ modifier = Modifier .fillMaxSize(),
8297 ) {
83- when {
84- uiState.isLoading -> {
85- CircularProgressIndicator (
86- modifier = Modifier .align(Alignment .Center ),
87- )
88- }
89- uiState.error != null -> {
90- Text (
91- text = stringResource(R .string.error_fetching_episode_details),
92- color = MaterialTheme .colorScheme.error,
93- modifier = Modifier .align(Alignment .Center ),
94- )
95- }
96- uiState.episodeDetail != null -> {
97- EpisodeDetailContent (
98- episodeDetail = uiState.episodeDetail!! ,
99- modifier = Modifier .fillMaxSize(),
100- )
101- }
98+ val backdropUrl = episodeDetailArg?.showBackgroundUrl ? : episodeDetailArg?.showImageUrl
99+ if (! backdropUrl.isNullOrEmpty()) {
100+ AsyncImage (
101+ model =
102+ ImageRequest .Builder (LocalContext .current)
103+ .data(backdropUrl)
104+ .crossfade(true )
105+ .build(),
106+ contentDescription = " Show Backdrop" ,
107+ modifier =
108+ Modifier
109+ .fillMaxWidth()
110+ .height(350 .dp),
111+ contentScale = ContentScale .Crop ,
112+ )
113+ Box (
114+ modifier =
115+ Modifier
116+ .fillMaxWidth()
117+ .height(350 .dp)
118+ .background(
119+ Brush .verticalGradient(
120+ colors =
121+ listOf (
122+ Color .Transparent ,
123+ MaterialTheme .colorScheme.background,
124+ ),
125+ startY = 100f ,
126+ ),
127+ ),
128+ )
102129 }
103- }
104- }
105- }
106130
107- @Composable
108- fun EpisodeDetailContent (
109- episodeDetail : EpisodeDetail ,
110- modifier : Modifier = Modifier ,
111- ) {
112- val scrollState = rememberScrollState()
131+ Box (
132+ modifier =
133+ Modifier
134+ .fillMaxSize()
135+ .padding(top = WindowInsets .statusBars.asPaddingValues().calculateTopPadding() + 56 .dp),
136+ ) {
137+ when {
138+ uiState.isLoading -> {
139+ CircularProgressIndicator (modifier = Modifier .align(Alignment .Center ))
140+ }
141+ uiState.error != null -> {
142+ Text (
143+ text = stringResource(R .string.error_fetching_episode_details),
144+ color = MaterialTheme .colorScheme.error,
145+ modifier = Modifier .align(Alignment .Center ),
146+ )
147+ }
148+ uiState.episodeDetail != null -> {
149+ Column (
150+ modifier =
151+ Modifier
152+ .fillMaxSize()
153+ .verticalScroll(scrollState),
154+ ) {
155+ Spacer (modifier = Modifier .height(if (! backdropUrl.isNullOrEmpty()) 140 .dp else 16 .dp))
113156
114- Column (
115- modifier =
116- modifier
117- .verticalScroll(scrollState)
118- .padding(16 .dp),
119- verticalArrangement = Arrangement .spacedBy(16 .dp),
120- ) {
121- Text (
122- text = episodeDetail.title ? : stringResource(id = R .string.title_unknown),
123- style = MaterialTheme .typography.headlineMedium,
124- fontWeight = FontWeight .Bold ,
125- )
157+ ElevatedCard (
158+ modifier =
159+ Modifier
160+ .fillMaxWidth()
161+ .padding(horizontal = 16 .dp, vertical = 16 .dp),
162+ shape = MaterialTheme .shapes.extraLarge,
163+ ) {
164+ Column (
165+ modifier = Modifier .padding(24 .dp),
166+ verticalArrangement = Arrangement .spacedBy(16 .dp),
167+ ) {
168+ Text (
169+ text = uiState.episodeDetail?.title ? : stringResource(id = R .string.title_unknown),
170+ style = MaterialTheme .typography.headlineMedium,
171+ fontWeight = FontWeight .Bold ,
172+ )
126173
127- Row (
128- modifier = Modifier .fillMaxWidth(),
129- horizontalArrangement = Arrangement .SpaceBetween ,
130- verticalAlignment = Alignment .CenterVertically ,
131- ) {
132- Text (
133- text = " Season ${episodeDetail.season} • Episode ${episodeDetail.number} " ,
134- style = MaterialTheme .typography.titleMedium,
135- color = MaterialTheme .colorScheme.secondary,
136- )
174+ Row (
175+ modifier = Modifier .fillMaxWidth(),
176+ horizontalArrangement = Arrangement .SpaceBetween ,
177+ verticalAlignment = Alignment .CenterVertically ,
178+ ) {
179+ Text (
180+ text = " Season ${uiState. episodeDetail? .season} • Episode ${uiState. episodeDetail? .number} " ,
181+ style = MaterialTheme .typography.titleMedium,
182+ color = MaterialTheme .colorScheme.secondary,
183+ )
137184
138- if (episodeDetail.rating != null && episodeDetail.rating!! > 0.0 ) {
139- Row (verticalAlignment = Alignment .CenterVertically ) {
140- Icon (
141- imageVector = Icons .Default .Star ,
142- contentDescription = " Rating" ,
143- tint = MaterialTheme .colorScheme.primary ,
144- modifier = Modifier .padding(end = 4 .dp),
145- )
146- Text (
147- text = String .format(Locale .getDefault(), " %.1f" , episodeDetail.rating),
148- style = MaterialTheme .typography.bodyLarge,
149- fontWeight = FontWeight .Medium ,
150- )
151- }
152- }
153- }
185+ if (uiState. episodeDetail? .rating != null && uiState. episodeDetail? .rating!! > 0.0 ) {
186+ Row (verticalAlignment = Alignment .CenterVertically ) {
187+ Icon (
188+ imageVector = Icons .Default .Star ,
189+ contentDescription = " Rating" ,
190+ tint = Color ( 0xFFFFC107 ) ,
191+ modifier = Modifier .padding(end = 4 .dp),
192+ )
193+ Text (
194+ text = String .format(Locale .getDefault(), " %.1f" , uiState. episodeDetail? .rating),
195+ style = MaterialTheme .typography.bodyLarge,
196+ fontWeight = FontWeight .Medium ,
197+ )
198+ }
199+ }
200+ }
154201
155- episodeDetail.firstAired?.let { aired ->
156- Text (
157- text = " Aired: ${formatDate(aired)} " ,
158- style = MaterialTheme .typography.bodyMedium,
159- color = MaterialTheme .colorScheme.onSurfaceVariant,
160- )
161- }
202+ uiState. episodeDetail? .firstAired?.let { aired ->
203+ Text (
204+ text = " Aired: ${formatDate(aired)} " ,
205+ style = MaterialTheme .typography.bodyMedium,
206+ color = MaterialTheme .colorScheme.onSurfaceVariant,
207+ )
208+ }
162209
163- Spacer (modifier = Modifier .height(8 .dp))
210+ Spacer (modifier = Modifier .height(8 .dp))
164211
165- Text (
166- text = " Overview" ,
167- style = MaterialTheme .typography.titleMedium,
168- fontWeight = FontWeight .SemiBold ,
169- )
212+ Text (
213+ text = " Overview" ,
214+ style = MaterialTheme .typography.titleMedium,
215+ fontWeight = FontWeight .SemiBold ,
216+ )
170217
171- Text (
172- text = episodeDetail.overview ? : stringResource(id = R .string.no_overview_available),
173- style = MaterialTheme .typography.bodyLarge,
174- lineHeight = MaterialTheme .typography.bodyLarge.lineHeight * 1.5f ,
175- )
218+ Text (
219+ text = uiState.episodeDetail?.overview ? : stringResource(id = R .string.no_overview_available),
220+ style = MaterialTheme .typography.bodyLarge,
221+ lineHeight = MaterialTheme .typography.bodyLarge.lineHeight * 1.5f ,
222+ color = MaterialTheme .colorScheme.onSurfaceVariant,
223+ )
224+ }
225+ }
226+ Spacer (modifier = Modifier .height(32 .dp))
227+ }
228+ }
229+ }
230+ }
231+ }
176232 }
177233}
178234
0 commit comments