Skip to content

Commit

Permalink
Wrap all ViewModels/Screens with UiState and Handlers for loading/err…
Browse files Browse the repository at this point in the history
…or/success handling
  • Loading branch information
RajashekarRaju committed Dec 23, 2024
1 parent 85ba4b8 commit e788ae0
Show file tree
Hide file tree
Showing 23 changed files with 283 additions and 309 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class HomeScreenUITest {
@get:Rule
val composeTestRule = createComposeRule()

private val mockHomeUIState: HomeUIState = HomeUIState(
private val mockHomeUIState: HomeData = HomeData(
popularPersonList = popularPersonLists,
trendingPersonList = trendingPersonLists,
isFetchingPersons = false,
Expand All @@ -45,7 +45,7 @@ class HomeScreenUITest {
navigateToAbout: () -> Unit = { },
navigateToSearchBySearchType: SearchType = SearchType.Persons,
homeSheetUIState: HomeSheetUIState = HomeSheetUIState(),
homeUIState: HomeUIState = mockHomeUIState
data: HomeData = mockHomeUIState
) {
HomeScreenUI(
modifier = Modifier,
Expand All @@ -55,7 +55,7 @@ class HomeScreenUITest {
navigateToSearch = navigateToSearch,
navigateToAbout = navigateToAbout,
navigateToSearchBySearchType = navigateToSearchBySearchType,
uiState = homeUIState,
data = data,
sheetUiState = homeSheetUIState,
updateHomeSearchType = updateHomeSearchType,
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ import kotlinx.coroutines.Job
@Composable
internal fun ActorDetailsContent(
navigateUp: () -> Unit,
detailUIState: ActorDetailsUIState,
data: ActorDetailsData,
openActorDetailsBottomSheet: () -> Job,
getSelectedMovieDetails: (Int) -> Unit,
showFab: MutableState<Boolean>
) {
val actorData = detailUIState.actorData
val actorData = data.actorData
val listState = rememberLazyListState()

/** Sticky actor details content */
Expand All @@ -44,7 +44,7 @@ internal fun ActorDetailsContent(
}
item {
ActorCastedMovies(
detailUIState,
data,
openActorDetailsBottomSheet,
getSelectedMovieDetails
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,36 +4,27 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.hilt.navigation.compose.hiltViewModel
import com.developersbreach.composeactors.ui.screens.home.HomeScreen
import com.developersbreach.composeactors.ui.screens.search.SearchScreen
import com.developersbreach.composeactors.ui.components.UiStateHandler


/**
* Shows details of user selected actor.
*
* @param viewModel to manage ui state of [ActorDetailsScreen]
* @param navigateUp navigates user to previous screen.
*
* This destination can be accessed from [HomeScreen] & [SearchScreen].
*/
@Composable
internal fun ActorDetailsScreen(
viewModel: ActorDetailsViewModel = hiltViewModel(),
navigateToSelectedMovie: (Int) -> Unit,
navigateUp: () -> Unit,
) {
val detailUIState = viewModel.detailUIState
val sheetUIState = viewModel.sheetUIState
val movieId by viewModel.isFavoriteMovie.observeAsState()

ActorDetailsUI(
detailUIState = detailUIState,
sheetUIState = sheetUIState,
navigateToSelectedMovie = navigateToSelectedMovie,
isFavoriteMovie = movieId != 0 && movieId != null,
navigateUp = navigateUp,
getSelectedMovieDetails = { viewModel.getSelectedMovieDetails(it) },
addActorToFavorites = { viewModel.addActorToFavorites() },
removeActorFromFavorites = { viewModel.removeActorFromFavorites() }
)
UiStateHandler(
uiState = viewModel.detailUIState
) { data ->
ActorDetailsUI(
data = data,
sheetUIState = viewModel.sheetUIState,
navigateToSelectedMovie = navigateToSelectedMovie,
isFavoriteMovie = movieId != 0 && movieId != null,
navigateUp = navigateUp,
getSelectedMovieDetails = { viewModel.getSelectedMovieDetails(it) },
addActorToFavorites = { viewModel.addActorToFavorites(data.actorData) },
removeActorFromFavorites = { viewModel.removeActorFromFavorites(data.actorData) }
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import com.developersbreach.composeactors.ui.theme.ComposeActorsTheme
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun ActorDetailsUI(
detailUIState: ActorDetailsUIState,
data: ActorDetailsData,
sheetUIState: ActorDetailsSheetUIState,
navigateToSelectedMovie: (Int) -> Unit,
isFavoriteMovie: Boolean,
Expand All @@ -37,7 +37,7 @@ internal fun ActorDetailsUI(
removeActorFromFavorites: () -> Unit
) {
val showFab = rememberSaveable { mutableStateOf(true) }
val actorProfileUrl = "${detailUIState.actorData?.profileUrl}"
val actorProfileUrl = "${data.actorData?.profileUrl}"
val modalSheetState = modalBottomSheetState()
val openActorDetailsBottomSheet = manageModalBottomSheet(
modalSheetState = modalSheetState
Expand Down Expand Up @@ -66,20 +66,20 @@ internal fun ActorDetailsUI(
// Custom top app bar
ActorDetailsTopAppBar(
navigateUp = navigateUp,
title = "${detailUIState.actorData?.personName}"
title = "${data.actorData?.personName}"
)

// Main details content
ActorDetailsContent(
navigateUp = navigateUp,
detailUIState = detailUIState,
data = data,
openActorDetailsBottomSheet = openActorDetailsBottomSheet,
getSelectedMovieDetails = getSelectedMovieDetails,
showFab = showFab
)
}
// Progress bar
ShowProgressIndicator(isLoadingData = detailUIState.isFetchingDetails)
ShowProgressIndicator(isLoadingData = data.isFetchingDetails)
}
}

Expand All @@ -98,7 +98,7 @@ internal fun ActorDetailsUI(
private fun ActorDetailsUIDarkPreview() {
ComposeActorsTheme(darkTheme = true) {
ActorDetailsUI(
detailUIState = ActorDetailsUIState(
data = ActorDetailsData(
castList = fakeMovieList(),
actorData = fakePersonDetail,
isFetchingDetails = false
Expand All @@ -119,7 +119,7 @@ private fun ActorDetailsUIDarkPreview() {
private fun ActorDetailsUILightPreview() {
ComposeActorsTheme(darkTheme = false) {
ActorDetailsUI(
detailUIState = ActorDetailsUIState(
data = ActorDetailsData(
castList = fakeMovieList(),
actorData = fakePersonDetail,
isFetchingDetails = false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,12 @@ import com.developersbreach.composeactors.data.person.model.PersonDetail
import com.developersbreach.composeactors.data.movie.model.Movie
import com.developersbreach.composeactors.data.movie.model.MovieDetail

// TODO - create a sealed class to contains the different states

/**
* Models the UI state for the [ActorDetailsScreen] screen.
*/
data class ActorDetailsUIState(
data class ActorDetailsData(
val castList: List<Movie> = listOf(),
val actorData: PersonDetail? = null,
val isFetchingDetails: Boolean = false,
)

/**
* Models the UI state for the SheetContentMovieDetails modal sheet.
*/
data class ActorDetailsSheetUIState(
val selectedMovieDetails: MovieDetail? = null
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import arrow.core.raise.either
import com.developersbreach.composeactors.data.movie.repository.MovieRepository
import com.developersbreach.composeactors.data.person.model.PersonDetail
import com.developersbreach.composeactors.data.person.model.toFavoritePerson
import com.developersbreach.composeactors.data.movie.repository.MovieRepository
import com.developersbreach.composeactors.data.person.repository.PersonRepository
import com.developersbreach.composeactors.ui.components.UiState
import com.developersbreach.composeactors.ui.navigation.AppDestinations.ACTOR_DETAIL_ID_KEY
import dagger.hilt.android.lifecycle.HiltViewModel
import java.io.IOException
import javax.inject.Inject
import kotlinx.coroutines.launch
import timber.log.Timber
Expand All @@ -30,7 +31,7 @@ class ActorDetailsViewModel @Inject constructor(

private val personId: Int = checkNotNull(savedStateHandle[ACTOR_DETAIL_ID_KEY])

var detailUIState by mutableStateOf(ActorDetailsUIState(actorData = null))
var detailUIState: UiState<ActorDetailsData> by mutableStateOf(UiState.Loading)
private set

var sheetUIState by mutableStateOf(ActorDetailsSheetUIState())
Expand All @@ -40,68 +41,62 @@ class ActorDetailsViewModel @Inject constructor(

init {
viewModelScope.launch {
try {
startFetchingDetails()
} catch (e: IOException) {
Timber.e("$e")
}
startFetchingDetails()
}
}

private suspend fun startFetchingDetails() {
detailUIState = ActorDetailsUIState(isFetchingDetails = true, actorData = null)
val actorData = personRepository.getPersonDetails(personId)
val castData = personRepository.getCastDetails(personId)
detailUIState = ActorDetailsUIState(
castList = castData,
actorData = actorData,
isFetchingDetails = false
detailUIState = UiState.Success(ActorDetailsData(isFetchingDetails = true))
detailUIState = either {
ActorDetailsData(
castList = personRepository.getCastDetails(personId).bind(),
actorData = personRepository.getPersonDetails(personId).bind(),
isFetchingDetails = false
)
}.fold(
ifLeft = { UiState.Error(it) },
ifRight = { UiState.Success(it) },
)
}

/**
* @param movieId for querying selected movie details.
* This function will be triggered only when user clicks any movie items.
* Updates the data values to show in modal sheet.
*/
fun getSelectedMovieDetails(
movieId: Int?
) {
if (movieId == null) {
Timber.e("Failed to getSelectedMovieDetails, since id was null")
return
}
viewModelScope.launch {
try {
movieId?.let { id ->
val movieData = movieRepository.getMovieDetails(id)
sheetUIState = ActorDetailsSheetUIState(selectedMovieDetails = movieData)
}
} catch (e: IOException) {
Timber.e("$e")
}
movieRepository.getMovieDetails(
movieId = movieId
).fold(
ifLeft = { UiState.Error(it) },
ifRight = { sheetUIState = ActorDetailsSheetUIState(selectedMovieDetails = it) }
)
}
}

fun addActorToFavorites() {
fun addActorToFavorites(
actor: PersonDetail?
) {
if (actor == null) {
Timber.e("Actor was null while adding to favorite operation.")
return
}
viewModelScope.launch {
val actor: PersonDetail? = detailUIState.actorData
if (actor != null) {
personRepository.addPersonToFavorite(
actor.toFavoritePerson()
)
} else {
Timber.e("Id of ${actor} was null while adding to favorite operation.")
}
personRepository.addPersonToFavorite(actor.toFavoritePerson())
}
}

fun removeActorFromFavorites() {
fun removeActorFromFavorites(actor: PersonDetail?) {
if (actor == null) {
Timber.e("Actor was null while delete operation.")
return
}
viewModelScope.launch {
val actor: PersonDetail? = detailUIState.actorData
if (actor != null) {
personRepository.deleteSelectedFavoritePerson(
actor.toFavoritePerson()
)
} else {
Timber.e("Id of ${actor} was null while delete operation.")
}
personRepository.deleteSelectedFavoritePerson(
actor.toFavoritePerson()
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,19 @@ import com.developersbreach.composeactors.R
import com.developersbreach.composeactors.data.movie.model.Movie
import com.developersbreach.composeactors.ui.components.CategoryTitle
import com.developersbreach.composeactors.ui.components.LoadNetworkImage
import com.developersbreach.composeactors.ui.screens.actorDetails.ActorDetailsUIState
import com.developersbreach.composeactors.ui.screens.actorDetails.ActorDetailsData
import kotlinx.coroutines.Job

/**
* Cast icon and title on top and list below.
*/
@Composable
internal fun ActorCastedMovies(
detailUIState: ActorDetailsUIState,
data: ActorDetailsData,
openActorDetailsBottomSheet: () -> Job,
getSelectedMovieDetails: (Int) -> Unit
) {
val cast: List<Movie> = detailUIState.castList
val cast: List<Movie> = data.castList

Row(
verticalAlignment = Alignment.CenterVertically
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
import androidx.hilt.navigation.compose.hiltViewModel
import com.developersbreach.composeactors.ui.components.UiStateHandler
import com.developersbreach.composeactors.ui.screens.search.SearchType

@Composable
Expand All @@ -18,18 +19,22 @@ fun HomeScreen(
) {
val navigateToSearchBySearchType by viewModel.updateHomeSearchType.observeAsState(SearchType.Persons)

HomeScreenUI(
modifier = Modifier,
navigateToFavorite = navigateToFavorite,
navigateToSearch = navigateToSearch,
navigateToAbout = navigateToAbout,
navigateToSearchBySearchType = navigateToSearchBySearchType,
navigateToSelectedPerson = navigateToSelectedPerson,
navigateToSelectedMovie = navigateToSelectedMovie,
uiState = viewModel.uiState,
sheetUiState = viewModel.sheetUiState,
updateHomeSearchType = { searchType: SearchType ->
viewModel.updateHomeSearchType(searchType)
}
)
UiStateHandler(
uiState = viewModel.uiState
) { data ->
HomeScreenUI(
modifier = Modifier,
navigateToFavorite = navigateToFavorite,
navigateToSearch = navigateToSearch,
navigateToAbout = navigateToAbout,
navigateToSearchBySearchType = navigateToSearchBySearchType,
navigateToSelectedPerson = navigateToSelectedPerson,
navigateToSelectedMovie = navigateToSelectedMovie,
data = data,
sheetUiState = viewModel.sheetUiState,
updateHomeSearchType = { searchType: SearchType ->
viewModel.updateHomeSearchType(searchType)
}
)
}
}
Loading

0 comments on commit e788ae0

Please sign in to comment.