Skip to content

Commit e320da2

Browse files
Merge pull request #12 from intive-FDV/feature/changes_in_viewmodels
Feature/changes in viewmodels
2 parents 84457c0 + 423ea0e commit e320da2

File tree

11 files changed

+183
-49
lines changed

11 files changed

+183
-49
lines changed

app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import com.intive.tmdbandroid.model.converter.CreatedByConverter
88
import com.intive.tmdbandroid.model.converter.GenreConverter
99

1010
@Database(entities = [(TVShowORMEntity::class)], version = 1, exportSchema = false)
11-
@TypeConverters(CreatedByConverter::class,GenreConverter::class)
11+
@TypeConverters(CreatedByConverter::class, GenreConverter::class)
1212
abstract class LocalStorage : RoomDatabase() {
1313
abstract fun tvShowDao(): Dao
1414
}

app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import com.intive.tmdbandroid.details.viewmodel.DetailsViewModel
2323
import com.intive.tmdbandroid.model.TVShow
2424
import dagger.hilt.android.AndroidEntryPoint
2525
import kotlinx.coroutines.flow.collect
26+
import kotlinx.coroutines.flow.collectLatest
27+
import kotlinx.coroutines.launch
2628
import java.text.SimpleDateFormat
2729
import java.util.*
2830

@@ -47,8 +49,8 @@ class DetailFragment : Fragment() {
4749
): View {
4850
val binding = FragmentDetailBinding.inflate(inflater, container, false)
4951

50-
collectDataFromViewModel(binding)
51-
setupToolbar(binding)
52+
collectTVShowDetailFromViewModel(binding)
53+
collectWatchlistDataFromViewModel(binding)
5254

5355
return binding.root
5456
}
@@ -65,7 +67,7 @@ class DetailFragment : Fragment() {
6567
Glide.get(requireContext()).clearMemory()
6668
}
6769

