From c3b17373e8e7fffe76efadcf222d69b1bf107a9a Mon Sep 17 00:00:00 2001 From: JoelKanyi Date: Sat, 10 Feb 2024 06:49:04 +0300 Subject: [PATCH 1/2] delete a favorite from film details screen --- .../filmdetail/presentation/FilmDetailsScreen.kt | 6 ++++++ .../filmdetail/presentation/FilmDetailsUiEvents.kt | 3 +++ .../filmdetail/presentation/FilmDetailsViewModel.kt | 6 ++++++ .../presentation/common/FilmImageBanner.kt | 13 +++++++++++-- gradle/libs.versions.toml | 4 ++-- 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsScreen.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsScreen.kt index 85dd05a..bd1755c 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsScreen.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsScreen.kt @@ -67,6 +67,12 @@ fun FilmDetailsScreen( event.favorite ) } + + is FilmDetailsUiEvents.RemoveFromFavorites -> { + viewModel.deleteFavorite( + event.favorite + ) + } } } ) diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsUiEvents.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsUiEvents.kt index 480c5ff..9b20d87 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsUiEvents.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsUiEvents.kt @@ -25,4 +25,7 @@ sealed interface FilmDetailsUiEvents { data class AddToFavorites(val favorite: Favorite) : FilmDetailsUiEvents + + data class RemoveFromFavorites(val favorite: Favorite) : + FilmDetailsUiEvents } diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsViewModel.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsViewModel.kt index aca3701..844ce01 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsViewModel.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsViewModel.kt @@ -188,4 +188,10 @@ class FilmDetailsViewModel @Inject constructor( favoritesRepository.insertFavorite(favorite) } } + + fun deleteFavorite(favorite: Favorite) { + viewModelScope.launch { + favoritesRepository.deleteOneFavorite(favorite) + } + } } diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmImageBanner.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmImageBanner.kt index 6758b20..e137beb 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmImageBanner.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmImageBanner.kt @@ -143,8 +143,17 @@ fun FilmImageBanner( isLiked = isLiked, onClick = { isFav -> if (isFav) { - Toast.makeText(context, "Already added to your favorites", Toast.LENGTH_SHORT).show() - return@CircularFavoriteButtons + onEvents(FilmDetailsUiEvents.RemoveFromFavorites( + Favorite( + favorite = false, + mediaId = filmId, + mediaType = filmType, + image = posterUrl, + title = filmName, + releaseDate = releaseDate, + rating = rating + ) + )) } else { onEvents(FilmDetailsUiEvents.AddToFavorites( Favorite( diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 3b3ee17..6759e03 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,9 +25,9 @@ okhttp = "5.0.0-alpha.12" pagingCompose = "3.2.1" retrofit = "2.9.0" roomRuntime = "2.6.1" -runtimeLivedata = "1.6.0" +runtimeLivedata = "1.6.1" spotless = "6.25.0" -google-services = "4.4.0" +google-services = "4.4.1" accomapnistInsets = "0.30.1" [libraries] From 6f8889c0f68662a50474d34925965f7e5de9efa4 Mon Sep 17 00:00:00 2001 From: JoelKanyi Date: Sat, 10 Feb 2024 08:58:51 +0300 Subject: [PATCH 2/2] =?UTF-8?q?-=20Add=20Pull=20to=20Refresh=20Feature=20#?= =?UTF-8?q?10=20-=20Add=20Film=20Genres=20in=20the=20Film=20Details=20Scre?= =?UTF-8?q?en=20#14=20-=20Cast=20Details=20#52=20-=20WIP=20=F0=9F=9B=A0?= =?UTF-8?q?=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/deploymentTargetDropDown.xml | 15 +- .../data/repository/CastRepositoryImpl.kt | 11 + .../cast/domain/repository/CastRepository.kt | 1 + .../domain/usecase/GetCastDetailsUseCase.kt | 25 + .../castdetails/CastDetailsEvents.kt | 20 + .../castdetails/CastDetailsScreen.kt | 126 ++ .../castdetails/CastDetailsUiState.kt | 22 + .../castdetails/CastDetailsViewModel.kt | 65 + .../presentation/{ => casts}/CastsScreen.kt | 22 +- .../presentation/{ => casts}/CastsUiEvents.kt | 7 +- .../muviz/common/data/network/TMDBApi.kt | 6 + .../presentation/FilmDetailsScreen.kt | 52 +- .../presentation/FilmDetailsUiEvents.kt | 2 + .../presentation/common/CastDetails.kt | 59 +- .../presentation/common/FilmImageBanner.kt | 199 +-- .../presentation/common/FilmInfo.kt | 200 ++- .../presentation/common/FilmNameAndRating.kt | 10 +- .../muviz/home/presentation/HomeScreen.kt | 1121 +++++++++-------- .../muviz/home/presentation/HomeUiEvents.kt | 2 + .../muviz/home/presentation/HomeViewModel.kt | 14 + 20 files changed, 1166 insertions(+), 813 deletions(-) create mode 100644 app/src/main/java/com/kanyideveloper/muviz/cast/domain/usecase/GetCastDetailsUseCase.kt create mode 100644 app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsEvents.kt create mode 100644 app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsScreen.kt create mode 100644 app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsUiState.kt create mode 100644 app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsViewModel.kt rename app/src/main/java/com/kanyideveloper/muviz/cast/presentation/{ => casts}/CastsScreen.kt (91%) rename app/src/main/java/com/kanyideveloper/muviz/cast/presentation/{ => casts}/CastsUiEvents.kt (77%) diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml index 0c0c338..82c5dd2 100644 --- a/.idea/deploymentTargetDropDown.xml +++ b/.idea/deploymentTargetDropDown.xml @@ -3,7 +3,20 @@ - + + + + + + + + + + + + + + diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/data/repository/CastRepositoryImpl.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/data/repository/CastRepositoryImpl.kt index dd09bf8..142e121 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/cast/data/repository/CastRepositoryImpl.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/data/repository/CastRepositoryImpl.kt @@ -51,4 +51,15 @@ class CastRepositoryImpl @Inject constructor( Timber.d("Movie Casts $response") return Resource.Success(response.toDomain()) } + + override suspend fun getCastDetails(id: Int): Resource { + val response = try { + api.getCreditDetails(creditId = id) + } catch (e: Exception) { + Timber.d(e.message) + return Resource.Error("Unknown error occurred") + } + Timber.d("Cast Details $response") + return Resource.Success(Unit) + } } diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/domain/repository/CastRepository.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/domain/repository/CastRepository.kt index 5e2e632..dddddb8 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/cast/domain/repository/CastRepository.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/domain/repository/CastRepository.kt @@ -21,4 +21,5 @@ import com.kanyideveloper.muviz.common.util.Resource interface CastRepository { suspend fun getTvSeriesCasts(id: Int): Resource suspend fun getMovieCasts(id: Int): Resource + suspend fun getCastDetails(id: Int): Resource } diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/domain/usecase/GetCastDetailsUseCase.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/domain/usecase/GetCastDetailsUseCase.kt new file mode 100644 index 0000000..9c93fd2 --- /dev/null +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/domain/usecase/GetCastDetailsUseCase.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2024 Joel Kanyi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.kanyideveloper.muviz.cast.domain.usecase + +import com.kanyideveloper.muviz.cast.domain.repository.CastRepository +import javax.inject.Inject + +class GetCastDetailsUseCase @Inject constructor( + private val repository: CastRepository +) { + suspend operator fun invoke(id: Int) = repository.getCastDetails(id) +} diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsEvents.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsEvents.kt new file mode 100644 index 0000000..d392f45 --- /dev/null +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsEvents.kt @@ -0,0 +1,20 @@ +/* + * Copyright 2024 Joel Kanyi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.kanyideveloper.muviz.cast.presentation.castdetails + +sealed interface CastDetailsEvents { + data object NavigateBack : CastDetailsEvents +} diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsScreen.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsScreen.kt new file mode 100644 index 0000000..a6ff9ff --- /dev/null +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsScreen.kt @@ -0,0 +1,126 @@ +/* + * Copyright 2024 Joel Kanyi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.kanyideveloper.muviz.cast.presentation.castdetails + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.Scaffold +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel +import com.kanyideveloper.muviz.R +import com.kanyideveloper.muviz.cast.domain.model.Cast +import com.kanyideveloper.muviz.common.presentation.components.StandardToolbar +import com.ramcosta.composedestinations.annotation.Destination +import com.ramcosta.composedestinations.navigation.DestinationsNavigator + +@Destination +@Composable +fun CastDetailsScreen( + cast: Cast, + navigator: DestinationsNavigator, + viewModel: CastDetailsViewModel = hiltViewModel(), +) { + val castDetailsUiState by viewModel.castDetailsUiState.collectAsState() + + LaunchedEffect(key1 = viewModel) { + viewModel.getCastDetails(cast.id) + } + + CastDetailsScreenContent( + castName = cast.name, + state = castDetailsUiState, + onEvent = { event -> + when (event) { + is CastDetailsEvents.NavigateBack -> { + navigator.popBackStack() + } + } + } + ) +} + +@Composable +fun CastDetailsScreenContent( + castName: String, + state: CastDetailsUiState, + onEvent: (CastDetailsEvents) -> Unit, +) { + Scaffold( + topBar = { + StandardToolbar( + onBackArrowClicked = { + onEvent(CastDetailsEvents.NavigateBack) + }, + title = { + Text( + text = castName, + color = Color.White, + fontWeight = FontWeight.SemiBold + ) + }, + showBackArrow = true + ) + } + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + ) { + if (state.isLoading) { + CircularProgressIndicator( + modifier = Modifier + .align(Alignment.Center) + ) + } + + if (state.isLoading.not() && state.error != null) { + Text( + text = state.error, + color = Color.Red, + modifier = Modifier + .align(Alignment.Center) + ) + } + + if (state.isLoading.not() && state.castDetails != null) { + // Show cast details + } + } + } +} + +@Preview +@Composable +fun CastDetailsScreenPreview() { + CastDetailsScreenContent( + castName = "Tom Cruise", + state = CastDetailsUiState(), + onEvent = {} + ) +} diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsUiState.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsUiState.kt new file mode 100644 index 0000000..4b9a14d --- /dev/null +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsUiState.kt @@ -0,0 +1,22 @@ +/* + * Copyright 2024 Joel Kanyi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.kanyideveloper.muviz.cast.presentation.castdetails + +data class CastDetailsUiState( + val isLoading: Boolean = false, + val castDetails: Any? = null, + val error: String? = null +) diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsViewModel.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsViewModel.kt new file mode 100644 index 0000000..7e7d8ef --- /dev/null +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/castdetails/CastDetailsViewModel.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2024 Joel Kanyi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.kanyideveloper.muviz.cast.presentation.castdetails + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.kanyideveloper.muviz.cast.domain.usecase.GetCastDetailsUseCase +import com.kanyideveloper.muviz.common.util.Resource +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class CastDetailsViewModel @Inject constructor( + private val getCastDetailsUseCase: GetCastDetailsUseCase, +) : ViewModel() { + private val _castDetailsUiState = MutableStateFlow(CastDetailsUiState()) + val castDetailsUiState = _castDetailsUiState.asStateFlow() + fun getCastDetails(id: Int) { + viewModelScope.launch { + _castDetailsUiState.update { + it.copy( + isLoading = true + ) + } + when (val result = getCastDetailsUseCase(id)) { + is Resource.Error -> { + _castDetailsUiState.update { + it.copy( + isLoading = false, + error = result.message + ) + } + } + is Resource.Success -> { + _castDetailsUiState.update { + it.copy( + isLoading = false, + castDetails = result.data + ) + } + } + else -> { + castDetailsUiState + } + } + } + } +} diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/CastsScreen.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/casts/CastsScreen.kt similarity index 91% rename from app/src/main/java/com/kanyideveloper/muviz/cast/presentation/CastsScreen.kt rename to app/src/main/java/com/kanyideveloper/muviz/cast/presentation/casts/CastsScreen.kt index 11dd41e..b3703cb 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/CastsScreen.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/casts/CastsScreen.kt @@ -13,8 +13,9 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.kanyideveloper.muviz.cast.presentation +package com.kanyideveloper.muviz.cast.presentation.casts +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid @@ -45,6 +46,7 @@ import com.kanyideveloper.muviz.common.presentation.components.StandardToolbar import com.kanyideveloper.muviz.common.presentation.theme.MuvizTheme import com.kanyideveloper.muviz.common.presentation.theme.lightGray import com.kanyideveloper.muviz.common.util.Constants +import com.kanyideveloper.muviz.destinations.CastDetailsScreenDestination import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator @@ -61,6 +63,12 @@ fun CastsScreen( is CastsUiEvents.NavigateBack -> { navigator.popBackStack() } + + is CastsUiEvents.NavigateToCastDetails -> { + navigator.navigate( + CastDetailsScreenDestination(event.cast) + ) + } } }, ) @@ -104,7 +112,10 @@ fun CastsScreenContent( CastItem( imageSize = 170.dp, castImageUrl = "${Constants.IMAGE_BASE_UR}/${cast.profilePath}", - castName = cast.name + castName = cast.name, + onClick = { + onEvent(CastsUiEvents.NavigateToCastDetails(cast)) + } ) } } @@ -116,10 +127,13 @@ fun CastItem( modifier: Modifier = Modifier, imageSize: Dp, castName: String, - castImageUrl: String + castImageUrl: String, + onClick: () -> Unit, ) { Column( - modifier = modifier, + modifier = modifier.clickable { + onClick() + }, verticalArrangement = Arrangement.spacedBy(4.dp), horizontalAlignment = Alignment.CenterHorizontally ) { diff --git a/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/CastsUiEvents.kt b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/casts/CastsUiEvents.kt similarity index 77% rename from app/src/main/java/com/kanyideveloper/muviz/cast/presentation/CastsUiEvents.kt rename to app/src/main/java/com/kanyideveloper/muviz/cast/presentation/casts/CastsUiEvents.kt index 14a1f7e..1bc637a 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/CastsUiEvents.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/cast/presentation/casts/CastsUiEvents.kt @@ -13,8 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.kanyideveloper.muviz.cast.presentation +package com.kanyideveloper.muviz.cast.presentation.casts + +import com.kanyideveloper.muviz.cast.domain.model.Cast sealed interface CastsUiEvents { + data class NavigateToCastDetails(val cast: Cast) : + CastsUiEvents + data object NavigateBack : CastsUiEvents } diff --git a/app/src/main/java/com/kanyideveloper/muviz/common/data/network/TMDBApi.kt b/app/src/main/java/com/kanyideveloper/muviz/common/data/network/TMDBApi.kt index ecaf99d..c827e82 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/common/data/network/TMDBApi.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/common/data/network/TMDBApi.kt @@ -127,6 +127,12 @@ interface TMDBApi { @Query("language") language: String = "en" ): CreditsResponse + @GET("credit/{credit_id}") + suspend fun getCreditDetails( + @Path("credit_id") creditId: Int, + @Query("api_key") apiKey: String = API_KEY, + ) + @GET("genre/movie/list") suspend fun getMovieGenres( @Query("api_key") apiKey: String = API_KEY, diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsScreen.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsScreen.kt index bd1755c..880d7cf 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsScreen.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsScreen.kt @@ -16,7 +16,10 @@ package com.kanyideveloper.muviz.filmdetail.presentation import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Text import androidx.compose.runtime.Composable @@ -24,8 +27,11 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import com.kanyideveloper.muviz.destinations.CastDetailsScreenDestination import com.kanyideveloper.muviz.destinations.CastsScreenDestination import com.kanyideveloper.muviz.filmdetail.presentation.common.FilmImageBanner import com.kanyideveloper.muviz.filmdetail.presentation.common.FilmInfo @@ -73,6 +79,10 @@ fun FilmDetailsScreen( event.favorite ) } + + is FilmDetailsUiEvents.NavigateToCastDetails -> { + navigator.navigate(CastDetailsScreenDestination(event.cast)) + } } } ) @@ -85,12 +95,11 @@ fun FilmDetailsScreenContent( onEvents: (FilmDetailsUiEvents) -> Unit, isLiked: Boolean, ) { - val scrollState = rememberLazyListState() Box { if (state.isLoading) { CircularProgressIndicator( - modifier = androidx.compose.ui.Modifier + modifier = Modifier .align(androidx.compose.ui.Alignment.Center) ) } @@ -99,19 +108,30 @@ fun FilmDetailsScreenContent( ((filmType == "tv" && state.tvSeriesDetails != null) || (filmType == "movie" && state.movieDetails != null)) && state.error == null ) { - FilmInfo( - scrollState = scrollState, - filmType = filmType, - state = state, - onEvent = onEvents, - ) - FilmImageBanner( - scrollState = scrollState, - filmType = filmType, - state = state, - isLiked = isLiked, - onEvents = onEvents, - ) + LazyColumn { + item { + FilmImageBanner( + modifier = Modifier + .fillMaxWidth() + .height(500.dp), + filmType = filmType, + state = state, + isLiked = isLiked, + onEvents = onEvents, + ) + } + + item { + FilmInfo( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + filmType = filmType, + state = state, + onEvents = onEvents, + ) + } + } } if ( diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsUiEvents.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsUiEvents.kt index 9b20d87..f514a8c 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsUiEvents.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/FilmDetailsUiEvents.kt @@ -15,6 +15,7 @@ */ package com.kanyideveloper.muviz.filmdetail.presentation +import com.kanyideveloper.muviz.cast.domain.model.Cast import com.kanyideveloper.muviz.cast.domain.model.Credits import com.kanyideveloper.muviz.favorites.data.data.local.Favorite @@ -28,4 +29,5 @@ sealed interface FilmDetailsUiEvents { data class RemoveFromFavorites(val favorite: Favorite) : FilmDetailsUiEvents + data class NavigateToCastDetails(val cast: Cast) : FilmDetailsUiEvents } diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/CastDetails.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/CastDetails.kt index df6a57f..b260370 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/CastDetails.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/CastDetails.kt @@ -15,12 +15,16 @@ */ package com.kanyideveloper.muviz.filmdetail.presentation.common -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.material.CircularProgressIndicator import androidx.compose.material.Icon -import androidx.compose.material.IconButton import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -32,7 +36,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.kanyideveloper.muviz.R -import com.kanyideveloper.muviz.cast.presentation.CastItem +import com.kanyideveloper.muviz.cast.presentation.casts.CastItem import com.kanyideveloper.muviz.common.presentation.theme.primaryPink import com.kanyideveloper.muviz.common.util.Constants import com.kanyideveloper.muviz.filmdetail.presentation.FilmDetailsUiEvents @@ -58,7 +62,8 @@ fun CastDetails( if (state.isLoadingCasts.not() && state.errorCasts == null && state.credits != null) { Row( modifier = Modifier - .fillMaxWidth(), + .fillMaxWidth() + .padding(bottom = 8.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -67,10 +72,7 @@ fun CastDetails( fontWeight = FontWeight.Bold, fontSize = 18.sp, color = Color.White, - modifier = Modifier - .padding( - start = 8.dp - ) + modifier = Modifier, ) Row( @@ -84,28 +86,33 @@ fun CastDetails( color = Color.White ) - IconButton(onClick = { - onEvent(FilmDetailsUiEvents.NavigateToCastsScreen(state.credits)) - }) { - Icon( - painter = painterResource(id = R.drawable.ic_chevron_right), - tint = primaryPink, - contentDescription = null - ) - } + Icon( + modifier = Modifier.clickable { + onEvent(FilmDetailsUiEvents.NavigateToCastsScreen(state.credits)) + }, + painter = painterResource(id = R.drawable.ic_chevron_right), + tint = primaryPink, + contentDescription = null + ) + } } - LazyRow(content = { - items(state.credits.cast) { cast -> - CastItem( - imageSize = 90.dp, - castImageUrl = "${Constants.IMAGE_BASE_UR}/${cast.profilePath}", - castName = cast.name - ) - } - }) + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + content = { + items(state.credits.cast.take(4)) { cast -> + CastItem( + imageSize = 90.dp, + castImageUrl = "${Constants.IMAGE_BASE_UR}/${cast.profilePath}", + castName = cast.name, + onClick = { + onEvent(FilmDetailsUiEvents.NavigateToCastDetails(cast)) + } + ) + } + }) } } } diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmImageBanner.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmImageBanner.kt index e137beb..53e73dc 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmImageBanner.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmImageBanner.kt @@ -15,25 +15,24 @@ */ package com.kanyideveloper.muviz.filmdetail.presentation.common -import android.widget.Toast -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.material.TopAppBar +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +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.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush -import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import coil.compose.rememberImagePainter -import com.google.accompanist.insets.LocalWindowInsets -import com.google.accompanist.insets.statusBarsPadding +import coil.compose.AsyncImage +import coil.request.ImageRequest import com.kanyideveloper.muviz.R import com.kanyideveloper.muviz.common.presentation.theme.AppBarCollapsedHeight import com.kanyideveloper.muviz.common.presentation.theme.AppBarExpendedHeight @@ -43,129 +42,135 @@ import com.kanyideveloper.muviz.common.util.Constants import com.kanyideveloper.muviz.favorites.data.data.local.Favorite import com.kanyideveloper.muviz.filmdetail.presentation.FilmDetailsUiEvents import com.kanyideveloper.muviz.filmdetail.presentation.FilmDetailsUiState -import kotlin.math.max -import kotlin.math.min @Composable fun FilmImageBanner( - scrollState: LazyListState, + modifier: Modifier = Modifier, state: FilmDetailsUiState, onEvents: (FilmDetailsUiEvents) -> Unit, isLiked: Boolean, filmType: String, ) { - val context = LocalContext.current - val filmName = if (filmType == "movie") state.movieDetails?.title.toString() else state.tvSeriesDetails?.name.toString() + val filmName = + if (filmType == "movie") state.movieDetails?.title.toString() else state.tvSeriesDetails?.name.toString() val posterUrl = "${Constants.IMAGE_BASE_UR}/${ if (filmType == "movie") state.movieDetails?.posterPath else state.tvSeriesDetails?.posterPath }" - val rating = if (filmType == "movie") state.movieDetails?.voteAverage?.toFloat()!! else state.tvSeriesDetails?.voteAverage?.toFloat()!! - val releaseDate = if (filmType == "movie") state.movieDetails?.releaseDate.toString() else state.tvSeriesDetails?.firstAirDate.toString() + val rating = + if (filmType == "movie") state.movieDetails?.voteAverage?.toFloat()!! else state.tvSeriesDetails?.voteAverage?.toFloat()!! + val releaseDate = + if (filmType == "movie") state.movieDetails?.releaseDate.toString() else state.tvSeriesDetails?.firstAirDate.toString() val filmId = if (filmType == "movie") state.movieDetails?.id!! else state.tvSeriesDetails?.id!! val imageHeight = AppBarExpendedHeight - AppBarCollapsedHeight - val maxOffset = with(LocalDensity.current) { - imageHeight.roundToPx() - } - LocalWindowInsets.current.systemBars.layoutInsets.top - - val offset = min(scrollState.firstVisibleItemScrollOffset, maxOffset) - - val offsetProgress = max(0f, offset * 3f - 2f * maxOffset) / maxOffset - - TopAppBar( - contentPadding = PaddingValues(), - backgroundColor = primaryDark, - modifier = Modifier - .height( - AppBarExpendedHeight - ) - .offset { IntOffset(x = 0, y = -offset) }, - elevation = if (offset == maxOffset) 4.dp else 0.dp - ) { - Column { - Box { - Image( - painter = rememberImagePainter( - data = posterUrl, - builder = { - placeholder(R.drawable.ic_placeholder) - crossfade(true) - } - ), - modifier = Modifier - .fillMaxSize() - .height(imageHeight) - .graphicsLayer { - alpha = 1f - offsetProgress - }, - contentScale = ContentScale.Crop, - contentDescription = "Movie Banner" - ) + Box(modifier = modifier) { + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(posterUrl) + .crossfade(true) + .build(), + placeholder = painterResource(R.drawable.ic_placeholder), + contentDescription = "Movie Banner", + contentScale = ContentScale.Crop, + modifier = Modifier + .fillMaxSize() + .height(imageHeight), + ) - Box( - modifier = Modifier - .fillMaxSize() - .background( - Brush.verticalGradient( - colorStops = arrayOf( - Pair(0.3f, Transparent), - Pair(1.5f, primaryDark) - ) - ) + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + colorStops = arrayOf( + Pair(0.3f, Transparent), + Pair(1.5f, primaryDark) ) + ) ) - FilmNameAndRating( - filmName = filmName, - rating = rating - ) - } - } + ) + FilmNameAndRating( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + .align(Alignment.BottomCenter), + filmName = filmName, + rating = rating + ) + + + FilmActions( + modifier = Modifier + .padding(16.dp) + .fillMaxWidth() + .align(Alignment.TopStart), + onEvents = onEvents, + isLiked = isLiked, + filmId = filmId, + filmType = filmType, + posterUrl = posterUrl, + filmName = filmName, + releaseDate = releaseDate, + rating = rating + ) } +} +@Composable +private fun FilmActions( + modifier: Modifier = Modifier, + onEvents: (FilmDetailsUiEvents) -> Unit, + isLiked: Boolean, + filmId: Int, + filmType: String, + posterUrl: String, + filmName: String, + releaseDate: String, + rating: Float +) { Row( + modifier = modifier, verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween, - modifier = Modifier - .fillMaxWidth() - .statusBarsPadding() - .padding(horizontal = 10.dp) ) { CircularBackButtons( onClick = { onEvents(FilmDetailsUiEvents.NavigateBack) } ) - - CircularFavoriteButtons( isLiked = isLiked, onClick = { isFav -> if (isFav) { - onEvents(FilmDetailsUiEvents.RemoveFromFavorites( - Favorite( - favorite = false, - mediaId = filmId, - mediaType = filmType, - image = posterUrl, - title = filmName, - releaseDate = releaseDate, - rating = rating + onEvents( + FilmDetailsUiEvents.RemoveFromFavorites( + Favorite( + favorite = false, + mediaId = filmId, + mediaType = filmType, + image = posterUrl, + title = filmName, + releaseDate = releaseDate, + rating = rating + ) ) - )) + ) } else { - onEvents(FilmDetailsUiEvents.AddToFavorites( - Favorite( - favorite = true, - mediaId = filmId, - mediaType = filmType, - image = posterUrl, - title = filmName, - releaseDate = releaseDate, - rating = rating + onEvents( + FilmDetailsUiEvents.AddToFavorites( + Favorite( + favorite = true, + mediaId = filmId, + mediaType = filmType, + image = posterUrl, + title = filmName, + releaseDate = releaseDate, + rating = rating + ) ) - )) + ) } } ) diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmInfo.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmInfo.kt index 49621f1..2ff473a 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmInfo.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmInfo.kt @@ -15,160 +15,106 @@ */ package com.kanyideveloper.muviz.filmdetail.presentation.common -import androidx.compose.foundation.clickable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha -import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.TextLayoutResult +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.unit.IntSize +import androidx.compose.ui.text.withStyle import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.kanyideveloper.muviz.R -import com.kanyideveloper.muviz.common.presentation.theme.AppBarExpendedHeight -import com.kanyideveloper.muviz.common.presentation.theme.primaryPink import com.kanyideveloper.muviz.filmdetail.presentation.FilmDetailsUiEvents import com.kanyideveloper.muviz.filmdetail.presentation.FilmDetailsUiState @Composable fun FilmInfo( - scrollState: LazyListState, + modifier: Modifier = Modifier, filmType: String, state: FilmDetailsUiState, - onEvent: (FilmDetailsUiEvents) -> Unit, + onEvents: (FilmDetailsUiEvents) -> Unit, ) { + Column( + modifier = modifier + .fillMaxWidth() + ) { + Text( + modifier = Modifier.fillMaxWidth(), + text = if (filmType == "movie") state.movieDetails?.overview.toString() else state.tvSeriesDetails?.overview.toString(), + fontSize = 14.sp, + color = Color.White, + ) + Spacer(modifier = Modifier.height(12.dp)) - Spacer(modifier = Modifier.height(10.dp)) - - LazyColumn(contentPadding = PaddingValues(top = AppBarExpendedHeight), state = scrollState) { - item { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(start = 8.dp, end = 8.dp) - ) { - Text( - text = stringResource(R.string.release_date), - fontSize = 14.sp, - fontWeight = FontWeight.SemiBold, - color = Color.White - ) - - Spacer(modifier = Modifier.height(5.dp)) - - Text( - text = if (filmType == "movie") state.movieDetails?.releaseDate.toString() else state.tvSeriesDetails?.firstAirDate.toString(), - fontSize = 12.sp, - fontWeight = FontWeight.ExtraLight, - color = Color.LightGray - ) + Genres( + filmType = filmType, + state = state + ) - Spacer(modifier = Modifier.height(8.dp)) + Spacer(modifier = Modifier.height(8.dp)) - ExpandableText( - text = if (filmType == "movie") state.movieDetails?.overview.toString() else state.tvSeriesDetails?.overview.toString() + Text( + text = buildAnnotatedString { + withStyle(SpanStyle(fontWeight = FontWeight.SemiBold)) { + append( + stringResource(R.string.release_date) + ) + } + append(": ") + append( + if (filmType == "movie") state.movieDetails?.releaseDate.toString() else state.tvSeriesDetails?.firstAirDate.toString() ) - } - } - item { - CastDetails( - state = state, - onEvent = onEvent, - ) - } + }, + fontSize = 12.sp, + color = Color.White + ) + Spacer(modifier = Modifier.height(16.dp)) + CastDetails( + state = state, + onEvent = onEvents, + ) } } @Composable -fun ExpandableText( - text: String, - modifier: Modifier = Modifier, - minimizedMaxLines: Int = 3, +@OptIn(ExperimentalLayoutApi::class) +fun Genres( + filmType: String, + state: FilmDetailsUiState ) { - var cutText by remember(text) { mutableStateOf(null) } - var expanded by remember { mutableStateOf(false) } - val textLayoutResultState = remember { mutableStateOf(null) } - val seeMoreSizeState = remember { mutableStateOf(null) } - val seeMoreOffsetState = remember { mutableStateOf(null) } - - // getting raw values for smart cast - val textLayoutResult = textLayoutResultState.value - val seeMoreSize = seeMoreSizeState.value - val seeMoreOffset = seeMoreOffsetState.value - - LaunchedEffect(text, expanded, textLayoutResult, seeMoreSize) { - val lastLineIndex = minimizedMaxLines - 1 - if (!expanded && textLayoutResult != null && seeMoreSize != null - && lastLineIndex + 1 == textLayoutResult.lineCount - && textLayoutResult.isLineEllipsized(lastLineIndex) - ) { - var lastCharIndex = textLayoutResult.getLineEnd(lastLineIndex, visibleEnd = true) + 1 - var charRect: Rect - do { - lastCharIndex -= 1 - charRect = textLayoutResult.getCursorRect(lastCharIndex) - } while ( - charRect.left > textLayoutResult.size.width - seeMoreSize.width - ) - seeMoreOffsetState.value = Offset(charRect.left, charRect.bottom - seeMoreSize.height) - cutText = text.substring(startIndex = 0, endIndex = lastCharIndex) - } - } - - Box(modifier) { - Text( - color = Color.LightGray, - text = cutText ?: text, - fontSize = 13.sp, - modifier = Modifier - .clickable( - interactionSource = remember { MutableInteractionSource() }, - indication = null + val genres = + if (filmType == "movie") state.movieDetails?.genres else state.tvSeriesDetails?.genres + FlowRow { + if (genres != null) { + for (genre in genres) { + Box( + modifier = Modifier + .padding(end = 8.dp) + .background(Color.Gray, MaterialTheme.shapes.small), + contentAlignment = Alignment.Center ) { - expanded = false - }, - maxLines = if (expanded) Int.MAX_VALUE else minimizedMaxLines, - overflow = TextOverflow.Ellipsis, - onTextLayout = { textLayoutResultState.value = it }, - ) - if (!expanded) { - val density = LocalDensity.current - Text( - // Fixme: Use your app theme color - color = primaryPink, - text = "... See more", - fontWeight = FontWeight.Bold, - fontSize = 13.sp, - onTextLayout = { seeMoreSizeState.value = it.size }, - modifier = Modifier - .then( - if (seeMoreOffset != null) - Modifier.offset( - x = with(density) { seeMoreOffset.x.toDp() }, - y = with(density) { seeMoreOffset.y.toDp() }, - ) - else Modifier + Text( + modifier = Modifier + .padding(horizontal = 8.dp, vertical = 4.dp), + text = genre.name, + style = MaterialTheme.typography.body2, ) - .clickable( - interactionSource = MutableInteractionSource(), - indication = null - ) { - expanded = true - cutText = null - } - .alpha(if (seeMoreOffset != null) 1f else 0f) - ) + } + } } } } diff --git a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmNameAndRating.kt b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmNameAndRating.kt index 3b11776..3741762 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmNameAndRating.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/filmdetail/presentation/common/FilmNameAndRating.kt @@ -15,25 +15,25 @@ */ package com.kanyideveloper.muviz.filmdetail.presentation.common -import androidx.compose.foundation.layout.* +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @Composable fun FilmNameAndRating( + modifier: Modifier = Modifier, filmName: String, rating: Float ) { Row( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 8.dp, vertical = 8.dp), + modifier = modifier, verticalAlignment = Alignment.Bottom ) { Row( diff --git a/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeScreen.kt b/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeScreen.kt index 566b50a..ff42ff2 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeScreen.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeScreen.kt @@ -21,18 +21,34 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.* +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.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.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.CircularProgressIndicator +import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.Icon import androidx.compose.material.IconButton import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme.typography import androidx.compose.material.Scaffold import androidx.compose.material.Text +import androidx.compose.material.pullrefresh.PullRefreshIndicator +import androidx.compose.material.pullrefresh.pullRefresh +import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -120,11 +136,16 @@ fun HomeScreen( is HomeUiEvents.OnFilmOptionSelected -> { viewModel.setSelectedOption(event.item) } + + HomeUiEvents.OnPullToRefresh -> { + viewModel.refreshAllData() + } } } ) } +@OptIn(ExperimentalMaterialApi::class) @Composable fun HomeScreenContent( state: HomeUiState, @@ -176,597 +197,625 @@ fun HomeScreenContent( ) } ) { innerPadding -> - LazyColumn( + val pullRefreshState = rememberPullRefreshState( + refreshing = false, + onRefresh = { + onEvent(HomeUiEvents.OnPullToRefresh) + } + ) + Box( modifier = Modifier + .pullRefresh(state = pullRefreshState) .fillMaxSize() - .padding(innerPadding) ) { - item { - FilmCategory( - items = listOf("Movies", "Tv Shows"), - modifier = Modifier.fillMaxWidth(), - state = state, - onEvent = onEvent, - ) - Spacer(modifier = Modifier.height(4.dp)) - } - item { - Text( - text = "Genres", - fontSize = 22.sp, - fontWeight = FontWeight.Bold, - color = Color.White, - modifier = Modifier.padding(start = 8.dp) - ) + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + contentPadding = PaddingValues(horizontal = 16.dp) + ) { + item { + FilmCategory( + items = listOf("Movies", "Tv Shows"), + modifier = Modifier.fillMaxWidth(), + state = state, + onEvent = onEvent, + ) + Spacer(modifier = Modifier.height(4.dp)) + } + item { + Text( + text = "Genres", + fontSize = 22.sp, + fontWeight = FontWeight.Bold, + color = Color.White, + modifier = Modifier.padding(start = 8.dp) + ) - Spacer(modifier = Modifier.height(5.dp)) + Spacer(modifier = Modifier.height(5.dp)) - } - item { - Genres( - state = state, - onEvent = onEvent, - ) - } - - item(content = { - Text(text = "Trending today", color = Color.White, fontSize = 18.sp) - - Spacer(modifier = Modifier.height(8.dp)) - }) - item(content = { - Spacer(modifier = Modifier.height(5.dp)) - Box( - Modifier - .fillMaxWidth() - .height(220.dp), - contentAlignment = Alignment.Center - ) { - LazyRow(content = { - - if (state.selectedFilmOption == "Tv Shows") { - items( - count = trendingTvSeries.itemCount - ) { index -> - val film = trendingTvSeries[index] - FilmItem( - modifier = Modifier - .height(220.dp) - .width(250.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "tv" - ) - ) - }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } - } else { - items( - count = trendingMovies.itemCount - ) { index -> - val film = trendingMovies[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(230.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "movie" - ) - ) - }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } - } - - if (trendingMovies.loadState.append == LoadState.Loading) { - item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally) - ) - } - } - } + } + item { + Genres( + state = state, + onEvent = onEvent, ) + } - trendingMovies.apply { - loadState - when (loadState.refresh) { - is LoadState.Loading -> { - CircularProgressIndicator( - modifier = Modifier, - color = primaryPink, - strokeWidth = 2.dp - ) + item(content = { + Text(text = "Trending today", color = Color.White, fontSize = 18.sp) + + Spacer(modifier = Modifier.height(8.dp)) + }) + item(content = { + Spacer(modifier = Modifier.height(5.dp)) + Box( + Modifier + .fillMaxWidth() + .height(220.dp), + contentAlignment = Alignment.Center + ) { + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + content = { + + if (state.selectedFilmOption == "Tv Shows") { + items( + count = trendingTvSeries.itemCount + ) { index -> + val film = trendingTvSeries[index] + FilmItem( + modifier = Modifier + .height(220.dp) + .width(250.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "tv" + ) + ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } else { + items( + count = trendingMovies.itemCount + ) { index -> + val film = trendingMovies[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(230.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "movie" + ) + ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } + + if (trendingMovies.loadState.append == LoadState.Loading) { + item { + CircularProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + ) + } + } } + ) - is LoadState.Error -> { - val e = trendingMovies.loadState.refresh as LoadState.Error - Text( - text = when (e.error) { - is HttpException -> { - "Oops, something went wrong!" - } - - is IOException -> { - "Couldn't reach server, check your internet connection!" - } - - else -> { - "Unknown error occurred" - } - }, - textAlign = TextAlign.Center, - color = primaryPink - ) - } + trendingMovies.apply { + loadState + when (loadState.refresh) { + is LoadState.Loading -> { + CircularProgressIndicator( + modifier = Modifier, + color = primaryPink, + strokeWidth = 2.dp + ) + } + + is LoadState.Error -> { + val e = trendingMovies.loadState.refresh as LoadState.Error + Text( + text = when (e.error) { + is HttpException -> { + "Oops, something went wrong!" + } + + is IOException -> { + "Couldn't reach server, check your internet connection!" + } + + else -> { + "Unknown error occurred" + } + }, + textAlign = TextAlign.Center, + color = primaryPink + ) + } - else -> { + else -> { + } } } } + }) + + item { + Spacer(modifier = Modifier.height(8.dp)) + Text(text = "Popular", color = Color.White, fontSize = 18.sp) } - }) - item { - Spacer(modifier = Modifier.height(8.dp)) - Text(text = "Popular", color = Color.White, fontSize = 18.sp) - } + item { + Spacer(modifier = Modifier.height(5.dp)) - item { - Spacer(modifier = Modifier.height(5.dp)) - - Box( - Modifier - .fillMaxWidth() - .height(210.dp), - contentAlignment = Alignment.Center - ) { - LazyRow { - if (state.selectedFilmOption == "Tv Shows") { - items( - count = popularTvSeries.itemCount - ) { index -> - val film = popularTvSeries[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(130.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "tv" + Box( + Modifier + .fillMaxWidth() + .height(210.dp), + contentAlignment = Alignment.Center + ) { + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + if (state.selectedFilmOption == "Tv Shows") { + items( + count = popularTvSeries.itemCount + ) { index -> + val film = popularTvSeries[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(130.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "tv" + ) ) - ) - }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } - } else { - items( - count = popularMovies.itemCount - ) { index -> - val film = popularMovies[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(130.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "movie" + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } else { + items( + count = popularMovies.itemCount + ) { index -> + val film = popularMovies[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(130.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "movie" + ) ) - ) - }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } } - } - if (popularMovies.loadState.append == LoadState.Loading) { - item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally) - ) + if (popularMovies.loadState.append == LoadState.Loading) { + item { + CircularProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + ) + } } } - } - popularMovies.apply { - loadState - when (loadState.refresh) { - is LoadState.Loading -> { - CircularProgressIndicator( - modifier = Modifier, - color = primaryPink, - strokeWidth = 2.dp - ) - } - - is LoadState.Error -> { - val e = popularMovies.loadState.refresh as LoadState.Error - Text( - text = when (e.error) { - is HttpException -> { - "Oops, something went wrong!" - } - - is IOException -> { - "Couldn't reach server, check your internet connection!" - } - - else -> { - "Unknown error occurred" - } - }, - textAlign = TextAlign.Center, - color = primaryPink - ) - } + popularMovies.apply { + loadState + when (loadState.refresh) { + is LoadState.Loading -> { + CircularProgressIndicator( + modifier = Modifier, + color = primaryPink, + strokeWidth = 2.dp + ) + } + + is LoadState.Error -> { + val e = popularMovies.loadState.refresh as LoadState.Error + Text( + text = when (e.error) { + is HttpException -> { + "Oops, something went wrong!" + } + + is IOException -> { + "Couldn't reach server, check your internet connection!" + } + + else -> { + "Unknown error occurred" + } + }, + textAlign = TextAlign.Center, + color = primaryPink + ) + } - else -> { + else -> { + } } } } - } - - Spacer(modifier = Modifier.height(10.dp)) - } - item { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = if (state.selectedFilmOption == "Tv Shows") { - "On Air" - } else { - "Upcoming" - }, - color = Color.White, - fontSize = 18.sp - ) - } + Spacer(modifier = Modifier.height(10.dp)) + } - item { - Spacer(modifier = Modifier.height(5.dp)) - - Box( - Modifier - .fillMaxWidth() - .height(210.dp), - contentAlignment = Alignment.Center - ) { - LazyRow(content = { - if (state.selectedFilmOption == "Tv Shows") { - items( - count = onAirTvSeries.itemCount - ) { index -> - val film = onAirTvSeries[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(130.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "tv" - ) - ) - }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } + item { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = if (state.selectedFilmOption == "Tv Shows") { + "On Air" } else { - items( - count = upcomingMovies.itemCount - ) { index -> - val film = upcomingMovies[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(130.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "movie" - ) - ) - }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } - } - if (upcomingMovies.loadState.append == LoadState.Loading) { - item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally) - ) - } - } - } + "Upcoming" + }, + color = Color.White, + fontSize = 18.sp ) + } - upcomingMovies.apply { - loadState - when (loadState.refresh) { - is LoadState.Loading -> { - CircularProgressIndicator( - modifier = Modifier, - color = primaryPink, - strokeWidth = 2.dp - ) - } + item { + Spacer(modifier = Modifier.height(5.dp)) - is LoadState.Error -> { - val e = upcomingMovies.loadState.refresh as LoadState.Error - Text( - text = when (e.error) { - is HttpException -> { - "Oops, something went wrong!" - } - - is IOException -> { - "Couldn't reach server, check your internet connection!" - } - - else -> { - "Unknown error occurred" - } - }, - textAlign = TextAlign.Center, - color = primaryPink - ) + Box( + Modifier + .fillMaxWidth() + .height(210.dp), + contentAlignment = Alignment.Center + ) { + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + content = { + if (state.selectedFilmOption == "Tv Shows") { + items( + count = onAirTvSeries.itemCount + ) { index -> + val film = onAirTvSeries[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(130.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "tv" + ) + ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } else { + items( + count = upcomingMovies.itemCount + ) { index -> + val film = upcomingMovies[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(130.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "movie" + ) + ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } + if (upcomingMovies.loadState.append == LoadState.Loading) { + item { + CircularProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + ) + } + } } + ) - else -> {} + upcomingMovies.apply { + loadState + when (loadState.refresh) { + is LoadState.Loading -> { + CircularProgressIndicator( + modifier = Modifier, + color = primaryPink, + strokeWidth = 2.dp + ) + } + + is LoadState.Error -> { + val e = upcomingMovies.loadState.refresh as LoadState.Error + Text( + text = when (e.error) { + is HttpException -> { + "Oops, something went wrong!" + } + + is IOException -> { + "Couldn't reach server, check your internet connection!" + } + + else -> { + "Unknown error occurred" + } + }, + textAlign = TextAlign.Center, + color = primaryPink + ) + } + + else -> {} + } } } + + Spacer(modifier = Modifier.height(10.dp)) } - Spacer(modifier = Modifier.height(10.dp)) - } + item { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = if (state.selectedFilmOption == "Tv Shows") { + "Airing today" + } else { + "Now playing" + }, + color = Color.White, + fontSize = 18.sp + ) + } - item { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = if (state.selectedFilmOption == "Tv Shows") { - "Airing today" - } else { - "Now playing" - }, - color = Color.White, - fontSize = 18.sp - ) - } + item { + Spacer(modifier = Modifier.height(5.dp)) - item { - Spacer(modifier = Modifier.height(5.dp)) - - Box( - Modifier - .fillMaxWidth() - .height(210.dp), - contentAlignment = Alignment.Center - ) { - LazyRow(content = { - if (state.selectedFilmOption == "Tv Shows") { - items( - count = airingTodayTvSeries.itemCount - ) { index -> - val film = airingTodayTvSeries[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(130.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "tv" - ) - ) - }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } - } else { - items( - count = nowPlayingMovies.itemCount - ) { index -> - val film = nowPlayingMovies[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(130.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "movie" - ) - ) + Box( + Modifier + .fillMaxWidth() + .height(210.dp), + contentAlignment = Alignment.Center + ) { + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + content = { + if (state.selectedFilmOption == "Tv Shows") { + items( + count = airingTodayTvSeries.itemCount + ) { index -> + val film = airingTodayTvSeries[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(130.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "tv" + ) + ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } else { + items( + count = nowPlayingMovies.itemCount + ) { index -> + val film = nowPlayingMovies[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(130.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "movie" + ) + ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } + if (nowPlayingMovies.loadState.append == LoadState.Loading) { + item { + CircularProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + ) + } + } + }) + + nowPlayingMovies.apply { + loadState + when (loadState.refresh) { + is LoadState.Loading -> { + CircularProgressIndicator( + modifier = Modifier, + color = primaryPink, + strokeWidth = 2.dp + ) + } + + is LoadState.Error -> { + val e = nowPlayingMovies.loadState.refresh as LoadState.Error + Text( + text = when (e.error) { + is HttpException -> { + "Oops, something went wrong!" + } + + is IOException -> { + "Couldn't reach server, check your internet connection!" + } + + else -> { + "Unknown error occurred" + } }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } - } - if (nowPlayingMovies.loadState.append == LoadState.Loading) { - item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally) - ) - } - } - }) - - nowPlayingMovies.apply { - loadState - when (loadState.refresh) { - is LoadState.Loading -> { - CircularProgressIndicator( - modifier = Modifier, - color = primaryPink, - strokeWidth = 2.dp - ) - } + textAlign = TextAlign.Center, + color = primaryPink + ) + } - is LoadState.Error -> { - val e = nowPlayingMovies.loadState.refresh as LoadState.Error - Text( - text = when (e.error) { - is HttpException -> { - "Oops, something went wrong!" - } - - is IOException -> { - "Couldn't reach server, check your internet connection!" - } - - else -> { - "Unknown error occurred" - } - }, - textAlign = TextAlign.Center, - color = primaryPink - ) + else -> {} } - - else -> {} } } + + Spacer(modifier = Modifier.height(10.dp)) } - Spacer(modifier = Modifier.height(10.dp)) - } + item { + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = "Top rated", + color = Color.White, + fontSize = 18.sp + ) + } - item { - Spacer(modifier = Modifier.height(8.dp)) - Text( - text = "Top rated", - color = Color.White, - fontSize = 18.sp - ) - } + item { + Spacer(modifier = Modifier.height(5.dp)) - item { - Spacer(modifier = Modifier.height(5.dp)) - - Box( - Modifier - .fillMaxWidth() - .height(210.dp), - contentAlignment = Alignment.Center - ) { - LazyRow(content = { - if (state.selectedFilmOption == "Tv Shows") { - items( - count = topRatedTvSeries.itemCount - ) { index -> - val film = topRatedTvSeries[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(130.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "tv" - ) - ) - }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } - } else { - items( - count = topRatedMovies.itemCount - ) { index -> - val film = topRatedMovies[index] - FilmItem( - modifier = Modifier - .height(200.dp) - .width(130.dp) - .clickable { - onEvent( - HomeUiEvents.NavigateToFilmDetails( - id = film?.id!!, - filmType = "movie" - ) - ) + Box( + Modifier + .fillMaxWidth() + .height(210.dp), + contentAlignment = Alignment.Center + ) { + LazyRow( + horizontalArrangement = Arrangement.spacedBy(8.dp), + content = { + if (state.selectedFilmOption == "Tv Shows") { + items( + count = topRatedTvSeries.itemCount + ) { index -> + val film = topRatedTvSeries[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(130.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "tv" + ) + ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } else { + items( + count = topRatedMovies.itemCount + ) { index -> + val film = topRatedMovies[index] + FilmItem( + modifier = Modifier + .height(200.dp) + .width(130.dp) + .clickable { + onEvent( + HomeUiEvents.NavigateToFilmDetails( + id = film?.id!!, + filmType = "movie" + ) + ) + }, + imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" + ) + } + } + if (topRatedMovies.loadState.append == LoadState.Loading) { + item { + CircularProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .wrapContentWidth(Alignment.CenterHorizontally) + ) + } + } + }) + + topRatedMovies.apply { + loadState + when (loadState.refresh) { + is LoadState.Loading -> { + CircularProgressIndicator( + modifier = Modifier, + color = primaryPink, + strokeWidth = 2.dp + ) + } + + is LoadState.Error -> { + val e = topRatedMovies.loadState.refresh as LoadState.Error + Text( + text = when (e.error) { + is HttpException -> { + "Oops, something went wrong!" + } + + is IOException -> { + "Couldn't reach server, check your internet connection!" + } + + else -> { + "Unknown error occurred" + } }, - imageUrl = "$IMAGE_BASE_UR/${film?.posterPath}" - ) - } - } - if (topRatedMovies.loadState.append == LoadState.Loading) { - item { - CircularProgressIndicator( - modifier = Modifier - .fillMaxWidth() - .wrapContentWidth(Alignment.CenterHorizontally) - ) - } - } - }) - - topRatedMovies.apply { - loadState - when (loadState.refresh) { - is LoadState.Loading -> { - CircularProgressIndicator( - modifier = Modifier, - color = primaryPink, - strokeWidth = 2.dp - ) - } + textAlign = TextAlign.Center, + color = primaryPink + ) + } - is LoadState.Error -> { - val e = topRatedMovies.loadState.refresh as LoadState.Error - Text( - text = when (e.error) { - is HttpException -> { - "Oops, something went wrong!" - } - - is IOException -> { - "Couldn't reach server, check your internet connection!" - } - - else -> { - "Unknown error occurred" - } - }, - textAlign = TextAlign.Center, - color = primaryPink - ) + else -> {} } - - else -> {} } } - } - Spacer(modifier = Modifier.height(10.dp)) + Spacer(modifier = Modifier.height(10.dp)) + } } + PullRefreshIndicator( + refreshing = false, + pullRefreshState, + Modifier.align(Alignment.TopCenter), + ) } } } @@ -787,7 +836,7 @@ fun FilmItem( contentScale = ContentScale.Crop, modifier = modifier .fillMaxSize() - .clip(shape = MaterialTheme.shapes.medium), + .clip(shape = MaterialTheme.shapes.medium) ) } diff --git a/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeUiEvents.kt b/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeUiEvents.kt index 0b3c30e..9a1db7a 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeUiEvents.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeUiEvents.kt @@ -20,6 +20,8 @@ import com.kanyideveloper.muviz.genre.domain.model.Genre sealed interface HomeUiEvents { data object OnSearchClick : HomeUiEvents data object NavigateBack : HomeUiEvents + data object OnPullToRefresh : HomeUiEvents + data class NavigateToFilmDetails( val id: Int, val filmType: String diff --git a/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeViewModel.kt b/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeViewModel.kt index 3978865..4df1ddd 100644 --- a/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeViewModel.kt +++ b/app/src/main/java/com/kanyideveloper/muviz/home/presentation/HomeViewModel.kt @@ -282,4 +282,18 @@ class HomeViewModel @Inject constructor( ) } } + + fun refreshAllData() { + getTrendingMovies(homeUiState.value.selectedGenre?.id) + getNowPayingMovies(homeUiState.value.selectedGenre?.id) + getUpcomingMovies(homeUiState.value.selectedGenre?.id) + getTopRatedMovies(homeUiState.value.selectedGenre?.id) + getPopularMovies(homeUiState.value.selectedGenre?.id) + getPopularTvSeries(homeUiState.value.selectedGenre?.id) + getAiringTodayTvSeries(homeUiState.value.selectedGenre?.id) + getTrendingTvSeries(homeUiState.value.selectedGenre?.id) + getOnTheAirTvSeries(homeUiState.value.selectedGenre?.id) + getTopRatedTvSeries(homeUiState.value.selectedGenre?.id) + getOnTheAirTvSeries(homeUiState.value.selectedGenre?.id) + } }