68-
private fun collectDataFromViewModel(binding: FragmentDetailBinding) {
70+
private fun collectTVShowDetailFromViewModel(binding: FragmentDetailBinding) {
6971
binding.coordinatorContainerDetail.visibility = View.INVISIBLE
7072
lifecycleScope.launchWhenCreated {
7173
viewModel.uiState.collect { state ->
@@ -89,6 +91,30 @@ class DetailFragment : Fragment() {
8991
}
9092
}
9193

94+
private fun collectWatchlistDataFromViewModel(binding: FragmentDetailBinding) {
95+
lifecycleScope.launch {
96+
viewModel.watchlistUIState.collectLatest {
97+
when (it) {
98+
is State.Success -> {
99+
binding.layoutErrorDetail.errorContainer.visibility = View.GONE
100+
binding.layoutLoadingDetail.progressBar.visibility = View.GONE
101+
selectOrUnselectWatchlistFav(binding, it.data)
102+
isSaveOnWatchlist = it.data
103+
}
104+
State.Error -> {
105+
binding.layoutLoadingDetail.progressBar.visibility = View.GONE
106+
binding.layoutErrorDetail.errorContainer.visibility = View.VISIBLE
107+
binding.coordinatorContainerDetail.visibility = View.VISIBLE
108+
}
109+
State.Loading -> {
110+
binding.layoutErrorDetail.errorContainer.visibility = View.GONE
111+
binding.layoutLoadingDetail.progressBar.visibility = View.VISIBLE
112+
}
113+
}
114+
}
115+
}
116+
}
117+
92118
private fun setupUI(binding: FragmentDetailBinding, tvShow: TVShow) {
93119

94120
setImages(binding, tvShow)
@@ -97,6 +123,8 @@ class DetailFragment : Fragment() {
97123

98124
setPercentageToCircularPercentage(binding, tvShow.vote_average)
99125

126+
setupToolbar(binding, tvShow)
127+
100128
binding.toolbar.title = tvShow.name
101129

102130
binding.statusDetailTextView.text = tvShow.status
@@ -126,6 +154,8 @@ class DetailFragment : Fragment() {
126154

127155
binding.overviewDetailTextView.text = tvShow.overview
128156
binding.coordinatorContainerDetail.visibility = View.VISIBLE
157+
158+
tvShowId?.let { viewModel.existAsFavorite(it) }
129159
}
130160

131161
private fun setPercentageToCircularPercentage(
@@ -139,18 +169,23 @@ class DetailFragment : Fragment() {
139169
val context = binding.root.context
140170

141171
when {
142-
percentage < 25 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.red)
143-
percentage < 45 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.orange)
144-
percentage < 75 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.yellow)
145-
else -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.green)
172+
percentage < 25 -> binding.circularPercentage.progressTintList =
173+
ContextCompat.getColorStateList(context, R.color.red)
174+
percentage < 45 -> binding.circularPercentage.progressTintList =
175+
ContextCompat.getColorStateList(context, R.color.orange)
176+
percentage < 75 -> binding.circularPercentage.progressTintList =
177+
ContextCompat.getColorStateList(context, R.color.yellow)
178+
else -> binding.circularPercentage.progressTintList =
179+
ContextCompat.getColorStateList(context, R.color.green)
146180
}
147181
binding.screeningPopularity.text = resources.getString(R.string.popularity, percentage)
148182
}
149183

150184
private fun setDate(binding: FragmentDetailBinding, firstAirDate: String) {
151185
try {
152186
val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(firstAirDate)
153-
val stringDate = date?.let { SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(it) }
187+
val stringDate =
188+
date?.let { SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(it) }
154189
binding.firstAirDateDetailTextView.text = stringDate
155190
} catch (e: Exception) {
156191
binding.firstAirDateDetailTextView.text = ""
@@ -176,15 +211,19 @@ class DetailFragment : Fragment() {
176211
.into(binding.backgroundImageToolbarLayout)
177212
}
178213

179-
private fun setupToolbar(binding: FragmentDetailBinding) {
214+
private fun setupToolbar(binding: FragmentDetailBinding, tvShow: TVShow) {
180215
val navController = findNavController()
181216
val appBarConfiguration = AppBarConfiguration(navController.graph)
182217
val toolbar = binding.toolbar
183218
toolbar.inflateMenu(R.menu.watchlist_favorite_detail_fragment)
184219
toolbar.setOnMenuItemClickListener {
185-
when(it.itemId) {
220+
when (it.itemId) {
186221
R.id.ic_heart_watchlist -> {
187-
selectOrUnselectWatchlistFav(binding)
222+
if (!isSaveOnWatchlist) {
223+
viewModel.addToWatchlist(tvShow.toTVShowORMEntity())
224+
} else {
225+
viewModel.deleteFromWatchlist(tvShow.toTVShowORMEntity())
226+
}
188227
true
189228
}
190229
else -> false
@@ -202,13 +241,14 @@ class DetailFragment : Fragment() {
202241
})
203242
}
204243

205-
private fun selectOrUnselectWatchlistFav(binding: FragmentDetailBinding) {
206-
isSaveOnWatchlist = !isSaveOnWatchlist
244+
private fun selectOrUnselectWatchlistFav(binding: FragmentDetailBinding, isFav: Boolean) {
207245
val watchlistItem = binding.toolbar.menu.findItem(R.id.ic_heart_watchlist)
208-
if (isSaveOnWatchlist){
209-
watchlistItem.icon = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_selected)
210-
}else {
211-
watchlistItem.icon = AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_unselected)
212-
}
246+
if (isFav) {
247+
watchlistItem.icon =
248+
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_selected)
249+
} else watchlistItem.icon =
250+
AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_unselected)
251+
213252
}
253+
214254
}

app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ package com.intive.tmdbandroid.details.viewmodel
33
import androidx.lifecycle.ViewModel
44
import androidx.lifecycle.viewModelScope
55
import com.intive.tmdbandroid.common.State
6+
import com.intive.tmdbandroid.entity.TVShowORMEntity
67
import com.intive.tmdbandroid.model.TVShow
78
import com.intive.tmdbandroid.usecase.DetailTVShowUseCase
9+
import com.intive.tmdbandroid.usecase.GetIfExistsUseCase
10+
import com.intive.tmdbandroid.usecase.RemoveTVShowFromWatchlistUseCase
11+
import com.intive.tmdbandroid.usecase.SaveTVShowInWatchlistUseCase
812
import dagger.hilt.android.lifecycle.HiltViewModel
913
import kotlinx.coroutines.flow.MutableStateFlow
1014
import kotlinx.coroutines.flow.StateFlow
@@ -16,12 +20,17 @@ import javax.inject.Inject
1620
@HiltViewModel
1721
class DetailsViewModel @Inject internal constructor(
1822
private val tVShowUseCase: DetailTVShowUseCase,
23+
private val saveTVShowInWatchlistUseCase: SaveTVShowInWatchlistUseCase,
24+
private val removeTVShowFromWatchlistUseCase: RemoveTVShowFromWatchlistUseCase,
25+
private val getIfExistsUseCase: GetIfExistsUseCase
1926
) : ViewModel() {
2027

2128
private val _state = MutableStateFlow<State<TVShow>>(State.Loading)
22-
2329
val uiState: StateFlow<State<TVShow>> = _state
2430

31+
private val _watchlistState = MutableStateFlow<State<Boolean>>(State.Loading)
32+
val watchlistUIState: StateFlow<State<Boolean>> = _watchlistState
33+
2534
fun tVShows(id: Int) {
2635
viewModelScope.launch {
2736
tVShowUseCase(id)
@@ -33,4 +42,40 @@ class DetailsViewModel @Inject internal constructor(
3342
}
3443
}
3544
}
45+
46+
fun addToWatchlist(tvShow: TVShowORMEntity) {
47+
viewModelScope.launch {
48+
saveTVShowInWatchlistUseCase(tvShow)
49+
.catch {
50+
_watchlistState.value = State.Error
51+
}
52+
.collect {
53+
_watchlistState.value = State.Success(it)
54+
}
55+
}
56+
}
57+
58+
fun deleteFromWatchlist(tvShow: TVShowORMEntity) {
59+
viewModelScope.launch {
60+
removeTVShowFromWatchlistUseCase(tvShow)
61+
.catch {
62+
_watchlistState.value = State.Error
63+
}
64+
.collect {
65+
_watchlistState.value = State.Success(it)
66+
}
67+
}
68+
}
69+
70+
fun existAsFavorite(id: Int) {
71+
viewModelScope.launch {
72+
getIfExistsUseCase(id)
73+
.catch {
74+
_watchlistState.value = State.Error
75+
}
76+
.collect {
77+
_watchlistState.value = State.Success(it)
78+
}
79+
}
80+
}
3681
}

app/src/main/java/com/intive/tmdbandroid/entity/TVShowORMEntity.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@ import com.intive.tmdbandroid.model.CreatedBy
77
import com.intive.tmdbandroid.model.Genre
88
import com.intive.tmdbandroid.model.TVShow
99
import com.intive.tmdbandroid.model.converter.CreatedByConverter
10+
import com.intive.tmdbandroid.model.converter.GenreConverter
1011

1112
@Entity
1213
data class TVShowORMEntity(
1314
val backdrop_path: String?,
1415
@TypeConverters(CreatedByConverter::class)
1516
val created_by: List<CreatedBy>,
1617
val first_air_date: String?,
18+
@TypeConverters(GenreConverter::class)
1719
val genres: List<Genre>,
1820
@PrimaryKey(autoGenerate = false)
1921
val id: Int,

app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import android.os.Bundle
44
import android.view.LayoutInflater
55
import android.view.View
66
import android.view.ViewGroup
7-
import androidx.annotation.NonNull
87
import androidx.fragment.app.Fragment
98
import androidx.fragment.app.viewModels
109
import androidx.lifecycle.lifecycleScope
@@ -35,6 +34,11 @@ class HomeFragment : Fragment() {
3534
viewModel.popularTVShows()
3635
}
3736

37+
override fun onResume() {
38+
super.onResume()
39+
viewModel.watchlistTVShows()
40+
}
41+
3842
override fun onCreateView(
3943
inflater: LayoutInflater,
4044
container: ViewGroup?,
@@ -46,15 +50,14 @@ class HomeFragment : Fragment() {
4650
initViews(binding)
4751
subscribePopularData(binding)
4852
setupToolbar(binding)
49-
53+
subscribeWatchlistData(binding)
5054
return binding.root
5155
}
5256

5357
private fun setupToolbar(binding: FragmentHomeBinding) {
5458
val navController = findNavController()
5559
val appBarConfiguration = AppBarConfiguration(navController.graph)
56-
val toolbar = binding.fragmentHomeToolbar
57-
toolbar.setupWithNavController(navController, appBarConfiguration)
60+
binding.fragmentHomeToolbar.setupWithNavController(navController, appBarConfiguration)
5861
}
5962

6063
private fun subscribePopularData(binding: FragmentHomeBinding) {
@@ -68,8 +71,6 @@ class HomeFragment : Fragment() {
6871
binding.layoutError.errorContainer.visibility = View.GONE
6972
binding.layoutProgressbar.progressBar.visibility = View.GONE
7073

71-
updateMockWatchlist()
72-
7374
tvShowPageAdapter.submitData(resultTVShows.data)
7475

7576
if (tvShowPageAdapter.itemCount == 0) {
@@ -89,15 +90,26 @@ class HomeFragment : Fragment() {
8990
}
9091
}
9192

92-
private fun updateMockWatchlist() {
93-
val list = listOf(
94-
TVShow("", emptyList(),"2021-09-13", emptyList(),0,"","Some TV Show 0", 1, 1, "","","","",2.0,10),
95-
TVShow("", emptyList(),"2021-09-13", emptyList(),1,"","Some TV Show 1", 2, 2, "","","","",4.0,10),
96-
TVShow("", emptyList(),"2021-09-13", emptyList(),2,"","Some TV Show 2", 3, 3, "","","","",6.0,10),
97-
TVShow("", emptyList(),"2021-09-13", emptyList(),3,"","Some TV Show 3", 4, 4, "","","","",8.0,10)
98-
)
99-
100-
tvShowPageAdapter.refreshWatchlistAdapter(list)
93+
private fun subscribeWatchlistData(binding: FragmentHomeBinding) {
94+
lifecycleScope.launchWhenStarted {
95+
viewModel.watchlistUIState.collectLatest {
96+
when(it) {
97+
is State.Success<List<TVShow>> -> {
98+
binding.layoutError.errorContainer.visibility = View.GONE
99+
binding.layoutProgressbar.progressBar.visibility = View.GONE
100+
tvShowPageAdapter.refreshWatchlistAdapter(it.data)
101+
}
102+
is State.Error -> {
103+
binding.layoutProgressbar.progressBar.visibility = View.GONE
104+
binding.layoutError.errorContainer.visibility = View.VISIBLE
105+
}
106+
is State.Loading -> {
107+
binding.layoutProgressbar.progressBar.visibility = View.VISIBLE
108+
binding.layoutError.errorContainer.visibility = View.GONE
109+
}
110+
}
111+
}
112+
}
101113
}
102114

103115
private fun initViews(binding: FragmentHomeBinding) {

app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import androidx.paging.PagingData
66
import androidx.paging.cachedIn
77
import com.intive.tmdbandroid.common.State
88
import com.intive.tmdbandroid.model.TVShow
9+
import com.intive.tmdbandroid.usecase.GetAllItemsInWatchlistUseCase
910
import com.intive.tmdbandroid.usecase.PaginatedPopularTVShowsUseCase
1011
import dagger.hilt.android.lifecycle.HiltViewModel
1112
import kotlinx.coroutines.flow.MutableStateFlow
@@ -18,11 +19,15 @@ import javax.inject.Inject
1819
@HiltViewModel
1920
class HomeViewModel @Inject internal constructor(
2021
private val paginatedPopularTVShowsUseCase: PaginatedPopularTVShowsUseCase,
22+
private val getAllItemsInWatchlistUseCase: GetAllItemsInWatchlistUseCase
2123
) : ViewModel() {
2224

2325
private val _state = MutableStateFlow<State<PagingData<TVShow>>>(State.Loading)
2426
val uiState: StateFlow<State<PagingData<TVShow>>> = _state
2527

28+
private val _watchlistState = MutableStateFlow<State<List<TVShow>>>(State.Loading)
29+
val watchlistUIState: StateFlow<State<List<TVShow>>> = _watchlistState
30+
2631
fun popularTVShows() {
2732
viewModelScope.launch {
2833
paginatedPopularTVShowsUseCase()
@@ -35,4 +40,16 @@ class HomeViewModel @Inject internal constructor(
3540
}
3641
}
3742
}
43+
44+
fun watchlistTVShows() {
45+
viewModelScope.launch {
46+
getAllItemsInWatchlistUseCase()
47+
.catch {
48+
_watchlistState.value = State.Error
49+
}
50+
.collect {
51+
_watchlistState.value = State.Success(it)
52+
}
53+
}
54+
}
3855
}

app/src/main/java/com/intive/tmdbandroid/model/converter/CreatedByConverter.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package com.intive.tmdbandroid.model.converter
22

33
import androidx.room.TypeConverter
4-
import com.intive.tmdbandroid.model.CreatedBy
54
import com.google.gson.Gson
65
import com.google.gson.reflect.TypeToken
6+
import com.intive.tmdbandroid.model.CreatedBy
77
import java.lang.reflect.Type
88

99

@@ -24,7 +24,7 @@ class CreatedByConverter {
2424
return null
2525
}
2626
val gson = Gson()
27-
val type: Type = object : TypeToken<List<CreatedBy?>?>() {}.getType()
27+
val type: Type = object : TypeToken<List<CreatedBy?>?>() {}.type
2828
return gson.toJson(createdBy, type)
2929
}
3030
}

0 commit comments

Comments
 (0)