From 7c50cd5cdefab7409b3579481b48bf50021d2ee7 Mon Sep 17 00:00:00 2001 From: "andres.seoane" Date: Thu, 2 Sep 2021 11:53:22 -0300 Subject: [PATCH 01/71] Created branch and added directories --- .../java/com/intive/tmdbandroid/search/ui/SearchFragment.kt | 4 ++++ .../intive/tmdbandroid/search/viewmodel/SearchViewModel.kt | 4 ++++ 2 files changed, 8 insertions(+) create mode 100644 app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt create mode 100644 app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt new file mode 100644 index 00000000..5aff2ddf --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -0,0 +1,4 @@ +package com.intive.tmdbandroid.search.ui + +class SearchFragment { +} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt new file mode 100644 index 00000000..6e41d86b --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt @@ -0,0 +1,4 @@ +package com.intive.tmdbandroid.search.viewmodel + +class SearchViewModel { +} \ No newline at end of file From a68b1233e83c1143056dea68bbdbabbb8c8e3c2c Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Thu, 2 Sep 2021 14:36:03 -0300 Subject: [PATCH 02/71] Add SearchViewModel and SearchUseCase --- .../repository/CatalogRepository.kt | 5 ++- .../search/viewmodel/SearchViewModel.kt | 39 ++++++++++++++++++- .../tmdbandroid/usecase/SearchUseCase.kt | 10 +++++ 3 files changed, 52 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt b/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt index 9a5996c1..f0ea10f2 100644 --- a/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt +++ b/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt @@ -5,7 +5,6 @@ import androidx.paging.PagingConfig import androidx.paging.PagingData import com.intive.tmdbandroid.datasource.TVShowPagingSource import com.intive.tmdbandroid.datasource.network.Service -import com.intive.tmdbandroid.entity.ResultTVShowsEntity import com.intive.tmdbandroid.model.TVShow import kotlinx.coroutines.flow.Flow import javax.inject.Inject @@ -34,4 +33,8 @@ class CatalogRepository @Inject constructor( fun getTVShowByID(id:Int): Flow{ return service.getTVShowByID(id) } + + fun search(): Flow { + TODO("Not yet implemented") + } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt index 6e41d86b..c10ee121 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt @@ -1,4 +1,41 @@ package com.intive.tmdbandroid.search.viewmodel -class SearchViewModel { +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.intive.tmdbandroid.usecase.SearchUseCase +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( + private val searchUseCase: SearchUseCase +) : ViewModel() { + + private val _state = MutableStateFlow(State.Loading) + + val uiState: StateFlow = _state + + fun search() { + viewModelScope.launch { + searchUseCase() + .catch { e -> + _state.value = State.Error(e) + } + .collect { + _state.value = State.Success(it) + } + } + } + +} + +sealed class State { + object Loading : State() + data class Success(val data: Any) : State() + data class Error(val exception: Throwable) : State() } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt new file mode 100644 index 00000000..49cd4ac8 --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt @@ -0,0 +1,10 @@ +package com.intive.tmdbandroid.usecase + +import com.intive.tmdbandroid.repository.CatalogRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class SearchUseCase @Inject constructor(private val catalogRepository: CatalogRepository) { + + operator fun invoke(): Flow = catalogRepository.search() +} \ No newline at end of file From dd7d7cc587a2895eb27a2861dfc8c3cfa257dbb9 Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Thu, 2 Sep 2021 14:45:20 -0300 Subject: [PATCH 03/71] Add fragment_search.xml and I add simple logic on SearchFragment.kt --- .../tmdbandroid/search/ui/SearchFragment.kt | 31 ++++++++++++++++++- app/src/main/res/layout/fragment_search.xml | 6 ++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/layout/fragment_search.xml diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 5aff2ddf..b37be442 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -1,4 +1,33 @@ package com.intive.tmdbandroid.search.ui -class SearchFragment { +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.intive.tmdbandroid.databinding.FragmentSearchBinding +import com.intive.tmdbandroid.search.viewmodel.SearchViewModel +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class SearchFragment: Fragment() { + private val viewModel: SearchViewModel by viewModels() + + private lateinit var binding: FragmentSearchBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentSearchBinding.inflate(inflater, container, false) + + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + viewModel.search() + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml new file mode 100644 index 00000000..77d9ef65 --- /dev/null +++ b/app/src/main/res/layout/fragment_search.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file From ad1d5c91555fcbfd304e8a08b7993abbae9e488e Mon Sep 17 00:00:00 2001 From: "andres.seoane" Date: Fri, 3 Sep 2021 13:41:06 -0300 Subject: [PATCH 04/71] Added SearchFragment.kt to nav_graph.xml and corresponding actions --- app/src/main/res/navigation/nav_graph.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 8f6ee3d4..e7ce845f 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -14,7 +14,12 @@ android:id="@+id/action_homeFragmentDest_to_TVShowDetail" app:destination="@id/detailFragmentDest" app:popUpTo="@id/homeFragmentDest" /> + + + + + + From 8e24de838c1d8c86bb4e4d5fa6e61dc39cc96920 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 3 Sep 2021 15:12:26 -0300 Subject: [PATCH 05/71] Add Search icon and clickListener to the Toolbar --- .../java/com/intive/tmdbandroid/home/ui/HomeFragment.kt | 7 +++++++ app/src/main/res/drawable/ic_search.xml | 5 +++++ app/src/main/res/menu/options_menu.xml | 9 +++++++++ app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 23 insertions(+) create mode 100644 app/src/main/res/drawable/ic_search.xml create mode 100644 app/src/main/res/menu/options_menu.xml diff --git a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt index a202bdef..07091bcb 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt @@ -8,10 +8,12 @@ import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope +import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController import androidx.recyclerview.widget.GridLayoutManager +import com.intive.tmdbandroid.R import com.intive.tmdbandroid.databinding.FragmentHomeBinding import com.intive.tmdbandroid.home.ui.adapters.TVShowPageAdapter import com.intive.tmdbandroid.home.viewmodel.HomeViewModel @@ -53,7 +55,12 @@ class HomeFragment : Fragment() { val navController = findNavController() val appBarConfiguration = AppBarConfiguration(navController.graph) val toolbar = binding.fragmentHomeToolbar + toolbar.inflateMenu(R.menu.options_menu) toolbar.setupWithNavController(navController, appBarConfiguration) + toolbar.setOnMenuItemClickListener(){ + toolbar.findNavController().navigate(R.id.action_homeFragmentDest_to_searchFragment) + true + } } private fun subscribePopularData() { diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 00000000..e2dd96c6 --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/menu/options_menu.xml b/app/src/main/res/menu/options_menu.xml new file mode 100644 index 00000000..e237b7a8 --- /dev/null +++ b/app/src/main/res/menu/options_menu.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dcc3d60e..a8cee2ab 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -10,6 +10,8 @@ Nothing to see here Warning Overview + Search + %d Season From b537ab12454e38b0362c43ac428b68bb1feedd26 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 3 Sep 2021 16:00:56 -0300 Subject: [PATCH 06/71] Add RecyclerView and comment ViewModel --- .../intive/tmdbandroid/search/ui/SearchFragment.kt | 4 ++-- app/src/main/res/layout/fragment_search.xml | 11 ++++++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index b37be442..dc0d3ffd 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -12,7 +12,7 @@ import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class SearchFragment: Fragment() { - private val viewModel: SearchViewModel by viewModels() +// private val viewModel: SearchViewModel by viewModels() private lateinit var binding: FragmentSearchBinding @@ -28,6 +28,6 @@ class SearchFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.search() +// viewModel.search() } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 77d9ef65..dc01f202 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -1,6 +1,11 @@ - + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> - \ No newline at end of file + + \ No newline at end of file From e6044edd0b197a4fae308fdac874b57e0b1e9912 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 3 Sep 2021 17:15:18 -0300 Subject: [PATCH 07/71] Add Item Found UI for Search --- app/src/main/res/layout/item_found_search.xml | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 app/src/main/res/layout/item_found_search.xml diff --git a/app/src/main/res/layout/item_found_search.xml b/app/src/main/res/layout/item_found_search.xml new file mode 100644 index 00000000..182eea99 --- /dev/null +++ b/app/src/main/res/layout/item_found_search.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + + \ No newline at end of file From 5129040e6995878aa550abb3defbb36caa52c812 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 3 Sep 2021 19:41:50 -0300 Subject: [PATCH 08/71] Add single Item for Search Fragment --- app/src/main/res/layout/item_found_search.xml | 33 ++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/app/src/main/res/layout/item_found_search.xml b/app/src/main/res/layout/item_found_search.xml index 182eea99..70776ce8 100644 --- a/app/src/main/res/layout/item_found_search.xml +++ b/app/src/main/res/layout/item_found_search.xml @@ -3,62 +3,65 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" > + android:layout_height="wrap_content"> + android:layout_height="wrap_content" + app:cardCornerRadius="5dp" + android:elevation="8dp"> + app:layout_constraintStart_toEndOf="@+id/itemPosterSearch" + app:layout_constraintTop_toBottomOf="@+id/itemTitleSearch" /> + app:layout_constraintStart_toEndOf="@+id/itemPosterSearch" /> + app:layout_constraintStart_toEndOf="@+id/itemYearSearch" /> + tools:srcCompat="@tools:sample/avatars" /> \ No newline at end of file From 9c143c8d5f1ccb40404e1f6aade5e416481bf984 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 3 Sep 2021 19:43:20 -0300 Subject: [PATCH 09/71] Add toolbar with navigation to Search Fragment --- .../tmdbandroid/search/ui/SearchFragment.kt | 20 +++++++++++-- app/src/main/res/layout/fragment_search.xml | 29 +++++++++++++++---- 2 files changed, 41 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index dc0d3ffd..e852c9d5 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -5,15 +5,20 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment -import androidx.fragment.app.viewModels +import androidx.navigation.fragment.findNavController +import androidx.navigation.ui.AppBarConfiguration +import androidx.navigation.ui.setupWithNavController +import com.google.android.material.appbar.AppBarLayout import com.intive.tmdbandroid.databinding.FragmentSearchBinding -import com.intive.tmdbandroid.search.viewmodel.SearchViewModel +import com.intive.tmdbandroid.search.ui.adapters.TVShowSearchAdapter import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint class SearchFragment: Fragment() { // private val viewModel: SearchViewModel by viewModels() + private val searchAdapter = TVShowSearchAdapter() + private lateinit var binding: FragmentSearchBinding override fun onCreateView( @@ -23,11 +28,20 @@ class SearchFragment: Fragment() { ): View { binding = FragmentSearchBinding.inflate(inflater, container, false) + println(searchAdapter.toString()) return binding.root } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + setupToolbar() // viewModel.search() } -} \ No newline at end of file + private fun setupToolbar() { + val navController = findNavController() + val appBarConfiguration = AppBarConfiguration(navController.graph) + val toolbar = binding.fragmentSearchToolbar + toolbar.setupWithNavController(navController, appBarConfiguration) + } +} diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index dc01f202..c877cce4 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -1,11 +1,30 @@ - + android:orientation="vertical"> + + - \ No newline at end of file + android:layout_height="0dp" + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/fragment_search_toolbar" + tools:listitem="@layout/item_found_search" /> + \ No newline at end of file From 1c7073dfc276257779b494203989e84fcaf204eb Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 6 Sep 2021 08:41:48 -0300 Subject: [PATCH 10/71] Search a TV Show * Repo + DataSource + test unit --- .../datasource/network/ApiClient.kt | 4 + .../tmdbandroid/datasource/network/Service.kt | 12 ++- .../repository/CatalogRepository.kt | 4 +- .../tmdbandroid/search/ui/SearchFragment.kt | 6 +- .../search/viewmodel/SearchViewModel.kt | 4 +- .../tmdbandroid/usecase/SearchUseCase.kt | 3 +- .../search/viewmodel/SearchViewModelTest.kt | 75 +++++++++++++++++++ 7 files changed, 99 insertions(+), 9 deletions(-) create mode 100644 app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt index e6b616ef..cf2da013 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt @@ -14,4 +14,8 @@ interface ApiClient { @GET("tv/{tv_id}") suspend fun getTVShowByID(@Path("tv_id") tvShowID: Int, @Query("api_key") apiKey: String) : TVShow + + @GET("search/movie") + suspend fun getTVShowByName(@Path("tv_id") tvShowName: String, + @Query("api_key") apiKey: String) : TVShow } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt index 565ebba4..62ccae78 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt @@ -10,15 +10,21 @@ import kotlinx.coroutines.flow.flow class Service { private val retrofit = RetrofitHelper.getRetrofit() - fun getPaginatedPopularTVShows(page: Int) : Flow { + fun getPaginatedPopularTVShows(page: Int): Flow { return flow { emit(retrofit.create(ApiClient::class.java).getPaginatedPopularTVShows(BuildConfig.API_KEY, page)) } } - fun getTVShowByID(tvShowID: Int) : Flow { + fun getTVShowByID(tvShowID: Int): Flow { return flow { emit(retrofit.create(ApiClient::class.java).getTVShowByID(tvShowID, BuildConfig.API_KEY)) } } -} \ No newline at end of file + + fun getTvShowByTitle(tvShowTitle: String): Flow { + return flow { + emit(retrofit.create(ApiClient::class.java).getTVShowByName(BuildConfig.API_KEY, tvShowTitle)) + } + } +} diff --git a/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt b/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt index f0ea10f2..1ffd5f02 100644 --- a/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt +++ b/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt @@ -34,7 +34,7 @@ class CatalogRepository @Inject constructor( return service.getTVShowByID(id) } - fun search(): Flow { - TODO("Not yet implemented") + fun search(name:String): Flow { + return service.getTvShowByTitle(name) } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index b37be442..6aa00c92 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -16,6 +16,8 @@ class SearchFragment: Fragment() { private lateinit var binding: FragmentSearchBinding + private var tvShowName: String? = null + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -28,6 +30,8 @@ class SearchFragment: Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - viewModel.search() + tvShowName?.let { + viewModel.search(it) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt index c10ee121..a75439a7 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt @@ -20,9 +20,9 @@ class SearchViewModel @Inject constructor( val uiState: StateFlow = _state - fun search() { + fun search(name:String) { viewModelScope.launch { - searchUseCase() + searchUseCase(name) .catch { e -> _state.value = State.Error(e) } diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt index 49cd4ac8..2f887e72 100644 --- a/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt @@ -1,10 +1,11 @@ package com.intive.tmdbandroid.usecase +import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.repository.CatalogRepository import kotlinx.coroutines.flow.Flow import javax.inject.Inject class SearchUseCase @Inject constructor(private val catalogRepository: CatalogRepository) { - operator fun invoke(): Flow = catalogRepository.search() + operator fun invoke(name:String): Flow = catalogRepository.search(name) } \ No newline at end of file diff --git a/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt b/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt new file mode 100644 index 00000000..a53c9f37 --- /dev/null +++ b/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt @@ -0,0 +1,75 @@ +package com.intive.tmdbandroid.search.viewmodel + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import app.cash.turbine.test +import com.google.common.truth.Truth +import com.intive.tmdbandroid.common.MainCoroutineRule +import com.intive.tmdbandroid.model.Genre +import com.intive.tmdbandroid.model.TVShow +import com.intive.tmdbandroid.usecase.DetailTVShowUseCase +import com.intive.tmdbandroid.usecase.SearchUseCase +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito.* +import kotlin.time.ExperimentalTime + +@ExperimentalCoroutinesApi +class SearchViewModelTest{ + + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + @get:Rule + var instantTaskExecutorRule = InstantTaskExecutorRule() + + private val tvShow = TVShow( + backdrop_path = "BACKDROP_PATH", + first_air_date = "1983-10-20", + genres = listOf(Genre(1, "genre1"), Genre(2,"genre2")), + id = 1, + name = "Simona la Cacarisa", + original_name = "El cochiloco", + overview = "Simona la cacarisa, el cochiloco", + poster_path = "POSTER_PATH", + vote_average = 10.5, + vote_count = 100, + created_by = emptyList(), + last_air_date = "1990-09-25", + number_of_episodes = 5, + number_of_seasons = 2, + status = "Online" + ) + + private lateinit var searchViewModel: SearchViewModel + private lateinit var searchUseCase: SearchUseCase + + + @Before + fun setup() { + searchUseCase = mock(SearchUseCase::class.java) + searchViewModel = SearchViewModel(searchUseCase) + } + + @Test + @ExperimentalTime + fun tVShowsTest() = mainCoroutineRule.runBlockingTest { + `when`(searchUseCase.invoke(anyString())).thenReturn( + flow { + emit( + tvShow + ) + } + ) + + searchViewModel.search("Simona la Cacarisa") + + searchViewModel.uiState.test { + Truth.assertThat(awaitItem()).isEqualTo(State.Success(tvShow)) + } + } + +} \ No newline at end of file From 79c663c7f4c8e175185c55760487b215842c7387 Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Mon, 6 Sep 2021 10:23:22 -0300 Subject: [PATCH 11/71] Change State for used the common State --- .../tmdbandroid/search/viewmodel/SearchViewModel.kt | 11 +++-------- .../search/viewmodel/SearchViewModelTest.kt | 2 +- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt index a75439a7..80b8e2d8 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt @@ -2,6 +2,7 @@ package com.intive.tmdbandroid.search.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.usecase.SearchUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -23,8 +24,8 @@ class SearchViewModel @Inject constructor( fun search(name:String) { viewModelScope.launch { searchUseCase(name) - .catch { e -> - _state.value = State.Error(e) + .catch { + _state.value = State.Error } .collect { _state.value = State.Success(it) @@ -32,10 +33,4 @@ class SearchViewModel @Inject constructor( } } -} - -sealed class State { - object Loading : State() - data class Success(val data: Any) : State() - data class Error(val exception: Throwable) : State() } \ No newline at end of file diff --git a/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt b/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt index a53c9f37..f70a1755 100644 --- a/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt +++ b/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt @@ -4,9 +4,9 @@ import androidx.arch.core.executor.testing.InstantTaskExecutorRule import app.cash.turbine.test import com.google.common.truth.Truth import com.intive.tmdbandroid.common.MainCoroutineRule +import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.model.Genre import com.intive.tmdbandroid.model.TVShow -import com.intive.tmdbandroid.usecase.DetailTVShowUseCase import com.intive.tmdbandroid.usecase.SearchUseCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow From 36e19183d75669baec51d132166da7ec52ea6003 Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Mon, 6 Sep 2021 13:23:18 -0300 Subject: [PATCH 12/71] Add more margin in item_screening Add lineHeight to overview in fragment_detail --- app/src/main/res/layout/fragment_detail.xml | 1 + app/src/main/res/layout/item_screening.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml index 330e9097..2945087a 100644 --- a/app/src/main/res/layout/fragment_detail.xml +++ b/app/src/main/res/layout/fragment_detail.xml @@ -121,6 +121,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="8dp" + android:lineSpacingExtra="4dp" android:textColor="@color/black" tools:text="Taking inspiration from the comic books of the same name, each episode explores a pivotal moment from the Marvel Cinematic Universe and turns it on its head, leading the audience into uncharted territory." /> diff --git a/app/src/main/res/layout/item_screening.xml b/app/src/main/res/layout/item_screening.xml index 2f0bde07..8bfb09de 100644 --- a/app/src/main/res/layout/item_screening.xml +++ b/app/src/main/res/layout/item_screening.xml @@ -6,7 +6,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="8dp" + android:layout_margin="16dp" app:cardCornerRadius="5dp" android:elevation="5dp"> From f2170bf3116bc8506d4df6460f21830735f7c358 Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Mon, 6 Sep 2021 16:01:46 -0300 Subject: [PATCH 13/71] Add line spacing extra to overview text --- app/src/main/res/layout/fragment_detail.xml | 42 +++++++++++---------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml index ced90a5a..655a5040 100644 --- a/app/src/main/res/layout/fragment_detail.xml +++ b/app/src/main/res/layout/fragment_detail.xml @@ -8,10 +8,10 @@ tools:context=".details.ui.DetailFragment"> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:fitsSystemWindows="true"> + app:layout_collapseMode="parallax" + tools:src="@drawable/ic_launcher_foreground" /> - + @@ -175,15 +175,17 @@ - + android:layout_height="match_parent" /> - + android:layout_height="match_parent" /> \ No newline at end of file From 56989c25e8ed23612b0df8ce4c91174f954240fe Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Mon, 6 Sep 2021 16:07:31 -0300 Subject: [PATCH 14/71] Add Search Icon to Search Fragment's toolbar --- .../tmdbandroid/search/ui/SearchFragment.kt | 11 ++++++----- app/src/main/res/layout/fragment_search.xml | 19 ++++++++++++++++++- app/src/main/res/values/strings.xml | 2 +- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index e852c9d5..a6ac20aa 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -1,14 +1,15 @@ package com.intive.tmdbandroid.search.ui import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup +import android.util.Log +import android.view.* +import android.view.inputmethod.InputMethodManager import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController import com.google.android.material.appbar.AppBarLayout +import com.intive.tmdbandroid.R import com.intive.tmdbandroid.databinding.FragmentSearchBinding import com.intive.tmdbandroid.search.ui.adapters.TVShowSearchAdapter import dagger.hilt.android.AndroidEntryPoint @@ -27,8 +28,6 @@ class SearchFragment: Fragment() { savedInstanceState: Bundle? ): View { binding = FragmentSearchBinding.inflate(inflater, container, false) - - println(searchAdapter.toString()) return binding.root } @@ -38,10 +37,12 @@ class SearchFragment: Fragment() { setupToolbar() // viewModel.search() } + private fun setupToolbar() { val navController = findNavController() val appBarConfiguration = AppBarConfiguration(navController.graph) val toolbar = binding.fragmentSearchToolbar toolbar.setupWithNavController(navController, appBarConfiguration) + binding.searchView.requestFocus() } } diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index c877cce4..ff659fe0 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -11,11 +11,28 @@ android:layout_width="match_parent" android:layout_height="?actionBarSize" android:background="@color/primaryColor" + android:gravity="start|center_horizontal|center_vertical" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - app:titleTextColor="@color/white" /> + app:titleTextColor="@color/white"> + + + + Nothing to see here Warning Overview - Search + Search TV shows or movies From 2144fba7f04705ebcdba105056c9b1da26197022 Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Mon, 6 Sep 2021 16:13:55 -0300 Subject: [PATCH 15/71] Add the methods but without implementation --- .../tmdbandroid/details/viewmodel/DetailsViewModel.kt | 8 ++++++++ .../intive/tmdbandroid/home/viewmodel/HomeViewModel.kt | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt index 04938a2e..b1003632 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt @@ -32,4 +32,12 @@ class DetailsViewModel @Inject internal constructor( } } } + + fun addToWatchlist() { + //TODO implement after enable the Use Case for Room + } + + fun removeFromWatchlist() { + //TODO implement after enable the Use Case for Room + } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt index ad86ffbf..dfacce22 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt @@ -34,4 +34,8 @@ class HomeViewModel @Inject internal constructor( } } } + + fun watchlistTVShows() { + //TODO implement after enable the Use Case for Room + } } From 230db80a93c7e726888fcf2b1f5a9ced15820ae2 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Mon, 6 Sep 2021 16:20:19 -0300 Subject: [PATCH 16/71] Add Search Adapter skeleton --- .../search/ui/adapters/TVShowSearchAdapter.kt | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt new file mode 100644 index 00000000..1215cc30 --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -0,0 +1,84 @@ +package com.intive.tmdbandroid.search.ui.adapters + +import android.content.res.ColorStateList +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.core.os.bundleOf +import androidx.navigation.findNavController +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.bumptech.glide.request.RequestOptions +import com.intive.tmdbandroid.R +import com.intive.tmdbandroid.databinding.ItemFoundSearchBinding +import com.intive.tmdbandroid.model.TVShow +import java.text.SimpleDateFormat +import java.util.* + + +class TVShowSearchAdapter : PagingDataAdapter(REPO_COMPARATOR) { + companion object { + private val REPO_COMPARATOR = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) + override fun areContentsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) + } + } + + override fun onBindViewHolder(holder: TVShowHolder, position: Int) { + with(holder) { + with(getItem(position) as TVShow) { + val options = RequestOptions() + .centerCrop() + .placeholder(R.drawable.ic_image) + .error(R.drawable.ic_image) + + val posterURL = binding.root.resources.getString(R.string.base_imageURL) + poster_path + + val context = binding.root.context + + Glide.with(context) + .load(posterURL) + .apply(options) + .into(binding.itemPosterSearch) + + val percentage = (vote_average * 10).toInt() + + try { + val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(first_air_date) + val stringDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(date!!) + binding.itemYearSearch.text = stringDate + } catch (e: Exception) { + binding.itemYearSearch.text = "" + } + +// binding.itemRatingSearch.progress = percentage + +// when { +// percentage < 25 -> binding.circularPercentage.progressTintList = ColorStateList.valueOf( +// ContextCompat.getColor(context, R.color.red)) +// percentage < 45 -> binding.circularPercentage.progressTintList = ColorStateList.valueOf( +// ContextCompat.getColor(context, R.color.orange)) +// percentage < 75 -> binding.circularPercentage.progressTintList = ColorStateList.valueOf( +// ContextCompat.getColor(context, R.color.yellow)) +// else -> binding.circularPercentage.progressTintList = ColorStateList.valueOf( +// ContextCompat.getColor(context, R.color.green)) +// } + + binding.itemRatingSearch.numStars = percentage + binding.itemTitleSearch.text = name + + binding.root.setOnClickListener() { + it.findNavController().navigate(R.id.action_homeFragmentDest_to_TVShowDetail, bundleOf("id" to id, "screeningTitle" to name)) + } + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TVShowHolder = TVShowHolder( + ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + inner class TVShowHolder (val binding: ItemFoundSearchBinding) : RecyclerView.ViewHolder(binding.root) +} \ No newline at end of file From e00769f82af74b975d30356a4091921fd36ee1fa Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Mon, 6 Sep 2021 17:12:16 -0300 Subject: [PATCH 17/71] Change the margins in item_screening --- app/src/main/res/layout/item_screening.xml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/item_screening.xml b/app/src/main/res/layout/item_screening.xml index 8bfb09de..c08eee00 100644 --- a/app/src/main/res/layout/item_screening.xml +++ b/app/src/main/res/layout/item_screening.xml @@ -6,7 +6,10 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_margin="16dp" + android:layout_marginTop="8dp" + android:layout_marginEnd="8dp" + android:layout_marginStart="8dp" + android:layout_marginBottom="16dp" app:cardCornerRadius="5dp" android:elevation="5dp"> From eb8d233891426af7e008977f5aa3e7d9531afb75 Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Mon, 6 Sep 2021 17:22:52 -0300 Subject: [PATCH 18/71] Change the margin from NestedScrollView to LinearLayout --- app/src/main/res/layout/fragment_detail.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_detail.xml b/app/src/main/res/layout/fragment_detail.xml index 655a5040..8809e2c1 100644 --- a/app/src/main/res/layout/fragment_detail.xml +++ b/app/src/main/res/layout/fragment_detail.xml @@ -66,12 +66,12 @@ Date: Mon, 6 Sep 2021 17:55:59 -0300 Subject: [PATCH 19/71] Refactor TVShowSearchAdapter.kt and add search_results_header.xml --- .../search/ui/adapters/TVShowSearchAdapter.kt | 95 ++++++++----------- .../main/res/layout/search_results_header.xml | 6 ++ 2 files changed, 47 insertions(+), 54 deletions(-) create mode 100644 app/src/main/res/layout/search_results_header.xml diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index 1215cc30..b506355b 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -2,7 +2,9 @@ package com.intive.tmdbandroid.search.ui.adapters import android.content.res.ColorStateList import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup +import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.navigation.findNavController @@ -13,72 +15,57 @@ import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.intive.tmdbandroid.R import com.intive.tmdbandroid.databinding.ItemFoundSearchBinding +import com.intive.tmdbandroid.databinding.ItemScreeningBinding +import com.intive.tmdbandroid.home.ui.adapters.TVShowPageAdapter import com.intive.tmdbandroid.model.TVShow import java.text.SimpleDateFormat import java.util.* +private const val TYPE_HEADER : Int = 0 +private const val TYPE_LIST : Int = 1 -class TVShowSearchAdapter : PagingDataAdapter(REPO_COMPARATOR) { - companion object { - private val REPO_COMPARATOR = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) - override fun areContentsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) - } - } - - override fun onBindViewHolder(holder: TVShowHolder, position: Int) { - with(holder) { - with(getItem(position) as TVShow) { - val options = RequestOptions() - .centerCrop() - .placeholder(R.drawable.ic_image) - .error(R.drawable.ic_image) - - val posterURL = binding.root.resources.getString(R.string.base_imageURL) + poster_path - val context = binding.root.context +class TVShowSearchAdapter(private val searchResults : PagingDataAdapter) : RecyclerView.Adapter() +{ + private val TYPE_HEADER : Int = 0 + private val TYPE_LIST : Int = 1 - Glide.with(context) - .load(posterURL) - .apply(options) - .into(binding.itemPosterSearch) - - val percentage = (vote_average * 10).toInt() - - try { - val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(first_air_date) - val stringDate = SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(date!!) - binding.itemYearSearch.text = stringDate - } catch (e: Exception) { - binding.itemYearSearch.text = "" - } + override fun getItemViewType(position: Int): Int { + return when(position){ + 0 -> TYPE_HEADER + else -> TYPE_LIST + } + } -// binding.itemRatingSearch.progress = percentage + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when(viewType){ + TYPE_HEADER -> { + val header = LayoutInflater.from(parent.context).inflate(R.layout.search_results_header,parent,false) + SearchResultsHeaderHolder(header) + } else -> { + val header = ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + SearchResultHolder(header) + } + } -// when { -// percentage < 25 -> binding.circularPercentage.progressTintList = ColorStateList.valueOf( -// ContextCompat.getColor(context, R.color.red)) -// percentage < 45 -> binding.circularPercentage.progressTintList = ColorStateList.valueOf( -// ContextCompat.getColor(context, R.color.orange)) -// percentage < 75 -> binding.circularPercentage.progressTintList = ColorStateList.valueOf( -// ContextCompat.getColor(context, R.color.yellow)) -// else -> binding.circularPercentage.progressTintList = ColorStateList.valueOf( -// ContextCompat.getColor(context, R.color.green)) -// } + } - binding.itemRatingSearch.numStars = percentage - binding.itemTitleSearch.text = name + override fun getItemCount(): Int { + return searchResults.itemCount + 1 + } - binding.root.setOnClickListener() { - it.findNavController().navigate(R.id.action_homeFragmentDest_to_TVShowDetail, bundleOf("id" to id, "screeningTitle" to name)) - } - } + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if(holder is SearchResultsHeaderHolder) + { + holder.headerText.text = "Results:" } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TVShowHolder = TVShowHolder( - ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) - ) + inner class SearchResultHolder (val binding: ItemFoundSearchBinding) : RecyclerView.ViewHolder(binding.root) + + class SearchResultsHeaderHolder(itemView : View) : RecyclerView.ViewHolder(itemView) + { + val headerText = R.layout.search_results_header as TextView + } - inner class TVShowHolder (val binding: ItemFoundSearchBinding) : RecyclerView.ViewHolder(binding.root) -} \ No newline at end of file +} diff --git a/app/src/main/res/layout/search_results_header.xml b/app/src/main/res/layout/search_results_header.xml new file mode 100644 index 00000000..0c3b1603 --- /dev/null +++ b/app/src/main/res/layout/search_results_header.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file From 9056ebb2e66fcf74c268007eea6b68d97d39e773 Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Tue, 7 Sep 2021 11:26:36 -0300 Subject: [PATCH 20/71] Change methods and add new StateFlow for Watchlist --- .../com/intive/tmdbandroid/home/ui/HomeFragment.kt | 11 +++++++++++ .../tmdbandroid/home/viewmodel/HomeViewModel.kt | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt index 45316fb4..6a9494f2 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt @@ -39,6 +39,7 @@ class HomeFragment : Fragment() { initViews() subscribePopularData() + subscribeWatchlistData() return binding.root } @@ -85,6 +86,16 @@ class HomeFragment : Fragment() { } } + private fun subscribeWatchlistData() { + lifecycleScope.launchWhenStarted { + viewModel.watchlistUIState.collectLatest { + when(it) { + + } + } + } + } + private fun initViews() { val rvTopTVShows = binding.rvPopularTVShows diff --git a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt index dfacce22..c1f32815 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt @@ -21,6 +21,9 @@ class HomeViewModel @Inject internal constructor( private val _state = MutableStateFlow(State.Loading) val uiState: StateFlow = _state + private val _watchlistState = MutableStateFlow(State.Loading) + val watchlistUIState: StateFlow = _watchlistState + fun popularTVShows() { if (_state.value !is State.Success<*>) viewModelScope.launch { @@ -36,6 +39,11 @@ class HomeViewModel @Inject internal constructor( } fun watchlistTVShows() { - //TODO implement after enable the Use Case for Room + if (_watchlistState.value !is State.Success<*>){ + viewModelScope.launch { + //TODO Use Case Room here + _watchlistState.value = State.Success("Data Room") + } + } } } From 6dfbf5258d0e106caafb3d22caad7d4bb0608019 Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Tue, 7 Sep 2021 11:46:57 -0300 Subject: [PATCH 21/71] Add Flows collects in DetailFragment and HomeFragment for watchlist feature. --- .../tmdbandroid/details/ui/DetailFragment.kt | 52 +++++++++++++++++-- .../details/viewmodel/DetailsViewModel.kt | 17 ++++-- .../tmdbandroid/home/ui/HomeFragment.kt | 19 +++++-- .../home/viewmodel/HomeViewModel.kt | 12 ++--- 4 files changed, 83 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt index 02500359..36276f47 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt @@ -23,6 +23,7 @@ import com.intive.tmdbandroid.details.viewmodel.DetailsViewModel import com.intive.tmdbandroid.model.TVShow import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collect +import kotlinx.coroutines.flow.collectLatest import java.text.SimpleDateFormat import java.util.* @@ -48,7 +49,8 @@ class DetailFragment : Fragment() { savedInstanceState: Bundle?, ): View { binding = FragmentDetailBinding.inflate(inflater, container, false) - collectDataFromViewModel() + collectTVShowDetailFromViewModel() + collectWatchlistDataFromViewModel() return binding.root } @@ -60,7 +62,7 @@ class DetailFragment : Fragment() { } } - private fun collectDataFromViewModel() { + private fun collectTVShowDetailFromViewModel() { binding.coordinatorContainerDetail.visibility = View.INVISIBLE lifecycleScope.launchWhenCreated { viewModel.uiState.collect { state -> @@ -68,7 +70,7 @@ class DetailFragment : Fragment() { is State.Success -> { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.GONE - setupUI(state.data) + setupTVShowDetailUI(state.data) } is State.Error -> { binding.layoutLoadingDetail.progressBar.visibility = View.GONE @@ -84,7 +86,49 @@ class DetailFragment : Fragment() { } } - private fun setupUI(tvShow: TVShow) { + private fun collectWatchlistDataFromViewModel() { + lifecycleScope.launchWhenStarted { + viewModel.addToWatchlistUIState.collectLatest { + when(it) { + is State.Success -> { + binding.layoutErrorDetail.errorContainer.visibility = View.GONE + binding.layoutLoadingDetail.progressBar.visibility = View.GONE + //TODO Add to Room + } + is State.Error -> { + binding.layoutLoadingDetail.progressBar.visibility = View.GONE + binding.layoutErrorDetail.errorContainer.visibility = View.VISIBLE + binding.coordinatorContainerDetail.visibility = View.VISIBLE + } + is State.Loading -> { + binding.layoutErrorDetail.errorContainer.visibility = View.GONE + binding.layoutLoadingDetail.progressBar.visibility = View.VISIBLE + } + } + } + + viewModel.removeFromWatchlistUIState.collectLatest { + when(it) { + is State.Success -> { + binding.layoutErrorDetail.errorContainer.visibility = View.GONE + binding.layoutLoadingDetail.progressBar.visibility = View.GONE + //TODO Remove from Room + } + is State.Error -> { + binding.layoutLoadingDetail.progressBar.visibility = View.GONE + binding.layoutErrorDetail.errorContainer.visibility = View.VISIBLE + binding.coordinatorContainerDetail.visibility = View.VISIBLE + } + is State.Loading -> { + binding.layoutErrorDetail.errorContainer.visibility = View.GONE + binding.layoutLoadingDetail.progressBar.visibility = View.VISIBLE + } + } + } + } + } + + private fun setupTVShowDetailUI(tvShow: TVShow) { setImages(tvShow) diff --git a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt index d4d2b5c3..0d737e36 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt @@ -19,9 +19,14 @@ class DetailsViewModel @Inject internal constructor( ) : ViewModel() { private val _state = MutableStateFlow>(State.Loading) - val uiState: StateFlow> = _state + private val _addToWatchlistState = MutableStateFlow>(State.Loading) + val addToWatchlistUIState: StateFlow> = _addToWatchlistState + + private val _removeFromWatchlistState = MutableStateFlow>(State.Loading) + val removeFromWatchlistUIState: StateFlow> = _removeFromWatchlistState + fun tVShows(id: Int) { viewModelScope.launch { tVShowUseCase(id) @@ -35,10 +40,16 @@ class DetailsViewModel @Inject internal constructor( } fun addToWatchlist() { - //TODO implement after enable the Use Case for Room + //TODO implement to Use Case for Room + viewModelScope.launch { + _addToWatchlistState.value = State.Success("Add to watchlist") + } } fun removeFromWatchlist() { - //TODO implement after enable the Use Case for Room + //TODO implement to Use Case for Room + viewModelScope.launch { + _removeFromWatchlistState.value = State.Success("Remove from watchlist") + } } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt index 77ea384d..03a1c2cc 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt @@ -1,7 +1,6 @@ package com.intive.tmdbandroid.home.ui import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -20,6 +19,7 @@ import com.intive.tmdbandroid.home.viewmodel.HomeViewModel import com.intive.tmdbandroid.model.TVShow import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest +import timber.log.Timber @AndroidEntryPoint class HomeFragment : Fragment() { @@ -33,6 +33,7 @@ class HomeFragment : Fragment() { super.onCreate(savedInstanceState) viewModel.popularTVShows() + viewModel.watchlistTVShows() } override fun onCreateView( @@ -66,7 +67,7 @@ class HomeFragment : Fragment() { binding.layoutProgressbar.progressBar.visibility = View.VISIBLE lifecycleScope.launchWhenStarted { viewModel.uiState.collectLatest { resultTVShows -> - Log.i("MAS", "popular tvshows status: $resultTVShows") + Timber.tag("MAS").i("popular tvshows status: %s", resultTVShows) when (resultTVShows) { is State.Success> -> { @@ -95,7 +96,19 @@ class HomeFragment : Fragment() { lifecycleScope.launchWhenStarted { viewModel.watchlistUIState.collectLatest { when(it) { - + is State.Success -> { + binding.layoutError.errorContainer.visibility = View.GONE + binding.layoutProgressbar.progressBar.visibility = View.GONE + //TODO Mostrar datos en la lista de watchlists. + } + is State.Error -> { + binding.layoutProgressbar.progressBar.visibility = View.GONE + binding.layoutError.errorContainer.visibility = View.VISIBLE + } + is State.Loading -> { + binding.layoutProgressbar.progressBar.visibility = View.VISIBLE + binding.layoutError.errorContainer.visibility = View.GONE + } } } } diff --git a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt index cc9ab281..b0485137 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt @@ -23,8 +23,8 @@ class HomeViewModel @Inject internal constructor( private val _state = MutableStateFlow>>(State.Loading) val uiState: StateFlow>> = _state - private val _watchlistState = MutableStateFlow(State.Loading) - val watchlistUIState: StateFlow = _watchlistState + private val _watchlistState = MutableStateFlow>(State.Loading) + val watchlistUIState: StateFlow> = _watchlistState fun popularTVShows() { viewModelScope.launch { @@ -40,11 +40,9 @@ class HomeViewModel @Inject internal constructor( } fun watchlistTVShows() { - if (_watchlistState.value !is State.Success<*>){ - viewModelScope.launch { - //TODO Use Case Room here - _watchlistState.value = State.Success("Data Room") - } + viewModelScope.launch { + //TODO Use Case Room here + _watchlistState.value = State.Success("Data Room") } } } From 68bc48f183a9713a8a3dccbbc800c3d981fd77c8 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Tue, 7 Sep 2021 17:30:44 -0300 Subject: [PATCH 22/71] Fix api call url --- .../com/intive/tmdbandroid/datasource/network/ApiClient.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt index cf2da013..41612fbd 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt @@ -15,7 +15,7 @@ interface ApiClient { suspend fun getTVShowByID(@Path("tv_id") tvShowID: Int, @Query("api_key") apiKey: String) : TVShow - @GET("search/movie") - suspend fun getTVShowByName(@Path("tv_id") tvShowName: String, - @Query("api_key") apiKey: String) : TVShow + @GET("search/tv") + suspend fun getTVShowByName(@Query("api_key") apiKey: String, + @Query("query") query: String) : ResultTVShowsEntity } \ No newline at end of file From cc25da0d5a44771154bb9e5395bc606e5be5fbf5 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Tue, 7 Sep 2021 17:48:22 -0300 Subject: [PATCH 23/71] Change search method's return type --- .../com/intive/tmdbandroid/repository/CatalogRepository.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt b/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt index 1ffd5f02..fdd64703 100644 --- a/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt +++ b/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt @@ -7,6 +7,7 @@ import com.intive.tmdbandroid.datasource.TVShowPagingSource import com.intive.tmdbandroid.datasource.network.Service import com.intive.tmdbandroid.model.TVShow import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject import javax.inject.Singleton @@ -34,7 +35,7 @@ class CatalogRepository @Inject constructor( return service.getTVShowByID(id) } - fun search(name:String): Flow { - return service.getTvShowByTitle(name) + fun search(name:String): Flow> { + return service.getTvShowByTitle(name).map { it.toTVShowList() } } } \ No newline at end of file From fdcaa03f94fe0396f19c83c307928388f793e5d4 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Tue, 7 Sep 2021 17:51:15 -0300 Subject: [PATCH 24/71] Edit Search Fragment and Result Item's xml --- app/src/main/res/layout/fragment_search.xml | 3 ++- app/src/main/res/layout/item_found_search.xml | 15 +++++++++---- .../main/res/layout/search_results_header.xml | 21 ++++++++++++++++--- app/src/main/res/values/strings.xml | 1 + 4 files changed, 32 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index ff659fe0..f95416b9 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -43,5 +43,6 @@ app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/fragment_search_toolbar" - tools:listitem="@layout/item_found_search" /> + tools:listitem="@layout/item_found_search" + /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_found_search.xml b/app/src/main/res/layout/item_found_search.xml index 70776ce8..00f048fc 100644 --- a/app/src/main/res/layout/item_found_search.xml +++ b/app/src/main/res/layout/item_found_search.xml @@ -1,15 +1,21 @@ - + android:layout_height="wrap_content" + app:cardElevation="3dp" + android:layout_marginStart="16dp" + android:layout_marginEnd="16dp" + android:layout_marginVertical="8dp" + app:cardCornerRadius="5dp" + android:layout_marginBottom="24dp"> + app:cardCornerRadius="5dp"> diff --git a/app/src/main/res/layout/search_results_header.xml b/app/src/main/res/layout/search_results_header.xml index 0c3b1603..28263dfe 100644 --- a/app/src/main/res/layout/search_results_header.xml +++ b/app/src/main/res/layout/search_results_header.xml @@ -1,6 +1,21 @@ - \ No newline at end of file + android:layout_height="wrap_content"> + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dd907f2b..8437658b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,7 @@ Warning Overview Add to Watchlist + Search a TV Show or Movie %d Season From cb3170d6d293bb0b76852e9d4331a848a4556c1d Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Tue, 7 Sep 2021 18:02:53 -0300 Subject: [PATCH 25/71] Change Flow's type to List of TVShows --- .../com/intive/tmdbandroid/datasource/network/Service.kt | 2 +- .../intive/tmdbandroid/search/viewmodel/SearchViewModel.kt | 5 +++-- .../java/com/intive/tmdbandroid/usecase/SearchUseCase.kt | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt index 62ccae78..448ae3bf 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt @@ -22,7 +22,7 @@ class Service { } } - fun getTvShowByTitle(tvShowTitle: String): Flow { + fun getTvShowByTitle(tvShowTitle: String): Flow { return flow { emit(retrofit.create(ApiClient::class.java).getTVShowByName(BuildConfig.API_KEY, tvShowTitle)) } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt index 80b8e2d8..fccc03d1 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt @@ -3,6 +3,7 @@ package com.intive.tmdbandroid.search.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.intive.tmdbandroid.common.State +import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.usecase.SearchUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -17,9 +18,9 @@ class SearchViewModel @Inject constructor( private val searchUseCase: SearchUseCase ) : ViewModel() { - private val _state = MutableStateFlow(State.Loading) + private val _state = MutableStateFlow>>(State.Loading) - val uiState: StateFlow = _state + val uiState: StateFlow>> = _state fun search(name:String) { viewModelScope.launch { diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt index 2f887e72..6200599e 100644 --- a/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt @@ -7,5 +7,5 @@ import javax.inject.Inject class SearchUseCase @Inject constructor(private val catalogRepository: CatalogRepository) { - operator fun invoke(name:String): Flow = catalogRepository.search(name) + operator fun invoke(name:String): Flow> = catalogRepository.search(name) } \ No newline at end of file From f954c371d117f740b7cae1781a707961224a95a6 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Tue, 7 Sep 2021 18:04:21 -0300 Subject: [PATCH 26/71] Add header type, subscribe and init methods --- .../search/ui/adapters/TVShowSearchAdapter.kt | 87 ++++++++++++++----- 1 file changed, 65 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index b506355b..8a0403e7 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -1,31 +1,23 @@ package com.intive.tmdbandroid.search.ui.adapters -import android.content.res.ColorStateList import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.TextView -import androidx.core.content.ContextCompat -import androidx.core.os.bundleOf -import androidx.navigation.findNavController -import androidx.paging.PagingDataAdapter -import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.intive.tmdbandroid.R import com.intive.tmdbandroid.databinding.ItemFoundSearchBinding -import com.intive.tmdbandroid.databinding.ItemScreeningBinding -import com.intive.tmdbandroid.home.ui.adapters.TVShowPageAdapter +import com.intive.tmdbandroid.databinding.SearchResultsHeaderBinding import com.intive.tmdbandroid.model.TVShow +import timber.log.Timber +import java.lang.Exception import java.text.SimpleDateFormat import java.util.* +import kotlin.collections.ArrayList -private const val TYPE_HEADER : Int = 0 -private const val TYPE_LIST : Int = 1 - - -class TVShowSearchAdapter(private val searchResults : PagingDataAdapter) : RecyclerView.Adapter() +class TVShowSearchAdapter (private val tvShowList: ArrayList) : RecyclerView.Adapter() { private val TYPE_HEADER : Int = 0 private val TYPE_LIST : Int = 1 @@ -40,8 +32,10 @@ class TVShowSearchAdapter(private val searchResults : PagingDataAdapter { - val header = LayoutInflater.from(parent.context).inflate(R.layout.search_results_header,parent,false) - SearchResultsHeaderHolder(header) + val header = SearchResultsHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val holder = SearchResultsHeaderHolder(header) + holder.headerText.text = "Results" + return holder } else -> { val header = ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) SearchResultHolder(header) @@ -51,21 +45,70 @@ class TVShowSearchAdapter(private val searchResults : PagingDataAdapter){ + tvShowList.clear() + tvShowList.addAll(result) + notifyDataSetChanged() } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - if(holder is SearchResultsHeaderHolder) - { - holder.headerText.text = "Results:" + when(holder){ + is SearchResultsHeaderHolder -> holder.bind() + is SearchResultHolder -> holder.bind(tvShowList[position - 1]) } } - inner class SearchResultHolder (val binding: ItemFoundSearchBinding) : RecyclerView.ViewHolder(binding.root) + inner class SearchResultHolder (val binding: ItemFoundSearchBinding) : RecyclerView.ViewHolder(binding.root){ + + private val itemTitle = binding.itemTitleSearch + private val itemYear = binding.itemYearSearch + private val itemSeasons = binding.itemSeasonsSearch + private val itemRating = binding.itemRatingSearch + + fun bind(item: TVShow){ + try { + val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(item.first_air_date) + itemYear.text = date.year.toString() + } catch (e : Exception){ + Timber.e(e) + } + + itemTitle.text = item.name + itemSeasons.text = item.number_of_seasons?.let { + binding.root.resources.getQuantityString( + R.plurals.numberOfSeasons, + it, + it + ) + } + itemRating.rating = item.vote_average.toFloat()/2 + + val options = RequestOptions() + .centerCrop() + .placeholder(R.drawable.ic_image) + .error(R.drawable.ic_image) + + val posterURL = binding.root.resources.getString(R.string.base_imageURL) + item.poster_path + + Glide.with(binding.root.context) + .load(posterURL) + .apply(options) + .into(binding.itemPosterSearch) - class SearchResultsHeaderHolder(itemView : View) : RecyclerView.ViewHolder(itemView) + + } + } + + inner class SearchResultsHeaderHolder(binding: SearchResultsHeaderBinding) : RecyclerView.ViewHolder(binding.root) { - val headerText = R.layout.search_results_header as TextView + val headerText = binding.searchHeader + + fun bind(){ + headerText.text = "Results for:" + } } } From 706164e7e9a74489d7ba534b917a19f5edee0c84 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Tue, 7 Sep 2021 18:06:49 -0300 Subject: [PATCH 27/71] Add subscribe and init methods when searchView is submitted --- .../tmdbandroid/search/ui/SearchFragment.kt | 64 +++++++++++++++++-- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 4ed28a20..bea2e754 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -1,24 +1,34 @@ package com.intive.tmdbandroid.search.ui import android.os.Bundle +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.SearchView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.navigation.fragment.NavHostFragment.findNavController +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController +import androidx.paging.PagingData +import androidx.recyclerview.widget.GridLayoutManager +import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.databinding.FragmentSearchBinding +import com.intive.tmdbandroid.model.TVShow +import com.intive.tmdbandroid.search.ui.adapters.TVShowSearchAdapter import com.intive.tmdbandroid.search.viewmodel.SearchViewModel import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.collectLatest @AndroidEntryPoint class SearchFragment: Fragment() { - // private val viewModel: SearchViewModel by viewModels() + private val viewModel: SearchViewModel by viewModels() - // private val searchAdapter = SearchViewModel() + private val searchList = arrayListOf() + + private val searchAdapter = TVShowSearchAdapter(searchList) private lateinit var binding: FragmentSearchBinding @@ -30,14 +40,31 @@ class SearchFragment: Fragment() { savedInstanceState: Bundle? ): View { binding = FragmentSearchBinding.inflate(inflater, container, false) + binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + + override fun onQueryTextChange(newText: String): Boolean { + return false + } + + override fun onQueryTextSubmit(query: String): Boolean { + if (query.isNotEmpty()){ + viewModel.search(query) + subscribeViewModel() + initViews() + println("ya busque") + return true + } + return false + } + }) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar() - //viewModel.search() + } private fun setupToolbar() { @@ -47,4 +74,33 @@ class SearchFragment: Fragment() { toolbar.setupWithNavController(navController, appBarConfiguration) binding.searchView.requestFocus() } + + fun subscribeViewModel(){ + lifecycleScope.launchWhenStarted { + viewModel.uiState.collectLatest { resultTVShow -> + + when (resultTVShow) { + is State.Success> -> { + searchAdapter.refresh(resultTVShow.data) + println("ta bien") + } + is State.Error -> { + println("ta mal") + } + is State.Loading -> { + println("ta cargando") + } + } + } + } + } + + private fun initViews() { + val resultsList = binding.searchResults + + resultsList.apply { + layoutManager = GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false) + adapter = searchAdapter + } + } } \ No newline at end of file From af544d991f74dcde7cb647483a8c8b28b88f6409 Mon Sep 17 00:00:00 2001 From: "andres.seoane" Date: Wed, 8 Sep 2021 12:26:24 -0300 Subject: [PATCH 28/71] Added navigation from SearchFragment.kt -> DetailFragment.kt --- .../intive/tmdbandroid/search/ui/SearchFragment.kt | 7 +++++++ .../search/ui/adapters/TVShowSearchAdapter.kt | 12 +++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index bea2e754..30f51671 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -9,6 +9,7 @@ import android.widget.SearchView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope +import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController @@ -16,6 +17,7 @@ import androidx.paging.PagingData import androidx.recyclerview.widget.GridLayoutManager import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.databinding.FragmentSearchBinding +import com.intive.tmdbandroid.home.ui.HomeFragmentDirections import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.search.ui.adapters.TVShowSearchAdapter import com.intive.tmdbandroid.search.viewmodel.SearchViewModel @@ -100,6 +102,11 @@ class SearchFragment: Fragment() { resultsList.apply { layoutManager = GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false) + + searchAdapter.clickListener = { tvShow -> + val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetail(tvShow.id) + findNavController().navigate(action) + } adapter = searchAdapter } } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index 8a0403e7..f4ea04c4 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -22,6 +22,8 @@ class TVShowSearchAdapter (private val tvShowList: ArrayList) : Recycler private val TYPE_HEADER : Int = 0 private val TYPE_LIST : Int = 1 + var clickListener: ((TVShow) -> Unit)? = null + override fun getItemViewType(position: Int): Int { return when(position){ 0 -> TYPE_HEADER @@ -57,7 +59,9 @@ class TVShowSearchAdapter (private val tvShowList: ArrayList) : Recycler override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when(holder){ is SearchResultsHeaderHolder -> holder.bind() - is SearchResultHolder -> holder.bind(tvShowList[position - 1]) + is SearchResultHolder -> { + holder.bind(tvShowList[position - 1]) + } } } @@ -68,6 +72,12 @@ class TVShowSearchAdapter (private val tvShowList: ArrayList) : Recycler private val itemSeasons = binding.itemSeasonsSearch private val itemRating = binding.itemRatingSearch + init { + itemView.setOnClickListener { + clickListener?.invoke(tvShowList[absoluteAdapterPosition - 1]) + } + } + fun bind(item: TVShow){ try { val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(item.first_air_date) From f524ecb34eeddeb01ebc57eaae6cc4d5eadddc9d Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Wed, 8 Sep 2021 15:19:17 -0300 Subject: [PATCH 29/71] Add pagination to Search Fragment's RecyclerView --- .../datasource/TVShowSearchSource.kt | 43 +++++++++++++++++++ .../datasource/network/ApiClient.kt | 3 +- .../tmdbandroid/datasource/network/Service.kt | 4 +- .../repository/CatalogRepository.kt | 13 +++++- .../tmdbandroid/search/ui/SearchFragment.kt | 10 ++--- .../search/ui/adapters/TVShowSearchAdapter.kt | 37 +++++++++------- .../search/viewmodel/SearchViewModel.kt | 8 +++- .../tmdbandroid/usecase/SearchUseCase.kt | 3 +- 8 files changed, 91 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt new file mode 100644 index 00000000..7a5de203 --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt @@ -0,0 +1,43 @@ +package com.intive.tmdbandroid.datasource + +import androidx.paging.PagingSource +import com.intive.tmdbandroid.entity.ResultTVShowsEntity +import androidx.paging.PagingState +import com.intive.tmdbandroid.datasource.network.Service +import com.intive.tmdbandroid.model.TVShow +import kotlinx.coroutines.flow.collect +import retrofit2.HttpException +import java.io.IOException + +class TVShowSearchSource(private val service: Service, private val query: String) : PagingSource() { + companion object { + const val DEFAULT_PAGE_INDEX = 1 + } + + override suspend fun load(params: LoadParams): LoadResult { + return try { + val pageNumber = params.key ?: DEFAULT_PAGE_INDEX + + lateinit var response: ResultTVShowsEntity + service.getTvShowByTitle(query, pageNumber).collect { response = it } + + val prevKey = if (pageNumber > DEFAULT_PAGE_INDEX) pageNumber - 1 else null + val nextKey = if (response.TVShows.isNotEmpty()) pageNumber + 1 else null + + LoadResult.Page( + data = response.toTVShowList(), + prevKey = prevKey, + nextKey = nextKey + ) + } catch (e: Exception) { + LoadResult.Error(e) + } + } + + override fun getRefreshKey(state: PagingState): Int? { + return state.anchorPosition?.let { + state.closestPageToPosition(it)?.prevKey?.plus(1) + ?: state.closestPageToPosition(it)?.nextKey?.minus(1) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt index 41612fbd..bd95100b 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt @@ -17,5 +17,6 @@ interface ApiClient { @GET("search/tv") suspend fun getTVShowByName(@Query("api_key") apiKey: String, - @Query("query") query: String) : ResultTVShowsEntity + @Query("query") query: String, + @Query("page") page: Int) : ResultTVShowsEntity } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt index 448ae3bf..4d3a1005 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt @@ -22,9 +22,9 @@ class Service { } } - fun getTvShowByTitle(tvShowTitle: String): Flow { + fun getTvShowByTitle(tvShowTitle: String, page: Int): Flow { return flow { - emit(retrofit.create(ApiClient::class.java).getTVShowByName(BuildConfig.API_KEY, tvShowTitle)) + emit(retrofit.create(ApiClient::class.java).getTVShowByName(BuildConfig.API_KEY, tvShowTitle, page)) } } } diff --git a/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt b/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt index fdd64703..1243ed84 100644 --- a/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt +++ b/app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt @@ -4,6 +4,7 @@ import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData import com.intive.tmdbandroid.datasource.TVShowPagingSource +import com.intive.tmdbandroid.datasource.TVShowSearchSource import com.intive.tmdbandroid.datasource.network.Service import com.intive.tmdbandroid.model.TVShow import kotlinx.coroutines.flow.Flow @@ -35,7 +36,15 @@ class CatalogRepository @Inject constructor( return service.getTVShowByID(id) } - fun search(name:String): Flow> { - return service.getTvShowByTitle(name).map { it.toTVShowList() } + fun search(name:String): Flow> { + return Pager( + config = PagingConfig( + pageSize = DEFAULT_PAGE_SIZE, + enablePlaceholders = false + ), + pagingSourceFactory = { + TVShowSearchSource(service = service, name) + } + ).flow } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index bea2e754..aa9feccb 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -26,14 +26,10 @@ import kotlinx.coroutines.flow.collectLatest class SearchFragment: Fragment() { private val viewModel: SearchViewModel by viewModels() - private val searchList = arrayListOf() - - private val searchAdapter = TVShowSearchAdapter(searchList) + private val searchAdapter = TVShowSearchAdapter() private lateinit var binding: FragmentSearchBinding - private var tvShowName: String? = null - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -80,8 +76,8 @@ class SearchFragment: Fragment() { viewModel.uiState.collectLatest { resultTVShow -> when (resultTVShow) { - is State.Success> -> { - searchAdapter.refresh(resultTVShow.data) + is State.Success> -> { + searchAdapter.submitData(resultTVShow.data) println("ta bien") } is State.Error -> { diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index 8a0403e7..6c55fd2e 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -1,9 +1,11 @@ package com.intive.tmdbandroid.search.ui.adapters +import android.util.Log import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import android.widget.TextView +import androidx.paging.PagingData +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions @@ -15,10 +17,14 @@ import timber.log.Timber import java.lang.Exception import java.text.SimpleDateFormat import java.util.* -import kotlin.collections.ArrayList -class TVShowSearchAdapter (private val tvShowList: ArrayList) : RecyclerView.Adapter() -{ +class TVShowSearchAdapter : PagingDataAdapter(REPO_COMPARATOR) { + companion object { + private val REPO_COMPARATOR = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) + override fun areContentsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) + } +} private val TYPE_HEADER : Int = 0 private val TYPE_LIST : Int = 1 @@ -45,19 +51,18 @@ class TVShowSearchAdapter (private val tvShowList: ArrayList) : Recycler } override fun getItemCount(): Int { - return tvShowList.size + 1 - } - - fun refresh(result: List){ - tvShowList.clear() - tvShowList.addAll(result) - notifyDataSetChanged() + val count = super.getItemCount() + 1 + println(count) + return count } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when(holder){ is SearchResultsHeaderHolder -> holder.bind() - is SearchResultHolder -> holder.bind(tvShowList[position - 1]) + is SearchResultHolder -> { + val tvShowItem = getItem(position - 1) as TVShow + holder.bind(tvShowItem) + } } } @@ -70,8 +75,10 @@ class TVShowSearchAdapter (private val tvShowList: ArrayList) : Recycler fun bind(item: TVShow){ try { - val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(item.first_air_date) - itemYear.text = date.year.toString() + if(item.first_air_date.toString().isNotEmpty()){ + val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(item.first_air_date) + itemYear.text = date.year.toString() + } } catch (e : Exception){ Timber.e(e) } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt index fccc03d1..1e4309f2 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt @@ -2,6 +2,9 @@ package com.intive.tmdbandroid.search.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.paging.PagedList +import androidx.paging.PagingData +import androidx.paging.cachedIn import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.usecase.SearchUseCase @@ -18,13 +21,14 @@ class SearchViewModel @Inject constructor( private val searchUseCase: SearchUseCase ) : ViewModel() { - private val _state = MutableStateFlow>>(State.Loading) + private val _state = MutableStateFlow>>(State.Loading) - val uiState: StateFlow>> = _state + val uiState: StateFlow>> = _state fun search(name:String) { viewModelScope.launch { searchUseCase(name) + .cachedIn(viewModelScope) .catch { _state.value = State.Error } diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt index 6200599e..bebdae18 100644 --- a/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/SearchUseCase.kt @@ -1,5 +1,6 @@ package com.intive.tmdbandroid.usecase +import androidx.paging.PagingData import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.repository.CatalogRepository import kotlinx.coroutines.flow.Flow @@ -7,5 +8,5 @@ import javax.inject.Inject class SearchUseCase @Inject constructor(private val catalogRepository: CatalogRepository) { - operator fun invoke(name:String): Flow> = catalogRepository.search(name) + operator fun invoke(name:String): Flow> = catalogRepository.search(name) } \ No newline at end of file From 6ec6be15085dd39e052fa6c4cc0d291f3480b740 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Wed, 8 Sep 2021 17:14:08 -0300 Subject: [PATCH 30/71] Change itemView's clickListener invocation to bind function --- .../tmdbandroid/datasource/TVShowSearchSource.kt | 4 +++- .../search/ui/adapters/TVShowSearchAdapter.kt | 11 ++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt index 7a5de203..025a6d6d 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt @@ -1,5 +1,6 @@ package com.intive.tmdbandroid.datasource +import android.util.Log import androidx.paging.PagingSource import com.intive.tmdbandroid.entity.ResultTVShowsEntity import androidx.paging.PagingState @@ -7,6 +8,7 @@ import com.intive.tmdbandroid.datasource.network.Service import com.intive.tmdbandroid.model.TVShow import kotlinx.coroutines.flow.collect import retrofit2.HttpException +import timber.log.Timber import java.io.IOException class TVShowSearchSource(private val service: Service, private val query: String) : PagingSource() { @@ -17,7 +19,7 @@ class TVShowSearchSource(private val service: Service, private val query: String override suspend fun load(params: LoadParams): LoadResult { return try { val pageNumber = params.key ?: DEFAULT_PAGE_INDEX - + Timber.d("$pageNumber") lateinit var response: ResultTVShowsEntity service.getTvShowByTitle(query, pageNumber).collect { response = it } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index 38cae080..73789269 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -53,9 +53,7 @@ class TVShowSearchAdapter : PagingDataAdapter(R } override fun getItemCount(): Int { - val count = super.getItemCount() + 1 - println(count) - return count + return super.getItemCount() + 1 } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { @@ -75,13 +73,12 @@ class TVShowSearchAdapter : PagingDataAdapter(R private val itemSeasons = binding.itemSeasonsSearch private val itemRating = binding.itemRatingSearch - init { + fun bind(item: TVShow){ + itemView.setOnClickListener { - getItem(absoluteAdapterPosition - 1)?.let { it1 -> clickListener?.invoke(it1) } + clickListener?.invoke(item) } - } - fun bind(item: TVShow){ try { if(item.first_air_date.toString().isNotEmpty()){ val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(item.first_air_date) From 256bf38af0ab8394462908413571fde0b7702c27 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 9 Sep 2021 09:58:33 -0300 Subject: [PATCH 31/71] Add search's query to recycler header --- .../tmdbandroid/search/ui/SearchFragment.kt | 1 + .../search/ui/adapters/TVShowSearchAdapter.kt | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 79858395..77a76340 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -46,6 +46,7 @@ class SearchFragment: Fragment() { override fun onQueryTextSubmit(query: String): Boolean { if (query.isNotEmpty()){ + searchAdapter.query = query viewModel.search(query) subscribeViewModel() initViews() diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index 73789269..a73a347e 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -1,9 +1,7 @@ package com.intive.tmdbandroid.search.ui.adapters -import android.util.Log import android.view.LayoutInflater import android.view.ViewGroup -import androidx.paging.PagingData import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView @@ -28,6 +26,8 @@ class TVShowSearchAdapter : PagingDataAdapter(R private val TYPE_HEADER : Int = 0 private val TYPE_LIST : Int = 1 + lateinit var query: String + var clickListener: ((TVShow) -> Unit)? = null override fun getItemViewType(position: Int): Int { @@ -40,10 +40,12 @@ class TVShowSearchAdapter : PagingDataAdapter(R override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when(viewType){ TYPE_HEADER -> { - val header = SearchResultsHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false) - val holder = SearchResultsHeaderHolder(header) - holder.headerText.text = "Results" - return holder + val header = SearchResultsHeaderBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return SearchResultsHeaderHolder(header) } else -> { val header = ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) SearchResultHolder(header) @@ -119,7 +121,7 @@ class TVShowSearchAdapter : PagingDataAdapter(R val headerText = binding.searchHeader fun bind(){ - headerText.text = "Results for:" + headerText.text = "Results for: $query" } } From e13a840c04fc819a7e6213ae75eab44224b54692 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 9 Sep 2021 11:13:19 -0300 Subject: [PATCH 32/71] Change TVShowSearchAdapter's getItemCount to be just the original count --- .../com/intive/tmdbandroid/datasource/TVShowSearchSource.kt | 2 +- .../java/com/intive/tmdbandroid/search/ui/SearchFragment.kt | 2 +- .../tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt | 2 +- app/src/main/res/layout/fragment_search.xml | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt index 025a6d6d..7a9b879b 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt @@ -19,7 +19,7 @@ class TVShowSearchSource(private val service: Service, private val query: String override suspend fun load(params: LoadParams): LoadResult { return try { val pageNumber = params.key ?: DEFAULT_PAGE_INDEX - Timber.d("$pageNumber") + Timber.d("pageNumber: $pageNumber") lateinit var response: ResultTVShowsEntity service.getTvShowByTitle(query, pageNumber).collect { response = it } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 77a76340..f29f9c5f 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -48,9 +48,9 @@ class SearchFragment: Fragment() { if (query.isNotEmpty()){ searchAdapter.query = query viewModel.search(query) + binding.searchView.clearFocus() subscribeViewModel() initViews() - println("ya busque") return true } return false diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index a73a347e..7ba4a2a9 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -55,7 +55,7 @@ class TVShowSearchAdapter : PagingDataAdapter(R } override fun getItemCount(): Int { - return super.getItemCount() + 1 + return super.getItemCount() } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index f95416b9..a997544e 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -28,7 +28,6 @@ android:iconifiedByDefault="false" android:queryHint="@string/search_text" android:searchIcon="@null" - android:translationX="-32dp" tools:layout_editor_absoluteX="16dp" tools:layout_editor_absoluteY="4dp" /> @@ -38,11 +37,12 @@ android:id="@+id/search_results" android:layout_width="match_parent" android:layout_height="0dp" + android:paddingBottom="16dp" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/fragment_search_toolbar" - tools:listitem="@layout/item_found_search" - /> + tools:listitem="@layout/item_found_search" /> \ No newline at end of file From f478cd02b6547749c39c8f244def9c420c215b6c Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 9 Sep 2021 12:06:16 -0300 Subject: [PATCH 33/71] Fix pagination issue when coming back from details to search --- .../java/com/intive/tmdbandroid/search/ui/SearchFragment.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index f29f9c5f..4cde80ca 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -50,13 +50,13 @@ class SearchFragment: Fragment() { viewModel.search(query) binding.searchView.clearFocus() subscribeViewModel() - initViews() return true } return false } }) + initViews() return binding.root } From 03a7bae720e231dbc9578b442fbe7b7ffb091e8a Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 9 Sep 2021 12:06:39 -0300 Subject: [PATCH 34/71] Remove Search Fragment's title --- app/src/main/res/navigation/nav_graph.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml index 8b1907b8..9fdc4e61 100644 --- a/app/src/main/res/navigation/nav_graph.xml +++ b/app/src/main/res/navigation/nav_graph.xml @@ -32,7 +32,6 @@ Date: Thu, 9 Sep 2021 14:55:16 -0300 Subject: [PATCH 35/71] Add Hint layout to search Fragment --- .../tmdbandroid/search/ui/SearchFragment.kt | 10 ++++-- app/src/main/res/drawable/ic_search_hint.xml | 5 +++ app/src/main/res/layout/fragment_search.xml | 26 ++++++++++++++ .../main/res/layout/layout_search_hint.xml | 36 +++++++++++++++++++ .../main/res/layout/search_results_header.xml | 1 - app/src/main/res/values/colors.xml | 2 ++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/ic_search_hint.xml create mode 100644 app/src/main/res/layout/layout_search_hint.xml diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 4cde80ca..be0fd423 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -38,6 +38,7 @@ class SearchFragment: Fragment() { savedInstanceState: Bundle? ): View { binding = FragmentSearchBinding.inflate(inflater, container, false) + binding.layoutProgressbar.progressBar.visibility = View.GONE binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String): Boolean { @@ -80,14 +81,17 @@ class SearchFragment: Fragment() { when (resultTVShow) { is State.Success> -> { + binding.layoutSearchHint.hintContainer.visibility = View.GONE + binding.layoutProgressbar.progressBar.visibility = View.GONE searchAdapter.submitData(resultTVShow.data) - println("ta bien") } is State.Error -> { - println("ta mal") + binding.layoutError.errorContainer.visibility = View.VISIBLE + binding.layoutSearchHint.hintContainer.visibility = View.GONE + binding.layoutProgressbar.progressBar.visibility = View.GONE } is State.Loading -> { - println("ta cargando") + binding.layoutProgressbar.progressBar.visibility = View.VISIBLE } } } diff --git a/app/src/main/res/drawable/ic_search_hint.xml b/app/src/main/res/drawable/ic_search_hint.xml new file mode 100644 index 00000000..7ccdb5eb --- /dev/null +++ b/app/src/main/res/drawable/ic_search_hint.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index a997544e..176fb3b9 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -45,4 +45,30 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/fragment_search_toolbar" tools:listitem="@layout/item_found_search" /> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_search_hint.xml b/app/src/main/res/layout/layout_search_hint.xml new file mode 100644 index 00000000..4913c825 --- /dev/null +++ b/app/src/main/res/layout/layout_search_hint.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_results_header.xml b/app/src/main/res/layout/search_results_header.xml index 28263dfe..e90a0f50 100644 --- a/app/src/main/res/layout/search_results_header.xml +++ b/app/src/main/res/layout/search_results_header.xml @@ -13,7 +13,6 @@ android:paddingEnd="16dp" android:paddingTop="24dp" android:paddingBottom="24dp" - android:textColor="@color/black" android:textSize="16sp" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index cd9ac96f..3cd3d772 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -1,6 +1,8 @@ #FF000000 + #BEBEBE + #949494 #FFFFFFFF #0198c5 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8437658b..41e23045 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,6 +12,7 @@ Overview Add to Watchlist Search a TV Show or Movie + Start typing to find awesome TV shows %d Season From 5788403d67feaa3d7a48bd19a905650dc53cc5c7 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 9 Sep 2021 15:06:08 -0300 Subject: [PATCH 36/71] Fix Search Fragment's hint layout visibility when coming back from Details --- .../com/intive/tmdbandroid/search/ui/SearchFragment.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index be0fd423..6c0fe61e 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -61,6 +61,13 @@ class SearchFragment: Fragment() { return binding.root } + override fun onResume() { + super.onResume() + if(binding.searchView.query.isNotEmpty()){ + binding.layoutSearchHint.hintContainer.visibility = View.GONE + } + } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar() From 02eeb8ed30647d84bc898324d30b42b6292eaf6d Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 10 Sep 2021 10:03:00 -0300 Subject: [PATCH 37/71] Revert itemCount + 1 --- .../intive/tmdbandroid/search/ui/SearchFragment.kt | 13 ++++++------- .../search/ui/adapters/TVShowSearchAdapter.kt | 14 +++++++------- app/src/main/res/layout/layout_search_hint.xml | 5 +++-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 6c0fe61e..96b351d0 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -1,7 +1,6 @@ package com.intive.tmdbandroid.search.ui import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -17,12 +16,12 @@ import androidx.paging.PagingData import androidx.recyclerview.widget.GridLayoutManager import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.databinding.FragmentSearchBinding -import com.intive.tmdbandroid.home.ui.HomeFragmentDirections import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.search.ui.adapters.TVShowSearchAdapter import com.intive.tmdbandroid.search.viewmodel.SearchViewModel import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest +import timber.log.Timber @AndroidEntryPoint class SearchFragment: Fragment() { @@ -48,16 +47,15 @@ class SearchFragment: Fragment() { override fun onQueryTextSubmit(query: String): Boolean { if (query.isNotEmpty()){ searchAdapter.query = query - viewModel.search(query) binding.searchView.clearFocus() + viewModel.search(query) subscribeViewModel() + initViews() return true } return false } - }) - initViews() return binding.root } @@ -98,6 +96,7 @@ class SearchFragment: Fragment() { binding.layoutProgressbar.progressBar.visibility = View.GONE } is State.Loading -> { + binding.layoutSearchHint.hintContainer.visibility = View.GONE binding.layoutProgressbar.progressBar.visibility = View.VISIBLE } } @@ -109,12 +108,12 @@ class SearchFragment: Fragment() { val resultsList = binding.searchResults resultsList.apply { - layoutManager = GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false) - searchAdapter.clickListener = { tvShow -> val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetail(tvShow.id) findNavController().navigate(action) } + + layoutManager = GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false) adapter = searchAdapter } } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index 7ba4a2a9..fb132008 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -16,7 +16,7 @@ import java.lang.Exception import java.text.SimpleDateFormat import java.util.* -class TVShowSearchAdapter : PagingDataAdapter(REPO_COMPARATOR) { +class TVShowSearchAdapter() : PagingDataAdapter(REPO_COMPARATOR) { companion object { private val REPO_COMPARATOR = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) @@ -26,10 +26,14 @@ class TVShowSearchAdapter : PagingDataAdapter(R private val TYPE_HEADER : Int = 0 private val TYPE_LIST : Int = 1 - lateinit var query: String + var query: String = "" var clickListener: ((TVShow) -> Unit)? = null + override fun getItemCount(): Int { + return super.getItemCount() + 1 + } + override fun getItemViewType(position: Int): Int { return when(position){ 0 -> TYPE_HEADER @@ -54,10 +58,6 @@ class TVShowSearchAdapter : PagingDataAdapter(R } - override fun getItemCount(): Int { - return super.getItemCount() - } - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when(holder){ is SearchResultsHeaderHolder -> holder.bind() @@ -82,7 +82,7 @@ class TVShowSearchAdapter : PagingDataAdapter(R } try { - if(item.first_air_date.toString().isNotEmpty()){ + if(!item.first_air_date.isNullOrBlank()){ val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(item.first_air_date) itemYear.text = date.year.toString() } diff --git a/app/src/main/res/layout/layout_search_hint.xml b/app/src/main/res/layout/layout_search_hint.xml index 4913c825..6c907d36 100644 --- a/app/src/main/res/layout/layout_search_hint.xml +++ b/app/src/main/res/layout/layout_search_hint.xml @@ -10,11 +10,12 @@ android:id="@+id/ic_warning_empty_imageView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="120dp" + android:layout_marginTop="-24dp" + android:contentDescription="Search Hint Icon" android:src="@drawable/ic_search_hint" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - android:contentDescription="Search Hint Icon" app:layout_constraintTop_toTopOf="parent" /> Date: Fri, 10 Sep 2021 14:35:57 -0300 Subject: [PATCH 38/71] Search tv show fix * Some Fix when search --- .../java/com/intive/tmdbandroid/search/ui/SearchFragment.kt | 3 +-- .../tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 96b351d0..f168ee6a 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -21,7 +21,6 @@ import com.intive.tmdbandroid.search.ui.adapters.TVShowSearchAdapter import com.intive.tmdbandroid.search.viewmodel.SearchViewModel import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest -import timber.log.Timber @AndroidEntryPoint class SearchFragment: Fragment() { @@ -50,12 +49,12 @@ class SearchFragment: Fragment() { binding.searchView.clearFocus() viewModel.search(query) subscribeViewModel() - initViews() return true } return false } }) + initViews() return binding.root } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index fb132008..b227e1b5 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -31,7 +31,11 @@ class TVShowSearchAdapter() : PagingDataAdapter var clickListener: ((TVShow) -> Unit)? = null override fun getItemCount(): Int { - return super.getItemCount() + 1 + var retorno = super.getItemCount() + if(retorno == 1){ + retorno+=1 + } + return retorno } override fun getItemViewType(position: Int): Int { From 0284007eb2d2299ef87eea8f61b24676d79dfbcf Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 10 Sep 2021 14:56:09 -0300 Subject: [PATCH 39/71] Add keyboard appearance when Search Fragment starts --- .../tmdbandroid/search/ui/SearchFragment.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 96b351d0..ffbe0600 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -1,9 +1,11 @@ package com.intive.tmdbandroid.search.ui +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager import android.widget.SearchView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -56,20 +58,22 @@ class SearchFragment: Fragment() { return false } }) + binding.searchView.requestFocus() return binding.root } - override fun onResume() { - super.onResume() - if(binding.searchView.query.isNotEmpty()){ - binding.layoutSearchHint.hintContainer.visibility = View.GONE - } - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setupToolbar() + } + override fun onStart() { + super.onStart() + val imm = binding.searchView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + println(binding.searchView.isFocused) + if(imm.isActive(binding.searchView) && binding.searchView.query.isEmpty()){ + imm.toggleSoftInput(0,0) + } } private fun setupToolbar() { @@ -77,7 +81,6 @@ class SearchFragment: Fragment() { val appBarConfiguration = AppBarConfiguration(navController.graph) val toolbar = binding.fragmentSearchToolbar toolbar.setupWithNavController(navController, appBarConfiguration) - binding.searchView.requestFocus() } fun subscribeViewModel(){ From b68640a990e871746f23ffc145fe6dbf6cb8e416 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 10 Sep 2021 15:22:58 -0300 Subject: [PATCH 40/71] Merge con Fix de paginacion --- .../java/com/intive/tmdbandroid/search/ui/SearchFragment.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 5cea81c8..a7f5de49 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -69,10 +69,9 @@ class SearchFragment: Fragment() { override fun onStart() { super.onStart() val imm = binding.searchView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - println(binding.searchView.isFocused) if(imm.isActive(binding.searchView) && binding.searchView.query.isEmpty()){ imm.toggleSoftInput(0,0) - } + } else binding.layoutSearchHint.hintContainer.visibility = View.GONE } private fun setupToolbar() { From 5a0fd23bd67e8f7bfd87400db3507938268bc374 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Mon, 13 Sep 2021 11:31:13 -0300 Subject: [PATCH 41/71] Remove focus from searchView when query is not empty --- .../java/com/intive/tmdbandroid/search/ui/SearchFragment.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index a7f5de49..e81c987b 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -7,6 +7,8 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.SearchView +import androidx.core.view.isEmpty +import androidx.core.view.isNotEmpty import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope @@ -57,7 +59,8 @@ class SearchFragment: Fragment() { } }) initViews() - binding.searchView.requestFocus() + if(binding.searchView.query.isEmpty()) binding.searchView.requestFocus() + else binding.searchView.clearFocus() return binding.root } From c49da8cc2ec2b4efcac74d8227242d5532a5b48e Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Tue, 14 Sep 2021 15:22:18 -0300 Subject: [PATCH 42/71] Add two Use Cases and a repository for Room --- .../datasource/local/LocalStorage.kt | 21 +++++++- .../tmdbandroid/details/ui/DetailFragment.kt | 52 +++++++++++++------ .../details/viewmodel/DetailsViewModel.kt | 35 +++++++++---- .../tmdbandroid/di/LocalStorageModule.kt | 27 ++++++++++ .../tmdbandroid/entity/TVShowORMEntity.kt | 4 +- .../com/intive/tmdbandroid/model/TVShow.kt | 28 +++++++--- .../model/converter/CreatedByConverter.kt | 4 +- .../model/converter/GenreConverter.kt | 3 +- .../repository/WatchlistRepository.kt | 29 +++++++++++ .../usecase/AddToWatchlistUseCase.kt | 20 +++++++ .../usecase/DeleteFromWatchlistUseCase.kt | 19 +++++++ 11 files changed, 203 insertions(+), 39 deletions(-) create mode 100644 app/src/main/java/com/intive/tmdbandroid/di/LocalStorageModule.kt create mode 100644 app/src/main/java/com/intive/tmdbandroid/repository/WatchlistRepository.kt create mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/AddToWatchlistUseCase.kt create mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt index 83069c7d..ffa48c81 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt @@ -1,6 +1,8 @@ package com.intive.tmdbandroid.datasource.local +import android.content.Context import androidx.room.Database +import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters import com.intive.tmdbandroid.entity.TVShowORMEntity @@ -8,7 +10,24 @@ import com.intive.tmdbandroid.model.converter.CreatedByConverter import com.intive.tmdbandroid.model.converter.GenreConverter @Database(entities = [(TVShowORMEntity::class)], version = 1, exportSchema = false) -@TypeConverters(CreatedByConverter::class,GenreConverter::class) +@TypeConverters(CreatedByConverter::class, GenreConverter::class) abstract class LocalStorage : RoomDatabase() { abstract fun tvShowDao(): Dao + + companion object { + private var INSTANCE: LocalStorage? = null + + fun getDB(context: Context): LocalStorage { + if (INSTANCE == null) { + synchronized(LocalStorage::class) { + INSTANCE = Room.databaseBuilder( + context.applicationContext, + LocalStorage::class.java, "watchlist.db" + ) + .build() + } + } + return INSTANCE!! + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt index 36276f47..336d7d35 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt @@ -32,6 +32,7 @@ class DetailFragment : Fragment() { private var isSaveOnWatchlist: Boolean = false private var tvShowId: Int? = null + private var tvShow: TVShow? = null private val viewModel: DetailsViewModel by viewModels() private val args: DetailFragmentArgs by navArgs() @@ -51,6 +52,7 @@ class DetailFragment : Fragment() { binding = FragmentDetailBinding.inflate(inflater, container, false) collectTVShowDetailFromViewModel() collectWatchlistDataFromViewModel() + collectDeletedFromWatchlistFromViewModel() return binding.root } @@ -70,6 +72,7 @@ class DetailFragment : Fragment() { is State.Success -> { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.GONE + tvShow = state.data setupTVShowDetailUI(state.data) } is State.Error -> { @@ -89,37 +92,41 @@ class DetailFragment : Fragment() { private fun collectWatchlistDataFromViewModel() { lifecycleScope.launchWhenStarted { viewModel.addToWatchlistUIState.collectLatest { - when(it) { + when (it) { is State.Success -> { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.GONE - //TODO Add to Room + selectWatchlistFav() } - is State.Error -> { + State.Error -> { binding.layoutLoadingDetail.progressBar.visibility = View.GONE binding.layoutErrorDetail.errorContainer.visibility = View.VISIBLE binding.coordinatorContainerDetail.visibility = View.VISIBLE } - is State.Loading -> { + State.Loading -> { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.VISIBLE } } } + } + } + private fun collectDeletedFromWatchlistFromViewModel() { + lifecycleScope.launchWhenStarted { viewModel.removeFromWatchlistUIState.collectLatest { - when(it) { + when (it) { is State.Success -> { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.GONE - //TODO Remove from Room + unselectWatchlistFav() } - is State.Error -> { + State.Error -> { binding.layoutLoadingDetail.progressBar.visibility = View.GONE binding.layoutErrorDetail.errorContainer.visibility = View.VISIBLE binding.coordinatorContainerDetail.visibility = View.VISIBLE } - is State.Loading -> { + State.Loading -> { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.VISIBLE } @@ -227,9 +234,19 @@ class DetailFragment : Fragment() { val toolbar = binding.toolbar toolbar.inflateMenu(R.menu.watchlist_favorite_detail_fragment) toolbar.setOnMenuItemClickListener { - when(it.itemId) { + when (it.itemId) { R.id.ic_heart_watchlist -> { - selectOrUnselectWatchlistFav() + if (!isSaveOnWatchlist) { + isSaveOnWatchlist = !isSaveOnWatchlist + tvShow?.toTVShowORMEntity()?.let { tvShowORMEntity -> + viewModel.addToWatchlist(tvShowId.toString(), + tvShowORMEntity + ) + } + } else { + isSaveOnWatchlist = !isSaveOnWatchlist + viewModel.deleteFromWatchlist(tvShowId.toString()) + } true } else -> false @@ -247,13 +264,14 @@ class DetailFragment : Fragment() { }) } - private fun selectOrUnselectWatchlistFav() { - isSaveOnWatchlist = !isSaveOnWatchlist + private fun selectWatchlistFav() { val watchlistItem = binding.toolbar.menu.findItem(R.id.ic_heart_watchlist) - if (isSaveOnWatchlist){ - watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_selected) - }else { - watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_unselected) - } + watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_selected) + } + + private fun unselectWatchlistFav() { + val watchlistItem = binding.toolbar.menu.findItem(R.id.ic_heart_watchlist) + watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_unselected) } + } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt index 0d737e36..0b6c313b 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt @@ -3,7 +3,10 @@ package com.intive.tmdbandroid.details.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.intive.tmdbandroid.common.State +import com.intive.tmdbandroid.entity.TVShowORMEntity import com.intive.tmdbandroid.model.TVShow +import com.intive.tmdbandroid.usecase.AddToWatchlistUseCase +import com.intive.tmdbandroid.usecase.DeleteFromWatchlistUseCase import com.intive.tmdbandroid.usecase.DetailTVShowUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -16,16 +19,18 @@ import javax.inject.Inject @HiltViewModel class DetailsViewModel @Inject internal constructor( private val tVShowUseCase: DetailTVShowUseCase, + private val addToWatchlistUseCase: AddToWatchlistUseCase, + private val deleteFromWatchlistUseCase: DeleteFromWatchlistUseCase ) : ViewModel() { private val _state = MutableStateFlow>(State.Loading) val uiState: StateFlow> = _state - private val _addToWatchlistState = MutableStateFlow>(State.Loading) - val addToWatchlistUIState: StateFlow> = _addToWatchlistState + private val _addToWatchlistState = MutableStateFlow>(State.Loading) + val addToWatchlistUIState: StateFlow> = _addToWatchlistState - private val _removeFromWatchlistState = MutableStateFlow>(State.Loading) - val removeFromWatchlistUIState: StateFlow> = _removeFromWatchlistState + private val _removeFromWatchlistState = MutableStateFlow>(State.Loading) + val removeFromWatchlistUIState: StateFlow> = _removeFromWatchlistState fun tVShows(id: Int) { viewModelScope.launch { @@ -39,17 +44,27 @@ class DetailsViewModel @Inject internal constructor( } } - fun addToWatchlist() { - //TODO implement to Use Case for Room + fun addToWatchlist(id: String, tvShow: TVShowORMEntity) { viewModelScope.launch { - _addToWatchlistState.value = State.Success("Add to watchlist") + addToWatchlistUseCase.addToWatchlist(id, tvShow) + .catch { + _addToWatchlistState.value = State.Error + } + .collect { + _addToWatchlistState.value = State.Success(it) + } } } - fun removeFromWatchlist() { - //TODO implement to Use Case for Room + fun deleteFromWatchlist(id: String) { viewModelScope.launch { - _removeFromWatchlistState.value = State.Success("Remove from watchlist") + deleteFromWatchlistUseCase.deleteFavorite(id) + .catch { + _removeFromWatchlistState.value = State.Error + } + .collect { + _removeFromWatchlistState.value = State.Success(it) + } } } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/di/LocalStorageModule.kt b/app/src/main/java/com/intive/tmdbandroid/di/LocalStorageModule.kt new file mode 100644 index 00000000..2cab044f --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/di/LocalStorageModule.kt @@ -0,0 +1,27 @@ +package com.intive.tmdbandroid.di + +import android.app.Application +import com.intive.tmdbandroid.datasource.local.Dao +import com.intive.tmdbandroid.datasource.local.LocalStorage +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class LocalStorageModule { + + @Singleton + @Provides + fun getDB(context: Application): LocalStorage { + return LocalStorage.getDB(context) + } + + @Singleton + @Provides + fun getDao(localStorage: LocalStorage): Dao { + return localStorage.tvShowDao() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/entity/TVShowORMEntity.kt b/app/src/main/java/com/intive/tmdbandroid/entity/TVShowORMEntity.kt index 132bd39d..b6160fdd 100644 --- a/app/src/main/java/com/intive/tmdbandroid/entity/TVShowORMEntity.kt +++ b/app/src/main/java/com/intive/tmdbandroid/entity/TVShowORMEntity.kt @@ -7,6 +7,7 @@ import com.intive.tmdbandroid.model.CreatedBy import com.intive.tmdbandroid.model.Genre import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.model.converter.CreatedByConverter +import com.intive.tmdbandroid.model.converter.GenreConverter @Entity data class TVShowORMEntity( @@ -14,6 +15,7 @@ data class TVShowORMEntity( @TypeConverters(CreatedByConverter::class) val created_by: List, val first_air_date: String?, + @TypeConverters(GenreConverter::class) val genres: List, @PrimaryKey(autoGenerate = false) val id: Int, @@ -29,7 +31,7 @@ data class TVShowORMEntity( val vote_count: Int ) { fun toTVShow() : TVShow { - return TVShow(backdrop_path, emptyList(), first_air_date, emptyList(), id, last_air_date, name, + return TVShow(backdrop_path, created_by, first_air_date, genres, id, last_air_date, name, number_of_episodes, number_of_seasons, original_name, overview, poster_path, status, vote_average, vote_count) } diff --git a/app/src/main/java/com/intive/tmdbandroid/model/TVShow.kt b/app/src/main/java/com/intive/tmdbandroid/model/TVShow.kt index 3b85ed8e..71aa58c0 100644 --- a/app/src/main/java/com/intive/tmdbandroid/model/TVShow.kt +++ b/app/src/main/java/com/intive/tmdbandroid/model/TVShow.kt @@ -1,10 +1,6 @@ package com.intive.tmdbandroid.model -import androidx.room.Entity -import androidx.room.PrimaryKey -import androidx.room.TypeConverters -import com.intive.tmdbandroid.model.converter.CreatedByConverter - +import com.intive.tmdbandroid.entity.TVShowORMEntity data class TVShow( @@ -23,4 +19,24 @@ data class TVShow( val status: String?, val vote_average: Double, val vote_count: Int -) \ No newline at end of file +) { + fun toTVShowORMEntity(): TVShowORMEntity { + return TVShowORMEntity( + backdrop_path, + created_by, + first_air_date, + genres, + id, + last_air_date, + name, + number_of_episodes, + number_of_seasons, + original_name, + overview, + poster_path, + status, + vote_average, + vote_count + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/model/converter/CreatedByConverter.kt b/app/src/main/java/com/intive/tmdbandroid/model/converter/CreatedByConverter.kt index 3caeaafd..fdb9182b 100644 --- a/app/src/main/java/com/intive/tmdbandroid/model/converter/CreatedByConverter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/model/converter/CreatedByConverter.kt @@ -1,9 +1,9 @@ package com.intive.tmdbandroid.model.converter import androidx.room.TypeConverter -import com.intive.tmdbandroid.model.CreatedBy import com.google.gson.Gson import com.google.gson.reflect.TypeToken +import com.intive.tmdbandroid.model.CreatedBy import java.lang.reflect.Type @@ -24,7 +24,7 @@ class CreatedByConverter { return null } val gson = Gson() - val type: Type = object : TypeToken?>() {}.getType() + val type: Type = object : TypeToken?>() {}.type return gson.toJson(createdBy, type) } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/model/converter/GenreConverter.kt b/app/src/main/java/com/intive/tmdbandroid/model/converter/GenreConverter.kt index af320d49..b14d1668 100644 --- a/app/src/main/java/com/intive/tmdbandroid/model/converter/GenreConverter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/model/converter/GenreConverter.kt @@ -1,7 +1,6 @@ package com.intive.tmdbandroid.model.converter import androidx.room.TypeConverter -import com.intive.tmdbandroid.model.CreatedBy import com.google.gson.Gson import com.google.gson.reflect.TypeToken import com.intive.tmdbandroid.model.Genre @@ -25,7 +24,7 @@ class GenreConverter { return null } val gson = Gson() - val type: Type = object : TypeToken?>() {}.getType() + val type: Type = object : TypeToken?>() {}.type return gson.toJson(genre, type) } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/repository/WatchlistRepository.kt b/app/src/main/java/com/intive/tmdbandroid/repository/WatchlistRepository.kt new file mode 100644 index 00000000..cabd0712 --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/repository/WatchlistRepository.kt @@ -0,0 +1,29 @@ +package com.intive.tmdbandroid.repository + +import com.intive.tmdbandroid.datasource.local.Dao +import com.intive.tmdbandroid.entity.TVShowORMEntity +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class WatchlistRepository @Inject constructor( + private val dao: Dao +) { + + suspend fun allFavorites(): List { + return dao.allFavorite() + } + + suspend fun insertFavorite(tvShow: TVShowORMEntity) { + dao.insertFavorite(tvShow) + } + + suspend fun deleteFavorite(tvShow: TVShowORMEntity) { + dao.deleteFavorite(tvShow) + } + + suspend fun existAsFavorite(id: String): List { + return dao.existAsFavorite(id) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/AddToWatchlistUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/AddToWatchlistUseCase.kt new file mode 100644 index 00000000..86027537 --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/AddToWatchlistUseCase.kt @@ -0,0 +1,20 @@ +package com.intive.tmdbandroid.usecase + +import com.intive.tmdbandroid.entity.TVShowORMEntity +import com.intive.tmdbandroid.repository.WatchlistRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +class AddToWatchlistUseCase @Inject constructor( + private val repository: WatchlistRepository +) { + suspend fun addToWatchlist(id: String, tvShowORMEntity: TVShowORMEntity): Flow { + val isExist: List = repository.existAsFavorite(id) + return if (isExist.isEmpty()) { + repository.insertFavorite(tvShowORMEntity) + flowOf(true) + } else + flowOf(false) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt new file mode 100644 index 00000000..f5afe85c --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt @@ -0,0 +1,19 @@ +package com.intive.tmdbandroid.usecase + +import com.intive.tmdbandroid.entity.TVShowORMEntity +import com.intive.tmdbandroid.repository.WatchlistRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +class DeleteFromWatchlistUseCase @Inject constructor( + private val repository: WatchlistRepository +) { + suspend fun deleteFavorite(id: String): Flow { + val isExist: List = repository.existAsFavorite(id) + return if (isExist.isNotEmpty()) { + repository.deleteFavorite(isExist.first()) + flowOf(true) + } else flowOf(false) + } +} \ No newline at end of file From 6d5ef78f73bc8203189820f60b05d8194b84e30e Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Tue, 14 Sep 2021 15:53:35 -0300 Subject: [PATCH 43/71] Fix collect values from add and delete to watchlist --- .../tmdbandroid/details/ui/DetailFragment.kt | 44 +++++-------------- .../details/viewmodel/DetailsViewModel.kt | 15 +++---- .../usecase/DeleteFromWatchlistUseCase.kt | 4 +- 3 files changed, 18 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt index 336d7d35..484ec186 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt @@ -24,6 +24,7 @@ import com.intive.tmdbandroid.model.TVShow import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch import java.text.SimpleDateFormat import java.util.* @@ -52,7 +53,6 @@ class DetailFragment : Fragment() { binding = FragmentDetailBinding.inflate(inflater, container, false) collectTVShowDetailFromViewModel() collectWatchlistDataFromViewModel() - collectDeletedFromWatchlistFromViewModel() return binding.root } @@ -90,36 +90,13 @@ class DetailFragment : Fragment() { } private fun collectWatchlistDataFromViewModel() { - lifecycleScope.launchWhenStarted { - viewModel.addToWatchlistUIState.collectLatest { + lifecycleScope.launch { + viewModel.watchlistUIState.collectLatest { when (it) { is State.Success -> { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.GONE - selectWatchlistFav() - } - State.Error -> { - binding.layoutLoadingDetail.progressBar.visibility = View.GONE - binding.layoutErrorDetail.errorContainer.visibility = View.VISIBLE - binding.coordinatorContainerDetail.visibility = View.VISIBLE - } - State.Loading -> { - binding.layoutErrorDetail.errorContainer.visibility = View.GONE - binding.layoutLoadingDetail.progressBar.visibility = View.VISIBLE - } - } - } - } - } - - private fun collectDeletedFromWatchlistFromViewModel() { - lifecycleScope.launchWhenStarted { - viewModel.removeFromWatchlistUIState.collectLatest { - when (it) { - is State.Success -> { - binding.layoutErrorDetail.errorContainer.visibility = View.GONE - binding.layoutLoadingDetail.progressBar.visibility = View.GONE - unselectWatchlistFav() + selectOrUnselectWatchlistFav(it.data) } State.Error -> { binding.layoutLoadingDetail.progressBar.visibility = View.GONE @@ -239,7 +216,8 @@ class DetailFragment : Fragment() { if (!isSaveOnWatchlist) { isSaveOnWatchlist = !isSaveOnWatchlist tvShow?.toTVShowORMEntity()?.let { tvShowORMEntity -> - viewModel.addToWatchlist(tvShowId.toString(), + viewModel.addToWatchlist( + tvShowId.toString(), tvShowORMEntity ) } @@ -264,14 +242,12 @@ class DetailFragment : Fragment() { }) } - private fun selectWatchlistFav() { + private fun selectOrUnselectWatchlistFav(isFav: Boolean) { val watchlistItem = binding.toolbar.menu.findItem(R.id.ic_heart_watchlist) - watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_selected) - } + if (isFav) { + watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_selected) + } else watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_unselected) - private fun unselectWatchlistFav() { - val watchlistItem = binding.toolbar.menu.findItem(R.id.ic_heart_watchlist) - watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_unselected) } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt index 0b6c313b..01beb35a 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt @@ -26,11 +26,8 @@ class DetailsViewModel @Inject internal constructor( private val _state = MutableStateFlow>(State.Loading) val uiState: StateFlow> = _state - private val _addToWatchlistState = MutableStateFlow>(State.Loading) - val addToWatchlistUIState: StateFlow> = _addToWatchlistState - - private val _removeFromWatchlistState = MutableStateFlow>(State.Loading) - val removeFromWatchlistUIState: StateFlow> = _removeFromWatchlistState + private val _watchlistState = MutableStateFlow>(State.Loading) + val watchlistUIState: StateFlow> = _watchlistState fun tVShows(id: Int) { viewModelScope.launch { @@ -48,10 +45,10 @@ class DetailsViewModel @Inject internal constructor( viewModelScope.launch { addToWatchlistUseCase.addToWatchlist(id, tvShow) .catch { - _addToWatchlistState.value = State.Error + _watchlistState.value = State.Error } .collect { - _addToWatchlistState.value = State.Success(it) + _watchlistState.value = State.Success(it) } } } @@ -60,10 +57,10 @@ class DetailsViewModel @Inject internal constructor( viewModelScope.launch { deleteFromWatchlistUseCase.deleteFavorite(id) .catch { - _removeFromWatchlistState.value = State.Error + _watchlistState.value = State.Error } .collect { - _removeFromWatchlistState.value = State.Success(it) + _watchlistState.value = State.Success(it) } } } diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt index f5afe85c..1a8412a6 100644 --- a/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt @@ -13,7 +13,7 @@ class DeleteFromWatchlistUseCase @Inject constructor( val isExist: List = repository.existAsFavorite(id) return if (isExist.isNotEmpty()) { repository.deleteFavorite(isExist.first()) - flowOf(true) - } else flowOf(false) + flowOf(false) + } else flowOf(true) } } \ No newline at end of file From 7a241de2645cd2b761a6291fd6ae006b219bce70 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Wed, 15 Sep 2021 10:02:33 -0300 Subject: [PATCH 44/71] Change GridLayout to LinearLayout in SearchFragment and resolve some warnings --- app/build.gradle | 9 +++++++++ .../tmdbandroid/search/ui/SearchFragment.kt | 6 ++---- .../search/ui/adapters/TVShowSearchAdapter.kt | 19 +++++++++---------- app/src/main/res/layout/fragment_search.xml | 8 +++++--- .../main/res/layout/layout_search_hint.xml | 5 +++-- app/src/main/res/values/strings.xml | 3 ++- 6 files changed, 30 insertions(+), 20 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 3a3293ae..276a9f84 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,6 +10,13 @@ plugins { android { compileSdk 30 + compileOptions { + // Flag to enable support for the new language APIs + coreLibraryDesugaringEnabled true + // Sets Java compatibility to Java 8 + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } def _major=0 def _minor=0 @@ -68,6 +75,7 @@ android { buildFeatures { viewBinding true } + compileSdkVersion targetSdkVersion } dependencies { @@ -96,6 +104,7 @@ dependencies { implementation "com.github.bumptech.glide:glide:$glideVersion" implementation "androidx.palette:palette-ktx:$paletteVersion" implementation "com.jakewharton.timber:timber:$timberVersion" + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' testImplementation "androidx.arch.core:core-testing:$archTestingVersion" testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion" diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index e81c987b..695215a6 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -7,8 +7,6 @@ import android.view.View import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import android.widget.SearchView -import androidx.core.view.isEmpty -import androidx.core.view.isNotEmpty import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope @@ -17,7 +15,7 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController import androidx.paging.PagingData -import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.databinding.FragmentSearchBinding import com.intive.tmdbandroid.model.TVShow @@ -117,7 +115,7 @@ class SearchFragment: Fragment() { findNavController().navigate(action) } - layoutManager = GridLayoutManager(context, 1, GridLayoutManager.VERTICAL, false) + layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) adapter = searchAdapter } } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index b227e1b5..5bf72e68 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -1,5 +1,6 @@ package com.intive.tmdbandroid.search.ui.adapters +import android.provider.Settings.Global.getString import android.view.LayoutInflater import android.view.ViewGroup import androidx.paging.PagingDataAdapter @@ -13,8 +14,8 @@ import com.intive.tmdbandroid.databinding.SearchResultsHeaderBinding import com.intive.tmdbandroid.model.TVShow import timber.log.Timber import java.lang.Exception -import java.text.SimpleDateFormat -import java.util.* +import java.time.LocalDate +import java.time.format.DateTimeFormatter class TVShowSearchAdapter() : PagingDataAdapter(REPO_COMPARATOR) { companion object { @@ -31,11 +32,7 @@ class TVShowSearchAdapter() : PagingDataAdapter var clickListener: ((TVShow) -> Unit)? = null override fun getItemCount(): Int { - var retorno = super.getItemCount() - if(retorno == 1){ - retorno+=1 - } - return retorno + return super.getItemCount() + 1 } override fun getItemViewType(position: Int): Int { @@ -53,7 +50,7 @@ class TVShowSearchAdapter() : PagingDataAdapter parent, false ) - return SearchResultsHeaderHolder(header) + SearchResultsHeaderHolder(header) } else -> { val header = ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) SearchResultHolder(header) @@ -87,7 +84,9 @@ class TVShowSearchAdapter() : PagingDataAdapter try { if(!item.first_air_date.isNullOrBlank()){ - val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(item.first_air_date) + val formatter: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd") + val dateStr = item.first_air_date + val date: LocalDate = LocalDate.parse(dateStr, formatter) itemYear.text = date.year.toString() } } catch (e : Exception){ @@ -125,7 +124,7 @@ class TVShowSearchAdapter() : PagingDataAdapter val headerText = binding.searchHeader fun bind(){ - headerText.text = "Results for: $query" + headerText.text = headerText.context.resources.getString(R.string.search_result_header, query) } } diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 176fb3b9..a53b109c 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -11,7 +11,6 @@ android:layout_width="match_parent" android:layout_height="?actionBarSize" android:background="@color/primaryColor" - android:gravity="start|center_horizontal|center_vertical" android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.0" @@ -41,7 +40,6 @@ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintHorizontal_bias="0.0" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/fragment_search_toolbar" tools:listitem="@layout/item_found_search" /> @@ -69,6 +67,10 @@ android:id="@+id/layout_search_hint" layout="@layout/layout_search_hint" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/fragment_search_toolbar" /> + android:layout_height="0dp" + android:layout_width="0dp" + app:layout_constraintTop_toBottomOf="@id/fragment_search_toolbar" + /> \ No newline at end of file diff --git a/app/src/main/res/layout/layout_search_hint.xml b/app/src/main/res/layout/layout_search_hint.xml index 6c907d36..47f9149d 100644 --- a/app/src/main/res/layout/layout_search_hint.xml +++ b/app/src/main/res/layout/layout_search_hint.xml @@ -4,14 +4,15 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/hint_container" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/white"> Popular TV shows https://image.tmdb.org/t/p/w780 "%1$d%%" - TV Show details!!!!!!!! Oops! Something went wrong.\nSorry about that :( Nothing to see here Warning @@ -13,6 +12,8 @@ Add to Watchlist Search a TV Show or Movie Start typing to find awesome TV shows + Search Hint Icon + Results for: %1$s %d Season From f482879ee3d4f2e1ec05cc8c6afaf9427d31801b Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Wed, 15 Sep 2021 12:04:18 -0300 Subject: [PATCH 45/71] Fix bug related to date in DetailFragment and remove unused imports --- .../com/intive/tmdbandroid/datasource/TVShowSearchSource.kt | 5 ----- .../java/com/intive/tmdbandroid/details/ui/DetailFragment.kt | 2 +- .../intive/tmdbandroid/search/viewmodel/SearchViewModel.kt | 1 - 3 files changed, 1 insertion(+), 7 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt index 7a9b879b..f5746c17 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/TVShowSearchSource.kt @@ -1,15 +1,11 @@ package com.intive.tmdbandroid.datasource -import android.util.Log import androidx.paging.PagingSource import com.intive.tmdbandroid.entity.ResultTVShowsEntity import androidx.paging.PagingState import com.intive.tmdbandroid.datasource.network.Service import com.intive.tmdbandroid.model.TVShow import kotlinx.coroutines.flow.collect -import retrofit2.HttpException -import timber.log.Timber -import java.io.IOException class TVShowSearchSource(private val service: Service, private val query: String) : PagingSource() { companion object { @@ -19,7 +15,6 @@ class TVShowSearchSource(private val service: Service, private val query: String override suspend fun load(params: LoadParams): LoadResult { return try { val pageNumber = params.key ?: DEFAULT_PAGE_INDEX - Timber.d("pageNumber: $pageNumber") lateinit var response: ResultTVShowsEntity service.getTvShowByTitle(query, pageNumber).collect { response = it } diff --git a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt index 02500359..a485b7ec 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt @@ -88,7 +88,7 @@ class DetailFragment : Fragment() { setImages(tvShow) - setDate(tvShow.first_air_date!!) + tvShow.first_air_date?.let { setDate(it) } setPercentageToCircularPercentage(tvShow.vote_average) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt index 1e4309f2..145b9eaf 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModel.kt @@ -2,7 +2,6 @@ package com.intive.tmdbandroid.search.viewmodel import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import androidx.paging.PagedList import androidx.paging.PagingData import androidx.paging.cachedIn import com.intive.tmdbandroid.common.State From 600002b9770f6a30cd123a4157c92cb0d4ee2f26 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Wed, 15 Sep 2021 15:16:30 -0300 Subject: [PATCH 46/71] Move Repo to GitHub first attem to run GA --- .Github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .Github/workflows/ci.yml diff --git a/.Github/workflows/ci.yml b/.Github/workflows/ci.yml new file mode 100644 index 00000000..df83eb5f --- /dev/null +++ b/.Github/workflows/ci.yml @@ -0,0 +1,26 @@ +name: TMDB App + +on: + push: + branches: [ github_actions ] + pull_request: + branches: [ github_actions ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build \ No newline at end of file From 704b49a9f13c153a7db8e30e37548f9df3c61af3 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Wed, 15 Sep 2021 15:19:01 -0300 Subject: [PATCH 47/71] Move Repo to GitHub rename directory --- {.Github => .github}/workflows/ci.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {.Github => .github}/workflows/ci.yml (100%) diff --git a/.Github/workflows/ci.yml b/.github/workflows/ci.yml similarity index 100% rename from .Github/workflows/ci.yml rename to .github/workflows/ci.yml From 2b70d1698ec077c2088da46102ff652a471d86be Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Tue, 14 Sep 2021 16:07:57 -0300 Subject: [PATCH 48/71] Fix select or unselect fav button of toolbar --- .../datasource/local/LocalStorage.kt | 5 +++- .../tmdbandroid/details/ui/DetailFragment.kt | 26 +++++++++---------- .../details/viewmodel/DetailsViewModel.kt | 16 +++++++++++- .../usecase/ExistAsFavoriteUseCase.kt | 17 ++++++++++++ .../watchlist_favorite_detail_fragment.xml | 5 ++-- 5 files changed, 51 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/ExistAsFavoriteUseCase.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt index ffa48c81..6c33e1e0 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt @@ -17,12 +17,15 @@ abstract class LocalStorage : RoomDatabase() { companion object { private var INSTANCE: LocalStorage? = null + private const val DB_NAME = "watchlist.db" + fun getDB(context: Context): LocalStorage { if (INSTANCE == null) { synchronized(LocalStorage::class) { INSTANCE = Room.databaseBuilder( context.applicationContext, - LocalStorage::class.java, "watchlist.db" + LocalStorage::class.java, + DB_NAME ) .build() } diff --git a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt index 484ec186..979f6dd5 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels @@ -33,7 +34,6 @@ class DetailFragment : Fragment() { private var isSaveOnWatchlist: Boolean = false private var tvShowId: Int? = null - private var tvShow: TVShow? = null private val viewModel: DetailsViewModel by viewModels() private val args: DetailFragmentArgs by navArgs() @@ -58,7 +58,6 @@ class DetailFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - setupToolbar() tvShowId?.let { viewModel.tVShows(it) } @@ -72,7 +71,6 @@ class DetailFragment : Fragment() { is State.Success -> { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.GONE - tvShow = state.data setupTVShowDetailUI(state.data) } is State.Error -> { @@ -97,6 +95,7 @@ class DetailFragment : Fragment() { binding.layoutErrorDetail.errorContainer.visibility = View.GONE binding.layoutLoadingDetail.progressBar.visibility = View.GONE selectOrUnselectWatchlistFav(it.data) + isSaveOnWatchlist = it.data } State.Error -> { binding.layoutLoadingDetail.progressBar.visibility = View.GONE @@ -120,6 +119,8 @@ class DetailFragment : Fragment() { setPercentageToCircularPercentage(tvShow.vote_average) + setupToolbar(tvShow) + binding.toolbar.title = tvShow.name binding.statusDetailTextView.text = tvShow.status @@ -149,6 +150,8 @@ class DetailFragment : Fragment() { binding.overviewDetailTextView.text = tvShow.overview binding.coordinatorContainerDetail.visibility = View.VISIBLE + + viewModel.existAsFavorite(tvShowId.toString()) } private fun setPercentageToCircularPercentage(voteAverage: Double) { @@ -205,7 +208,7 @@ class DetailFragment : Fragment() { .into(binding.backgroundImageToolbarLayout) } - private fun setupToolbar() { + private fun setupToolbar(tvShow: TVShow) { val navController = findNavController() val appBarConfiguration = AppBarConfiguration(navController.graph) val toolbar = binding.toolbar @@ -214,15 +217,8 @@ class DetailFragment : Fragment() { when (it.itemId) { R.id.ic_heart_watchlist -> { if (!isSaveOnWatchlist) { - isSaveOnWatchlist = !isSaveOnWatchlist - tvShow?.toTVShowORMEntity()?.let { tvShowORMEntity -> - viewModel.addToWatchlist( - tvShowId.toString(), - tvShowORMEntity - ) - } + viewModel.addToWatchlist(tvShowId.toString(), tvShow.toTVShowORMEntity()) } else { - isSaveOnWatchlist = !isSaveOnWatchlist viewModel.deleteFromWatchlist(tvShowId.toString()) } true @@ -245,8 +241,10 @@ class DetailFragment : Fragment() { private fun selectOrUnselectWatchlistFav(isFav: Boolean) { val watchlistItem = binding.toolbar.menu.findItem(R.id.ic_heart_watchlist) if (isFav) { - watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_selected) - } else watchlistItem.icon = requireContext().getDrawable(R.drawable.ic_heart_unselected) + watchlistItem.icon = + AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_selected) + } else watchlistItem.icon = + AppCompatResources.getDrawable(requireContext(), R.drawable.ic_heart_unselected) } diff --git a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt index 01beb35a..702b719d 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt @@ -8,6 +8,7 @@ import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.usecase.AddToWatchlistUseCase import com.intive.tmdbandroid.usecase.DeleteFromWatchlistUseCase import com.intive.tmdbandroid.usecase.DetailTVShowUseCase +import com.intive.tmdbandroid.usecase.ExistAsFavoriteUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -20,7 +21,8 @@ import javax.inject.Inject class DetailsViewModel @Inject internal constructor( private val tVShowUseCase: DetailTVShowUseCase, private val addToWatchlistUseCase: AddToWatchlistUseCase, - private val deleteFromWatchlistUseCase: DeleteFromWatchlistUseCase + private val deleteFromWatchlistUseCase: DeleteFromWatchlistUseCase, + private val existAsFavoriteUseCase: ExistAsFavoriteUseCase ) : ViewModel() { private val _state = MutableStateFlow>(State.Loading) @@ -64,4 +66,16 @@ class DetailsViewModel @Inject internal constructor( } } } + + fun existAsFavorite(id: String) { + viewModelScope.launch { + existAsFavoriteUseCase.existAsFavorite(id) + .catch { + _watchlistState.value = State.Error + } + .collect { + _watchlistState.value = State.Success(it) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/ExistAsFavoriteUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/ExistAsFavoriteUseCase.kt new file mode 100644 index 00000000..c74eb822 --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/ExistAsFavoriteUseCase.kt @@ -0,0 +1,17 @@ +package com.intive.tmdbandroid.usecase + +import com.intive.tmdbandroid.entity.TVShowORMEntity +import com.intive.tmdbandroid.repository.WatchlistRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +class ExistAsFavoriteUseCase @Inject constructor( + private val repository: WatchlistRepository +) { + + suspend fun existAsFavorite(id: String): Flow { + val isExist: List = repository.existAsFavorite(id) + return flowOf(isExist.isNotEmpty()) + } +} \ No newline at end of file diff --git a/app/src/main/res/menu/watchlist_favorite_detail_fragment.xml b/app/src/main/res/menu/watchlist_favorite_detail_fragment.xml index cc99741b..675f8bed 100644 --- a/app/src/main/res/menu/watchlist_favorite_detail_fragment.xml +++ b/app/src/main/res/menu/watchlist_favorite_detail_fragment.xml @@ -1,8 +1,9 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools"> - From 525ccbb42c3d41679f4917d9d8c2388aa61a96e9 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 16 Sep 2021 10:56:48 -0300 Subject: [PATCH 49/71] Change test types in PagingData in SearchViewModelTest.kt and Add SearchTVShowUseCaseTest.kt --- .../search/viewmodel/SearchViewModelTest.kt | 70 +++++++++++----- .../usecase/SearchTVShowUseCaseTest.kt | 84 +++++++++++++++++++ 2 files changed, 132 insertions(+), 22 deletions(-) create mode 100644 app/src/test/java/com/intive/tmdbandroid/usecase/SearchTVShowUseCaseTest.kt diff --git a/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt b/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt index f70a1755..e9d5c45e 100644 --- a/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt +++ b/app/src/test/java/com/intive/tmdbandroid/search/viewmodel/SearchViewModelTest.kt @@ -1,6 +1,7 @@ package com.intive.tmdbandroid.search.viewmodel import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.paging.PagingData import app.cash.turbine.test import com.google.common.truth.Truth import com.intive.tmdbandroid.common.MainCoroutineRule @@ -11,9 +12,8 @@ import com.intive.tmdbandroid.usecase.SearchUseCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.runBlockingTest -import org.junit.Before -import org.junit.Rule -import org.junit.Test +import org.junit.* +import org.mockito.BDDMockito import org.mockito.Mockito.* import kotlin.time.ExperimentalTime @@ -26,22 +26,26 @@ class SearchViewModelTest{ @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - private val tvShow = TVShow( - backdrop_path = "BACKDROP_PATH", - first_air_date = "1983-10-20", - genres = listOf(Genre(1, "genre1"), Genre(2,"genre2")), - id = 1, - name = "Simona la Cacarisa", - original_name = "El cochiloco", - overview = "Simona la cacarisa, el cochiloco", - poster_path = "POSTER_PATH", - vote_average = 10.5, - vote_count = 100, - created_by = emptyList(), - last_air_date = "1990-09-25", - number_of_episodes = 5, - number_of_seasons = 2, - status = "Online" + private val testTVShowPagingData = PagingData.from( + listOf( + TVShow( + backdrop_path = "BACKDROP_PATH", + first_air_date = "1983-10-20", + genres = listOf(Genre(1, "genre1"), Genre(2,"genre2")), + id = 1, + name = "Simona la Cacarisa", + original_name = "El cochiloco", + overview = "Simona la cacarisa, el cochiloco", + poster_path = "POSTER_PATH", + vote_average = 10.5, + vote_count = 100, + created_by = emptyList(), + last_air_date = "1990-09-25", + number_of_episodes = 5, + number_of_seasons = 2, + status = "Online" + ) + ) ) private lateinit var searchViewModel: SearchViewModel @@ -56,11 +60,13 @@ class SearchViewModelTest{ @Test @ExperimentalTime - fun tVShowsTest() = mainCoroutineRule.runBlockingTest { + @ExperimentalCoroutinesApi + @Ignore("There's a problem in how the cachedIn ext func from paging data works (it's using a flow to handle the cache which makes the content of the success not to be a paging data but actually a new flow). Ignoring this test for now, until we get a better way to test the paging library.") + fun tvShowsTest() = mainCoroutineRule.runBlockingTest { `when`(searchUseCase.invoke(anyString())).thenReturn( flow { emit( - tvShow + testTVShowPagingData ) } ) @@ -68,8 +74,28 @@ class SearchViewModelTest{ searchViewModel.search("Simona la Cacarisa") searchViewModel.uiState.test { - Truth.assertThat(awaitItem()).isEqualTo(State.Success(tvShow)) + Truth.assertThat(awaitItem()).isEqualTo(State.Success(testTVShowPagingData)) } + + } + + @ExperimentalTime + @ExperimentalCoroutinesApi + @Test + fun searchTvShowError() { + mainCoroutineRule.runBlockingTest { + val runtimeException = RuntimeException() + BDDMockito.given(searchUseCase.invoke(anyString())).willReturn(flow { + throw runtimeException + }) + + searchViewModel.search("alberto fernandez") + + searchViewModel.uiState.test { + Truth.assertThat(awaitItem()).isEqualTo(State.Error) + } + } + } } \ No newline at end of file diff --git a/app/src/test/java/com/intive/tmdbandroid/usecase/SearchTVShowUseCaseTest.kt b/app/src/test/java/com/intive/tmdbandroid/usecase/SearchTVShowUseCaseTest.kt new file mode 100644 index 00000000..40aa02b8 --- /dev/null +++ b/app/src/test/java/com/intive/tmdbandroid/usecase/SearchTVShowUseCaseTest.kt @@ -0,0 +1,84 @@ +package com.intive.tmdbandroid.usecase + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.paging.PagingData +import app.cash.turbine.test +import com.intive.tmdbandroid.common.MainCoroutineRule +import com.intive.tmdbandroid.model.Genre +import com.intive.tmdbandroid.model.TVShow +import com.intive.tmdbandroid.repository.CatalogRepository +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.test.runBlockingTest +import org.junit.Assert +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.Mockito +import org.mockito.Mockito.anyString +import org.mockito.Mockito.mock +import kotlin.time.ExperimentalTime + +@ExperimentalCoroutinesApi +class SearchTVShowUseCaseTest { + + @get:Rule + var mainCoroutineRule = MainCoroutineRule() + + @get:Rule + var instantTaskExecutorRule = InstantTaskExecutorRule() + + private val testTVShowPagingData = PagingData.from( + listOf( + TVShow( + backdrop_path = "BACKDROP_PATH", + first_air_date = "1983-10-20", + genres = listOf(Genre(1, "genre1"), Genre(2,"genre2")), + id = 1, + name = "Simona la Cacarisa", + original_name = "El cochiloco", + overview = "Simona la cacarisa, el cochiloco", + poster_path = "POSTER_PATH", + vote_average = 10.5, + vote_count = 100, + created_by = emptyList(), + last_air_date = "1990-09-25", + number_of_episodes = 5, + number_of_seasons = 2, + status = "Online" + ) + ) + ) + + private lateinit var searchUseCase: SearchUseCase + private lateinit var catalogRepository: CatalogRepository + + @Before + fun setup(){ + catalogRepository = mock(CatalogRepository::class.java) + searchUseCase = SearchUseCase(catalogRepository) + } + + @Test + @ExperimentalTime + fun invokeTest() { + mainCoroutineRule.runBlockingTest { + Mockito.`when`(catalogRepository.search(anyString())) + .thenReturn( + flow { + emit( + testTVShowPagingData + ) + } + ) + + val actual = searchUseCase("cristina kirchner") + actual.test { + Assert.assertEquals(awaitItem(), testTVShowPagingData) + awaitComplete() + } + Mockito.verify(catalogRepository, Mockito.only()).search("cristina kirchner") + + } + } +} \ No newline at end of file From e66c82bdd05a07c97c790b38eeb90a7712f3bdaa Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Thu, 16 Sep 2021 15:53:29 -0300 Subject: [PATCH 50/71] Add real logic to get all favorites tv shows from watchlist --- .../tmdbandroid/home/ui/HomeFragment.kt | 28 +++++++------------ .../home/viewmodel/HomeViewModel.kt | 16 ++++++++--- .../usecase/GetAllFromWatchlistUseCase.kt | 15 ++++++++++ 3 files changed, 37 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/GetAllFromWatchlistUseCase.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt index 15d7f34d..766c2a7e 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt @@ -4,7 +4,6 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import androidx.annotation.NonNull import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope @@ -15,6 +14,7 @@ import androidx.paging.PagingData import androidx.recyclerview.widget.GridLayoutManager import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.databinding.FragmentHomeBinding +import com.intive.tmdbandroid.entity.TVShowORMEntity import com.intive.tmdbandroid.home.ui.adapters.TVShowPageAdapter import com.intive.tmdbandroid.home.viewmodel.HomeViewModel import com.intive.tmdbandroid.model.TVShow @@ -33,6 +33,10 @@ class HomeFragment : Fragment() { super.onCreate(savedInstanceState) viewModel.popularTVShows() + } + + override fun onResume() { + super.onResume() viewModel.watchlistTVShows() } @@ -54,8 +58,7 @@ class HomeFragment : Fragment() { private fun setupToolbar(binding: FragmentHomeBinding) { val navController = findNavController() val appBarConfiguration = AppBarConfiguration(navController.graph) - val toolbar = binding.fragmentHomeToolbar - toolbar.setupWithNavController(navController, appBarConfiguration) + binding.fragmentHomeToolbar.setupWithNavController(navController, appBarConfiguration) } private fun subscribePopularData(binding: FragmentHomeBinding) { @@ -69,8 +72,6 @@ class HomeFragment : Fragment() { binding.layoutError.errorContainer.visibility = View.GONE binding.layoutProgressbar.progressBar.visibility = View.GONE - updateMockWatchlist() - tvShowPageAdapter.submitData(resultTVShows.data) if (tvShowPageAdapter.itemCount == 0) { @@ -94,10 +95,12 @@ class HomeFragment : Fragment() { lifecycleScope.launchWhenStarted { viewModel.watchlistUIState.collectLatest { when(it) { - is State.Success -> { + is State.Success> -> { binding.layoutError.errorContainer.visibility = View.GONE binding.layoutProgressbar.progressBar.visibility = View.GONE - //TODO Mostrar datos en la lista de watchlists. + tvShowPageAdapter.refreshWatchlistAdapter(it.data.map { tvShowORMEntity -> + tvShowORMEntity.toTVShow() + }) } is State.Error -> { binding.layoutProgressbar.progressBar.visibility = View.GONE @@ -112,17 +115,6 @@ class HomeFragment : Fragment() { } } - private fun updateMockWatchlist() { - val list = listOf( - TVShow("", emptyList(),"2021-09-13", emptyList(),0,"","Some TV Show 0", 1, 1, "","","","",2.0,10), - TVShow("", emptyList(),"2021-09-13", emptyList(),1,"","Some TV Show 1", 2, 2, "","","","",4.0,10), - TVShow("", emptyList(),"2021-09-13", emptyList(),2,"","Some TV Show 2", 3, 3, "","","","",6.0,10), - TVShow("", emptyList(),"2021-09-13", emptyList(),3,"","Some TV Show 3", 4, 4, "","","","",8.0,10) - ) - - tvShowPageAdapter.refreshWatchlistAdapter(list) - } - private fun initViews(binding: FragmentHomeBinding) { val rvTopTVShows = binding.rvPopularTVShows diff --git a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt index b0485137..48f713e7 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt @@ -5,7 +5,9 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import com.intive.tmdbandroid.common.State +import com.intive.tmdbandroid.entity.TVShowORMEntity import com.intive.tmdbandroid.model.TVShow +import com.intive.tmdbandroid.usecase.GetAllFromWatchlistUseCase import com.intive.tmdbandroid.usecase.PaginatedPopularTVShowsUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -18,13 +20,14 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject internal constructor( private val paginatedPopularTVShowsUseCase: PaginatedPopularTVShowsUseCase, + private val getAllFromWatchlistUseCase: GetAllFromWatchlistUseCase ) : ViewModel() { private val _state = MutableStateFlow>>(State.Loading) val uiState: StateFlow>> = _state - private val _watchlistState = MutableStateFlow>(State.Loading) - val watchlistUIState: StateFlow> = _watchlistState + private val _watchlistState = MutableStateFlow>>(State.Loading) + val watchlistUIState: StateFlow>> = _watchlistState fun popularTVShows() { viewModelScope.launch { @@ -41,8 +44,13 @@ class HomeViewModel @Inject internal constructor( fun watchlistTVShows() { viewModelScope.launch { - //TODO Use Case Room here - _watchlistState.value = State.Success("Data Room") + getAllFromWatchlistUseCase.allFavorites() + .catch { + _watchlistState.value = State.Error + } + .collect { + _watchlistState.value = State.Success(it) + } } } } diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/GetAllFromWatchlistUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/GetAllFromWatchlistUseCase.kt new file mode 100644 index 00000000..c98263fe --- /dev/null +++ b/app/src/main/java/com/intive/tmdbandroid/usecase/GetAllFromWatchlistUseCase.kt @@ -0,0 +1,15 @@ +package com.intive.tmdbandroid.usecase + +import com.intive.tmdbandroid.entity.TVShowORMEntity +import com.intive.tmdbandroid.repository.WatchlistRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import javax.inject.Inject + +class GetAllFromWatchlistUseCase @Inject constructor( + private val repository: WatchlistRepository +) { + suspend fun allFavorites(): Flow> { + return flowOf(repository.allFavorites()) + } +} \ No newline at end of file From 2b65707ae3e46605ca398f45c9f70ff1b62e528b Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 16 Sep 2021 16:34:42 -0300 Subject: [PATCH 51/71] Remove binding variable --- .../tmdbandroid/search/ui/SearchFragment.kt | 41 +++++++++---------- 1 file changed, 19 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 695215a6..0250ffd7 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -30,15 +30,16 @@ class SearchFragment: Fragment() { private val searchAdapter = TVShowSearchAdapter() - private lateinit var binding: FragmentSearchBinding + private var searchViewQuery: String? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { - binding = FragmentSearchBinding.inflate(inflater, container, false) + val binding = FragmentSearchBinding.inflate(inflater, container, false) binding.layoutProgressbar.progressBar.visibility = View.GONE + setupToolbar(binding) binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextChange(newText: String): Boolean { @@ -50,39 +51,35 @@ class SearchFragment: Fragment() { searchAdapter.query = query binding.searchView.clearFocus() viewModel.search(query) - subscribeViewModel() + subscribeViewModel(binding) + searchViewQuery = query return true } return false } }) - initViews() - if(binding.searchView.query.isEmpty()) binding.searchView.requestFocus() - else binding.searchView.clearFocus() + initViews(binding) + if(searchViewQuery.isNullOrEmpty()){ + binding.searchView.requestFocus() + val imm = binding.searchView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + binding.searchView.postDelayed( { + imm.showSoftInput(binding.searchView, InputMethodManager.SHOW_IMPLICIT) + }, 100) + } + else{ + binding.layoutSearchHint.hintContainer.visibility = View.GONE + } return binding.root } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - setupToolbar() - } - - override fun onStart() { - super.onStart() - val imm = binding.searchView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - if(imm.isActive(binding.searchView) && binding.searchView.query.isEmpty()){ - imm.toggleSoftInput(0,0) - } else binding.layoutSearchHint.hintContainer.visibility = View.GONE - } - - private fun setupToolbar() { + private fun setupToolbar(binding: FragmentSearchBinding) { val navController = findNavController() val appBarConfiguration = AppBarConfiguration(navController.graph) val toolbar = binding.fragmentSearchToolbar toolbar.setupWithNavController(navController, appBarConfiguration) } - fun subscribeViewModel(){ + fun subscribeViewModel(binding: FragmentSearchBinding){ lifecycleScope.launchWhenStarted { viewModel.uiState.collectLatest { resultTVShow -> @@ -106,7 +103,7 @@ class SearchFragment: Fragment() { } } - private fun initViews() { + private fun initViews(binding: FragmentSearchBinding) { val resultsList = binding.searchResults resultsList.apply { From 6e27ce41182d30a636213b75b919d8e89f7bc669 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Thu, 16 Sep 2021 17:10:56 -0300 Subject: [PATCH 52/71] Move Repo to GitHub Github action --- .github/workflows/android.yml | 41 +++++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 26 ---------------------- 2 files changed, 41 insertions(+), 26 deletions(-) create mode 100644 .github/workflows/android.yml delete mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 00000000..545786e2 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,41 @@ +name: TMDB CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + name: Run Unit Tests + runs-on: ubuntu-18.04 + steps: + - uses: actions/checkout@v1 + - name: set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + - name: Unit tests + run: bash ./gradlew test --stacktrace + - name: Unit tests results + uses: actions/upload-artifact@v1 + with: + name: unit-tests-results + path: app/build/reports/tests/testDebugUnitTest/index.html + + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build with Gradle + run: ./gradlew build \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index df83eb5f..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: TMDB App - -on: - push: - branches: [ github_actions ] - pull_request: - branches: [ github_actions ] - -jobs: - build: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: set up JDK 11 - uses: actions/setup-java@v2 - with: - java-version: '11' - distribution: 'adopt' - cache: gradle - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - name: Build with Gradle - run: ./gradlew build \ No newline at end of file From c74b6666d55fadf26c414f794940ce29a0b651e1 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Thu, 16 Sep 2021 20:36:27 -0300 Subject: [PATCH 53/71] Move Repo to GitHub Github action --- .github/workflows/android.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 545786e2..a694f91d 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -2,9 +2,9 @@ name: TMDB CI on: push: - branches: [ master ] + branches: [ master, feature/github_actions ] pull_request: - branches: [ master ] + branches: [ master, feature/github_actions ] jobs: test: From 270215b12896a17e8f1af2fe9909cfa4a547988b Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Fri, 17 Sep 2021 07:06:45 -0300 Subject: [PATCH 54/71] Move Repo to GitHub Set up java 11 for testing --- .github/workflows/android.yml | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index a694f91d..d16500d2 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -9,13 +9,15 @@ on: jobs: test: name: Run Unit Tests - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: set up JDK 1.8 - uses: actions/setup-java@v1 + - uses: actions/checkout@v2 + - name: set up JDK 11 + uses: actions/setup-java@v2 with: - java-version: 1.8 + java-version: '11' + distribution: 'adopt' + cache: gradle - name: Unit tests run: bash ./gradlew test --stacktrace - name: Unit tests results From eaacdeb3126dcca72bcd9db657e4288ed6464c38 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Fri, 17 Sep 2021 10:09:39 -0300 Subject: [PATCH 55/71] Move Repo to GitHub add mail --- .github/workflows/android.yml | 46 ++++++++++++++++++++++++++++++++--- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index d16500d2..af773e4f 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,4 +1,6 @@ name: TMDB CI +author: 'Francisco Beccuti' +description: 'Github Action example for TMDB team' on: push: @@ -9,7 +11,7 @@ on: jobs: test: name: Run Unit Tests - runs-on: ubuntu-latest + runs-on: ubuntu-latest #Each job runs in a runner environment specified steps: - uses: actions/checkout@v2 - name: set up JDK 11 @@ -24,7 +26,26 @@ jobs: uses: actions/upload-artifact@v1 with: name: unit-tests-results - path: app/build/reports/tests/testDebugUnitTest/index.html + path: app/build/reports/tests/testDebugUnitTest/unit-test-results.html + + lint: + name: Lint Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + cache: gradle + - name: Lint debug flavor + run: bash ./gradlew lintDebug --stacktrace + - name: Lint results + uses: actions/upload-artifact@v1 + with: + name: lint-results + path: app/build/reports/lint-results.html build: runs-on: ubuntu-latest @@ -40,4 +61,23 @@ jobs: - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle - run: ./gradlew build \ No newline at end of file + run: ./gradlew build + + - name: Send mail + if: always() + uses: dawidd6/action-send-mail@v2 + with: + # mail server settings + server_address: smtp.gmail.com + server_port: 465 + # user credentials + username: ${{ secrets.EMAIL_USERNAME }} + password: ${{ secrets.EMAIL_PASSWORD }} + # email subject + subject: ${{ github.job }} job of ${{ github.repository }} has ${{ job.status }} + # email body as text + body: ${{ github.job }} job in worflow ${{ github.workflow }} of ${{ github.repository }} has ${{ job.status }} + # comma-separated string, send email to + to: francisco.beccuti@intive.com + # from email name + from: TMDB Team! GitHub Actions! \ No newline at end of file From ccac60e21f1c057e2c0e5bd5f0a5234277ade65d Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Fri, 17 Sep 2021 10:14:38 -0300 Subject: [PATCH 56/71] Move Repo to GitHub add mail --- .github/workflows/android.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index af773e4f..6764cafb 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -1,6 +1,4 @@ name: TMDB CI -author: 'Francisco Beccuti' -description: 'Github Action example for TMDB team' on: push: From 645f84de2e2495457ebfbbebd76d7c3c8e959d0c Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Fri, 17 Sep 2021 10:33:26 -0300 Subject: [PATCH 57/71] Move Repo to GitHub error on Lint and JU test --- .github/workflows/android.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 6764cafb..e9f2db02 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -24,7 +24,7 @@ jobs: uses: actions/upload-artifact@v1 with: name: unit-tests-results - path: app/build/reports/tests/testDebugUnitTest/unit-test-results.html + path: app/build/reports/tests/testDebugUnitTest/index.html lint: name: Lint Check @@ -42,8 +42,8 @@ jobs: - name: Lint results uses: actions/upload-artifact@v1 with: - name: lint-results - path: app/build/reports/lint-results.html + name: app + path: app/build/reports/lint-results-debug.html build: runs-on: ubuntu-latest From 3e9eef1b7fa39a42b5eb8c011d895189c88da1af Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Fri, 17 Sep 2021 10:39:07 -0300 Subject: [PATCH 58/71] Move Repo to GitHub add mails ... generating SPAM --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index e9f2db02..221d4171 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -76,6 +76,6 @@ jobs: # email body as text body: ${{ github.job }} job in worflow ${{ github.workflow }} of ${{ github.repository }} has ${{ job.status }} # comma-separated string, send email to - to: francisco.beccuti@intive.com + to: francisco.beccuti@intive.com,uriel.garrido@intive.com,andres.seoane@intive.com,j.dominguez@intive.com # from email name from: TMDB Team! GitHub Actions! \ No newline at end of file From a6518611eb6450ee278cd271a5f3b412a53bacec Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Fri, 17 Sep 2021 15:51:46 -0300 Subject: [PATCH 59/71] Add the logic in view models to use the Use Cases --- .../datasource/local/LocalStorage.kt | 22 --------------- .../tmdbandroid/details/ui/DetailFragment.kt | 21 +++++++++------ .../details/viewmodel/DetailsViewModel.kt | 24 ++++++++--------- .../tmdbandroid/di/LocalStorageModule.kt | 27 ------------------- .../tmdbandroid/home/ui/HomeFragment.kt | 7 ++--- .../home/viewmodel/HomeViewModel.kt | 11 ++++---- .../usecase/AddToWatchlistUseCase.kt | 20 -------------- .../usecase/DeleteFromWatchlistUseCase.kt | 19 ------------- .../usecase/ExistAsFavoriteUseCase.kt | 17 ------------ .../usecase/GetAllFromWatchlistUseCase.kt | 15 ----------- 10 files changed, 32 insertions(+), 151 deletions(-) delete mode 100644 app/src/main/java/com/intive/tmdbandroid/di/LocalStorageModule.kt delete mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/AddToWatchlistUseCase.kt delete mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt delete mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/ExistAsFavoriteUseCase.kt delete mode 100644 app/src/main/java/com/intive/tmdbandroid/usecase/GetAllFromWatchlistUseCase.kt diff --git a/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt b/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt index 6c33e1e0..da10e0a8 100644 --- a/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt +++ b/app/src/main/java/com/intive/tmdbandroid/datasource/local/LocalStorage.kt @@ -1,8 +1,6 @@ package com.intive.tmdbandroid.datasource.local -import android.content.Context import androidx.room.Database -import androidx.room.Room import androidx.room.RoomDatabase import androidx.room.TypeConverters import com.intive.tmdbandroid.entity.TVShowORMEntity @@ -13,24 +11,4 @@ import com.intive.tmdbandroid.model.converter.GenreConverter @TypeConverters(CreatedByConverter::class, GenreConverter::class) abstract class LocalStorage : RoomDatabase() { abstract fun tvShowDao(): Dao - - companion object { - private var INSTANCE: LocalStorage? = null - - private const val DB_NAME = "watchlist.db" - - fun getDB(context: Context): LocalStorage { - if (INSTANCE == null) { - synchronized(LocalStorage::class) { - INSTANCE = Room.databaseBuilder( - context.applicationContext, - LocalStorage::class.java, - DB_NAME - ) - .build() - } - } - return INSTANCE!! - } - } } \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt index 428a303d..64ccc8a7 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/ui/DetailFragment.kt @@ -155,7 +155,7 @@ class DetailFragment : Fragment() { binding.overviewDetailTextView.text = tvShow.overview binding.coordinatorContainerDetail.visibility = View.VISIBLE - viewModel.existAsFavorite(tvShowId.toString()) + tvShowId?.let { viewModel.existAsFavorite(it) } } private fun setPercentageToCircularPercentage( @@ -169,10 +169,14 @@ class DetailFragment : Fragment() { val context = binding.root.context when { - percentage < 25 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.red) - percentage < 45 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.orange) - percentage < 75 -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.yellow) - else -> binding.circularPercentage.progressTintList = ContextCompat.getColorStateList(context, R.color.green) + percentage < 25 -> binding.circularPercentage.progressTintList = + ContextCompat.getColorStateList(context, R.color.red) + percentage < 45 -> binding.circularPercentage.progressTintList = + ContextCompat.getColorStateList(context, R.color.orange) + percentage < 75 -> binding.circularPercentage.progressTintList = + ContextCompat.getColorStateList(context, R.color.yellow) + else -> binding.circularPercentage.progressTintList = + ContextCompat.getColorStateList(context, R.color.green) } binding.screeningPopularity.text = resources.getString(R.string.popularity, percentage) } @@ -180,7 +184,8 @@ class DetailFragment : Fragment() { private fun setDate(binding: FragmentDetailBinding, firstAirDate: String) { try { val date = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(firstAirDate) - val stringDate = date?.let { SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(it) } + val stringDate = + date?.let { SimpleDateFormat("MMM dd, yyyy", Locale.getDefault()).format(it) } binding.firstAirDateDetailTextView.text = stringDate } catch (e: Exception) { binding.firstAirDateDetailTextView.text = "" @@ -215,9 +220,9 @@ class DetailFragment : Fragment() { when (it.itemId) { R.id.ic_heart_watchlist -> { if (!isSaveOnWatchlist) { - viewModel.addToWatchlist(tvShowId.toString(), tvShow.toTVShowORMEntity()) + viewModel.addToWatchlist(tvShow.toTVShowORMEntity()) } else { - viewModel.deleteFromWatchlist(tvShowId.toString()) + viewModel.deleteFromWatchlist(tvShow.toTVShowORMEntity()) } true } diff --git a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt index 702b719d..8ead7380 100644 --- a/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModel.kt @@ -5,10 +5,10 @@ import androidx.lifecycle.viewModelScope import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.entity.TVShowORMEntity import com.intive.tmdbandroid.model.TVShow -import com.intive.tmdbandroid.usecase.AddToWatchlistUseCase -import com.intive.tmdbandroid.usecase.DeleteFromWatchlistUseCase import com.intive.tmdbandroid.usecase.DetailTVShowUseCase -import com.intive.tmdbandroid.usecase.ExistAsFavoriteUseCase +import com.intive.tmdbandroid.usecase.GetIfExistsUseCase +import com.intive.tmdbandroid.usecase.RemoveTVShowFromWatchlistUseCase +import com.intive.tmdbandroid.usecase.SaveTVShowInWatchlistUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -20,9 +20,9 @@ import javax.inject.Inject @HiltViewModel class DetailsViewModel @Inject internal constructor( private val tVShowUseCase: DetailTVShowUseCase, - private val addToWatchlistUseCase: AddToWatchlistUseCase, - private val deleteFromWatchlistUseCase: DeleteFromWatchlistUseCase, - private val existAsFavoriteUseCase: ExistAsFavoriteUseCase + private val saveTVShowInWatchlistUseCase: SaveTVShowInWatchlistUseCase, + private val removeTVShowFromWatchlistUseCase: RemoveTVShowFromWatchlistUseCase, + private val getIfExistsUseCase: GetIfExistsUseCase ) : ViewModel() { private val _state = MutableStateFlow>(State.Loading) @@ -43,9 +43,9 @@ class DetailsViewModel @Inject internal constructor( } } - fun addToWatchlist(id: String, tvShow: TVShowORMEntity) { + fun addToWatchlist(tvShow: TVShowORMEntity) { viewModelScope.launch { - addToWatchlistUseCase.addToWatchlist(id, tvShow) + saveTVShowInWatchlistUseCase(tvShow) .catch { _watchlistState.value = State.Error } @@ -55,9 +55,9 @@ class DetailsViewModel @Inject internal constructor( } } - fun deleteFromWatchlist(id: String) { + fun deleteFromWatchlist(tvShow: TVShowORMEntity) { viewModelScope.launch { - deleteFromWatchlistUseCase.deleteFavorite(id) + removeTVShowFromWatchlistUseCase(tvShow) .catch { _watchlistState.value = State.Error } @@ -67,9 +67,9 @@ class DetailsViewModel @Inject internal constructor( } } - fun existAsFavorite(id: String) { + fun existAsFavorite(id: Int) { viewModelScope.launch { - existAsFavoriteUseCase.existAsFavorite(id) + getIfExistsUseCase(id) .catch { _watchlistState.value = State.Error } diff --git a/app/src/main/java/com/intive/tmdbandroid/di/LocalStorageModule.kt b/app/src/main/java/com/intive/tmdbandroid/di/LocalStorageModule.kt deleted file mode 100644 index 2cab044f..00000000 --- a/app/src/main/java/com/intive/tmdbandroid/di/LocalStorageModule.kt +++ /dev/null @@ -1,27 +0,0 @@ -package com.intive.tmdbandroid.di - -import android.app.Application -import com.intive.tmdbandroid.datasource.local.Dao -import com.intive.tmdbandroid.datasource.local.LocalStorage -import dagger.Module -import dagger.Provides -import dagger.hilt.InstallIn -import dagger.hilt.components.SingletonComponent -import javax.inject.Singleton - -@Module -@InstallIn(SingletonComponent::class) -class LocalStorageModule { - - @Singleton - @Provides - fun getDB(context: Application): LocalStorage { - return LocalStorage.getDB(context) - } - - @Singleton - @Provides - fun getDao(localStorage: LocalStorage): Dao { - return localStorage.tvShowDao() - } -} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt index 766c2a7e..922fd37c 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt @@ -14,7 +14,6 @@ import androidx.paging.PagingData import androidx.recyclerview.widget.GridLayoutManager import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.databinding.FragmentHomeBinding -import com.intive.tmdbandroid.entity.TVShowORMEntity import com.intive.tmdbandroid.home.ui.adapters.TVShowPageAdapter import com.intive.tmdbandroid.home.viewmodel.HomeViewModel import com.intive.tmdbandroid.model.TVShow @@ -95,12 +94,10 @@ class HomeFragment : Fragment() { lifecycleScope.launchWhenStarted { viewModel.watchlistUIState.collectLatest { when(it) { - is State.Success> -> { + is State.Success> -> { binding.layoutError.errorContainer.visibility = View.GONE binding.layoutProgressbar.progressBar.visibility = View.GONE - tvShowPageAdapter.refreshWatchlistAdapter(it.data.map { tvShowORMEntity -> - tvShowORMEntity.toTVShow() - }) + tvShowPageAdapter.refreshWatchlistAdapter(it.data) } is State.Error -> { binding.layoutProgressbar.progressBar.visibility = View.GONE diff --git a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt index 48f713e7..64af955d 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/viewmodel/HomeViewModel.kt @@ -5,9 +5,8 @@ import androidx.lifecycle.viewModelScope import androidx.paging.PagingData import androidx.paging.cachedIn import com.intive.tmdbandroid.common.State -import com.intive.tmdbandroid.entity.TVShowORMEntity import com.intive.tmdbandroid.model.TVShow -import com.intive.tmdbandroid.usecase.GetAllFromWatchlistUseCase +import com.intive.tmdbandroid.usecase.GetAllItemsInWatchlistUseCase import com.intive.tmdbandroid.usecase.PaginatedPopularTVShowsUseCase import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.flow.MutableStateFlow @@ -20,14 +19,14 @@ import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject internal constructor( private val paginatedPopularTVShowsUseCase: PaginatedPopularTVShowsUseCase, - private val getAllFromWatchlistUseCase: GetAllFromWatchlistUseCase + private val getAllItemsInWatchlistUseCase: GetAllItemsInWatchlistUseCase ) : ViewModel() { private val _state = MutableStateFlow>>(State.Loading) val uiState: StateFlow>> = _state - private val _watchlistState = MutableStateFlow>>(State.Loading) - val watchlistUIState: StateFlow>> = _watchlistState + private val _watchlistState = MutableStateFlow>>(State.Loading) + val watchlistUIState: StateFlow>> = _watchlistState fun popularTVShows() { viewModelScope.launch { @@ -44,7 +43,7 @@ class HomeViewModel @Inject internal constructor( fun watchlistTVShows() { viewModelScope.launch { - getAllFromWatchlistUseCase.allFavorites() + getAllItemsInWatchlistUseCase() .catch { _watchlistState.value = State.Error } diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/AddToWatchlistUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/AddToWatchlistUseCase.kt deleted file mode 100644 index 86027537..00000000 --- a/app/src/main/java/com/intive/tmdbandroid/usecase/AddToWatchlistUseCase.kt +++ /dev/null @@ -1,20 +0,0 @@ -package com.intive.tmdbandroid.usecase - -import com.intive.tmdbandroid.entity.TVShowORMEntity -import com.intive.tmdbandroid.repository.WatchlistRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject - -class AddToWatchlistUseCase @Inject constructor( - private val repository: WatchlistRepository -) { - suspend fun addToWatchlist(id: String, tvShowORMEntity: TVShowORMEntity): Flow { - val isExist: List = repository.existAsFavorite(id) - return if (isExist.isEmpty()) { - repository.insertFavorite(tvShowORMEntity) - flowOf(true) - } else - flowOf(false) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt deleted file mode 100644 index 1a8412a6..00000000 --- a/app/src/main/java/com/intive/tmdbandroid/usecase/DeleteFromWatchlistUseCase.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.intive.tmdbandroid.usecase - -import com.intive.tmdbandroid.entity.TVShowORMEntity -import com.intive.tmdbandroid.repository.WatchlistRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject - -class DeleteFromWatchlistUseCase @Inject constructor( - private val repository: WatchlistRepository -) { - suspend fun deleteFavorite(id: String): Flow { - val isExist: List = repository.existAsFavorite(id) - return if (isExist.isNotEmpty()) { - repository.deleteFavorite(isExist.first()) - flowOf(false) - } else flowOf(true) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/ExistAsFavoriteUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/ExistAsFavoriteUseCase.kt deleted file mode 100644 index c74eb822..00000000 --- a/app/src/main/java/com/intive/tmdbandroid/usecase/ExistAsFavoriteUseCase.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.intive.tmdbandroid.usecase - -import com.intive.tmdbandroid.entity.TVShowORMEntity -import com.intive.tmdbandroid.repository.WatchlistRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject - -class ExistAsFavoriteUseCase @Inject constructor( - private val repository: WatchlistRepository -) { - - suspend fun existAsFavorite(id: String): Flow { - val isExist: List = repository.existAsFavorite(id) - return flowOf(isExist.isNotEmpty()) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/intive/tmdbandroid/usecase/GetAllFromWatchlistUseCase.kt b/app/src/main/java/com/intive/tmdbandroid/usecase/GetAllFromWatchlistUseCase.kt deleted file mode 100644 index c98263fe..00000000 --- a/app/src/main/java/com/intive/tmdbandroid/usecase/GetAllFromWatchlistUseCase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.intive.tmdbandroid.usecase - -import com.intive.tmdbandroid.entity.TVShowORMEntity -import com.intive.tmdbandroid.repository.WatchlistRepository -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flowOf -import javax.inject.Inject - -class GetAllFromWatchlistUseCase @Inject constructor( - private val repository: WatchlistRepository -) { - suspend fun allFavorites(): Flow> { - return flowOf(repository.allFavorites()) - } -} \ No newline at end of file From 423ea0e6052e0aa18ca9951d794dac7f47b795ee Mon Sep 17 00:00:00 2001 From: urielgarrido Date: Fri, 17 Sep 2021 15:59:45 -0300 Subject: [PATCH 60/71] Fix the test of view models --- .../details/viewmodel/DetailsViewModelTest.kt | 28 ++++++++++++++----- .../viewmodel/HomeViewModelTest.kt | 6 +++- 2 files changed, 26 insertions(+), 8 deletions(-) diff --git a/app/src/test/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModelTest.kt b/app/src/test/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModelTest.kt index a7f9fb53..b99cb3e0 100644 --- a/app/src/test/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModelTest.kt +++ b/app/src/test/java/com/intive/tmdbandroid/details/viewmodel/DetailsViewModelTest.kt @@ -8,6 +8,9 @@ import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.model.Genre import com.intive.tmdbandroid.model.TVShow import com.intive.tmdbandroid.usecase.DetailTVShowUseCase +import com.intive.tmdbandroid.usecase.GetIfExistsUseCase +import com.intive.tmdbandroid.usecase.RemoveTVShowFromWatchlistUseCase +import com.intive.tmdbandroid.usecase.SaveTVShowInWatchlistUseCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.runBlockingTest @@ -18,7 +21,7 @@ import org.mockito.Mockito.* import kotlin.time.ExperimentalTime @ExperimentalCoroutinesApi -class DetailsViewModelTest{ +class DetailsViewModelTest { @get:Rule var mainCoroutineRule = MainCoroutineRule() @@ -26,10 +29,10 @@ class DetailsViewModelTest{ @get:Rule var instantTaskExecutorRule = InstantTaskExecutorRule() - private val tvShow = TVShow( + private val tvShow = TVShow( backdrop_path = "BACKDROP_PATH", first_air_date = "1983-10-20", - genres = listOf(Genre(1, "genre1"), Genre(2,"genre2")), + genres = listOf(Genre(1, "genre1"), Genre(2, "genre2")), id = 1, name = "Simona la Cacarisa", original_name = "El cochiloco", @@ -45,19 +48,30 @@ class DetailsViewModelTest{ ) private lateinit var detailViewModel: DetailsViewModel - private lateinit var detailUseCase: DetailTVShowUseCase + private lateinit var tVShowUseCase: DetailTVShowUseCase + private lateinit var saveTVShowInWatchlistUseCase: SaveTVShowInWatchlistUseCase + private lateinit var removeTVShowFromWatchlistUseCase: RemoveTVShowFromWatchlistUseCase + private lateinit var getIfExistsUseCase: GetIfExistsUseCase @Before fun setup() { - detailUseCase = mock(DetailTVShowUseCase::class.java) - detailViewModel = DetailsViewModel(detailUseCase) + tVShowUseCase = mock(DetailTVShowUseCase::class.java) + saveTVShowInWatchlistUseCase = mock(SaveTVShowInWatchlistUseCase::class.java) + removeTVShowFromWatchlistUseCase = mock(RemoveTVShowFromWatchlistUseCase::class.java) + getIfExistsUseCase = mock(GetIfExistsUseCase::class.java) + detailViewModel = DetailsViewModel( + tVShowUseCase, + saveTVShowInWatchlistUseCase, + removeTVShowFromWatchlistUseCase, + getIfExistsUseCase + ) } @Test @ExperimentalTime fun tVShowsTest() = mainCoroutineRule.runBlockingTest { - `when`(detailUseCase.invoke(anyInt())).thenReturn( + `when`(tVShowUseCase.invoke(anyInt())).thenReturn( flow { emit( tvShow diff --git a/app/src/test/java/com/intive/tmdbandroid/viewmodel/HomeViewModelTest.kt b/app/src/test/java/com/intive/tmdbandroid/viewmodel/HomeViewModelTest.kt index ca9d135a..ec6c2438 100644 --- a/app/src/test/java/com/intive/tmdbandroid/viewmodel/HomeViewModelTest.kt +++ b/app/src/test/java/com/intive/tmdbandroid/viewmodel/HomeViewModelTest.kt @@ -9,6 +9,7 @@ import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.home.viewmodel.HomeViewModel import com.intive.tmdbandroid.model.Genre import com.intive.tmdbandroid.model.TVShow +import com.intive.tmdbandroid.usecase.GetAllItemsInWatchlistUseCase import com.intive.tmdbandroid.usecase.PaginatedPopularTVShowsUseCase import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.flow @@ -56,6 +57,9 @@ class HomeViewModelTest { @Mock private lateinit var popularTVShowsUseCase: PaginatedPopularTVShowsUseCase + @Mock + private lateinit var getAllItemsInWatchlistUseCase: GetAllItemsInWatchlistUseCase + // Set the main coroutines dispatcher for unit testing. @ExperimentalCoroutinesApi @get:Rule @@ -67,7 +71,7 @@ class HomeViewModelTest { @Before fun setupViewModel() { - viewModel = HomeViewModel(popularTVShowsUseCase) + viewModel = HomeViewModel(popularTVShowsUseCase, getAllItemsInWatchlistUseCase) } @ExperimentalTime From a24fffc65ab833efa6d5669782e14b938e2a6a8b Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 17 Sep 2021 18:46:55 -0300 Subject: [PATCH 61/71] Remove header from RecyclerView and add a fixed header --- .../tmdbandroid/search/ui/SearchFragment.kt | 9 ++- .../search/ui/adapters/TVShowSearchAdapter.kt | 55 +++---------------- app/src/main/res/layout/fragment_search.xml | 37 ++++++++++++- .../main/res/layout/layout_search_hint.xml | 3 +- .../main/res/layout/search_results_header.xml | 20 ------- 5 files changed, 49 insertions(+), 75 deletions(-) delete mode 100644 app/src/main/res/layout/search_results_header.xml diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 0250ffd7..6576178e 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -16,6 +16,7 @@ import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController import androidx.paging.PagingData import androidx.recyclerview.widget.LinearLayoutManager +import com.intive.tmdbandroid.R import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.databinding.FragmentSearchBinding import com.intive.tmdbandroid.model.TVShow @@ -30,7 +31,7 @@ class SearchFragment: Fragment() { private val searchAdapter = TVShowSearchAdapter() - private var searchViewQuery: String? = null + private var searchViewQuery: String = "" override fun onCreateView( inflater: LayoutInflater, @@ -51,15 +52,15 @@ class SearchFragment: Fragment() { searchAdapter.query = query binding.searchView.clearFocus() viewModel.search(query) - subscribeViewModel(binding) searchViewQuery = query + subscribeViewModel(binding) return true } return false } }) initViews(binding) - if(searchViewQuery.isNullOrEmpty()){ + if(searchViewQuery.isEmpty()){ binding.searchView.requestFocus() val imm = binding.searchView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager binding.searchView.postDelayed( { @@ -87,6 +88,8 @@ class SearchFragment: Fragment() { is State.Success> -> { binding.layoutSearchHint.hintContainer.visibility = View.GONE binding.layoutProgressbar.progressBar.visibility = View.GONE + binding.searchHeaderContainer.visibility = View.VISIBLE + binding.searchHeaderText.text = binding.searchHeaderText.context.getString(R.string.search_result_header, searchViewQuery) searchAdapter.submitData(resultTVShow.data) } is State.Error -> { diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index 5bf72e68..805279b3 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -1,6 +1,5 @@ package com.intive.tmdbandroid.search.ui.adapters -import android.provider.Settings.Global.getString import android.view.LayoutInflater import android.view.ViewGroup import androidx.paging.PagingDataAdapter @@ -10,63 +9,32 @@ import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.intive.tmdbandroid.R import com.intive.tmdbandroid.databinding.ItemFoundSearchBinding -import com.intive.tmdbandroid.databinding.SearchResultsHeaderBinding import com.intive.tmdbandroid.model.TVShow import timber.log.Timber import java.lang.Exception import java.time.LocalDate import java.time.format.DateTimeFormatter -class TVShowSearchAdapter() : PagingDataAdapter(REPO_COMPARATOR) { +class TVShowSearchAdapter() : PagingDataAdapter(REPO_COMPARATOR) { companion object { private val REPO_COMPARATOR = object : DiffUtil.ItemCallback() { override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) override fun areContentsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) } } - private val TYPE_HEADER : Int = 0 - private val TYPE_LIST : Int = 1 - var query: String = "" var clickListener: ((TVShow) -> Unit)? = null - override fun getItemCount(): Int { - return super.getItemCount() + 1 - } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TVShowSearchAdapter.SearchResultHolder { + val resultHolder = ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return SearchResultHolder(resultHolder) - override fun getItemViewType(position: Int): Int { - return when(position){ - 0 -> TYPE_HEADER - else -> TYPE_LIST - } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when(viewType){ - TYPE_HEADER -> { - val header = SearchResultsHeaderBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - SearchResultsHeaderHolder(header) - } else -> { - val header = ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) - SearchResultHolder(header) - } - } - - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when(holder){ - is SearchResultsHeaderHolder -> holder.bind() - is SearchResultHolder -> { - val tvShowItem = getItem(position - 1) as TVShow - holder.bind(tvShowItem) - } - } + override fun onBindViewHolder(holder: TVShowSearchAdapter.SearchResultHolder, position: Int) { + val tvShowItem = getItem(position) as TVShow + holder.bind(tvShowItem) } inner class SearchResultHolder (val binding: ItemFoundSearchBinding) : RecyclerView.ViewHolder(binding.root){ @@ -119,13 +87,4 @@ class TVShowSearchAdapter() : PagingDataAdapter } } - inner class SearchResultsHeaderHolder(binding: SearchResultsHeaderBinding) : RecyclerView.ViewHolder(binding.root) - { - val headerText = binding.searchHeader - - fun bind(){ - headerText.text = headerText.context.resources.getString(R.string.search_result_header, query) - } - } - } diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index a53b109c..3fe163c4 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -32,6 +32,38 @@ + + + + + + + + + app:layout_constraintTop_toBottomOf="@id/fragment_search_toolbar" /> + android:layout_height="match_parent"> - - - - From 0394a6e4130407150da85320d5ee5cfe0ee21b21 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 20 Sep 2021 11:21:37 -0300 Subject: [PATCH 62/71] Move Repo to GitHub add Upload APK --- .github/workflows/android.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 221d4171..96c9a7d7 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -56,10 +56,14 @@ jobs: distribution: 'adopt' cache: gradle - - name: Grant execute permission for gradlew - run: chmod +x gradlew - name: Build with Gradle - run: ./gradlew build + run: bash ./gradlew assembleDebug --stacktrace + + - name: Upload APK + uses: actions/upload-artifact@v1 + with: + name: app + path: app/build/outputs/apk/debug/*.apk - name: Send mail if: always() From bf99f5a0b24fc9f89470732a1c9a3e392af5f7bf Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 20 Sep 2021 11:29:54 -0300 Subject: [PATCH 63/71] Move Repo to GitHub add Upload APK FIX --- .github/workflows/android.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 96c9a7d7..41287ce4 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -60,10 +60,10 @@ jobs: run: bash ./gradlew assembleDebug --stacktrace - name: Upload APK - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2.2.3 with: name: app - path: app/build/outputs/apk/debug/*.apk + path: ./app/build/outputs/apk/debug/app-debug.apk - name: Send mail if: always() From 58447bdc8dd873e50408ae40a4de3732e1157fc5 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 20 Sep 2021 12:02:09 -0300 Subject: [PATCH 64/71] Move Repo to GitHub add Upload APK FIX --- .github/workflows/android.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 41287ce4..77778870 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -11,7 +11,7 @@ jobs: name: Run Unit Tests runs-on: ubuntu-latest #Each job runs in a runner environment specified steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2.3.3 - name: set up JDK 11 uses: actions/setup-java@v2 with: @@ -30,7 +30,7 @@ jobs: name: Lint Check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2.3.3 - name: set up JDK 11 uses: actions/setup-java@v2 with: @@ -48,7 +48,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v2.3.3 - name: set up JDK 11 uses: actions/setup-java@v2 with: @@ -63,7 +63,7 @@ jobs: uses: actions/upload-artifact@v2.2.3 with: name: app - path: ./app/build/outputs/apk/debug/app-debug.apk + path: app/build/outputs/apk/debug/app-debug.apk - name: Send mail if: always() From 07c628672f9cf61451ae5ea35796e4c71ef70132 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 20 Sep 2021 12:10:47 -0300 Subject: [PATCH 65/71] Move Repo to GitHub add Upload APK FIX --- .github/workflows/android.yml | 2 +- app/APKBUILD/readme.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 app/APKBUILD/readme.md diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 77778870..630dae69 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -63,7 +63,7 @@ jobs: uses: actions/upload-artifact@v2.2.3 with: name: app - path: app/build/outputs/apk/debug/app-debug.apk + path: app/APKBUILD/app-debug.apk - name: Send mail if: always() diff --git a/app/APKBUILD/readme.md b/app/APKBUILD/readme.md new file mode 100644 index 00000000..7a022ecc --- /dev/null +++ b/app/APKBUILD/readme.md @@ -0,0 +1 @@ +Test, directory and file \ No newline at end of file From 628a66fb9232435c30c62536f8970a08cefae692 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 20 Sep 2021 12:25:56 -0300 Subject: [PATCH 66/71] Move Repo to GitHub add Upload APK FIX --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 630dae69..31c996c0 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -63,7 +63,7 @@ jobs: uses: actions/upload-artifact@v2.2.3 with: name: app - path: app/APKBUILD/app-debug.apk + path: app\APKBUILD\app-debug.apk - name: Send mail if: always() From d4cd3daa54b706aa322c50d2b66d564d503dc10c Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 20 Sep 2021 14:10:58 -0300 Subject: [PATCH 67/71] Move Repo to GitHub add Upload APK FIX --- .github/workflows/android.yml | 2 +- app/APKBUILD/readme.md | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 app/APKBUILD/readme.md diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 31c996c0..90008b9f 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -63,7 +63,7 @@ jobs: uses: actions/upload-artifact@v2.2.3 with: name: app - path: app\APKBUILD\app-debug.apk + path: app/build/outputs/apk/debug/*.apk - name: Send mail if: always() diff --git a/app/APKBUILD/readme.md b/app/APKBUILD/readme.md deleted file mode 100644 index 7a022ecc..00000000 --- a/app/APKBUILD/readme.md +++ /dev/null @@ -1 +0,0 @@ -Test, directory and file \ No newline at end of file From b48c5777f74a99a187f6eda402e530cf749e0252 Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 20 Sep 2021 14:58:54 -0300 Subject: [PATCH 68/71] Move Repo to GitHub lint name change --- .github/workflows/android.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 90008b9f..f763e288 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -21,7 +21,7 @@ jobs: - name: Unit tests run: bash ./gradlew test --stacktrace - name: Unit tests results - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2.2.3 with: name: unit-tests-results path: app/build/reports/tests/testDebugUnitTest/index.html @@ -40,9 +40,9 @@ jobs: - name: Lint debug flavor run: bash ./gradlew lintDebug --stacktrace - name: Lint results - uses: actions/upload-artifact@v1 + uses: actions/upload-artifact@v2.2.3 with: - name: app + name: lint-result path: app/build/reports/lint-results-debug.html build: From 4c4af88751d7b2fbdd6c1547bfee2d4dc227128c Mon Sep 17 00:00:00 2001 From: Francisco Beccuti Date: Mon, 20 Sep 2021 15:08:10 -0300 Subject: [PATCH 69/71] Move Repo to GitHub delete mails --- .github/workflows/android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index f763e288..656334db 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -80,6 +80,6 @@ jobs: # email body as text body: ${{ github.job }} job in worflow ${{ github.workflow }} of ${{ github.repository }} has ${{ job.status }} # comma-separated string, send email to - to: francisco.beccuti@intive.com,uriel.garrido@intive.com,andres.seoane@intive.com,j.dominguez@intive.com + to: francisco.beccuti@intive.com # from email name from: TMDB Team! GitHub Actions! \ No newline at end of file From b67f6081c2c67581442b5a2d46ded1d8ff4a0677 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Mon, 20 Sep 2021 16:57:30 -0300 Subject: [PATCH 70/71] Remove toolbar padding and make sure the empty layout appears when the list is empty. --- .../com/intive/tmdbandroid/search/ui/SearchFragment.kt | 10 ++++++++++ app/src/main/res/layout/fragment_search.xml | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 6576178e..8d24ac2a 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -15,6 +15,8 @@ import androidx.navigation.fragment.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController import androidx.paging.PagingData +import androidx.paging.insertHeaderItem +import androidx.paging.map import androidx.recyclerview.widget.LinearLayoutManager import com.intive.tmdbandroid.R import com.intive.tmdbandroid.common.State @@ -81,6 +83,14 @@ class SearchFragment: Fragment() { } fun subscribeViewModel(binding: FragmentSearchBinding){ + searchAdapter.addLoadStateListener { loadState -> + if ( loadState.append.endOfPaginationReached ){ + if ( searchAdapter.itemCount < 1) + binding.layoutEmpty.root.visibility = View.VISIBLE + else + binding.layoutEmpty.root.visibility = View.GONE + } + } lifecycleScope.launchWhenStarted { viewModel.uiState.collectLatest { resultTVShow -> diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 3fe163c4..46fabd5d 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -18,11 +18,12 @@ app:layout_constraintRight_toRightOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" + app:contentInsetStartWithNavigation="0dp" app:titleTextColor="@color/white"> Date: Mon, 20 Sep 2021 23:29:53 -0300 Subject: [PATCH 71/71] Add header to RecyclerView in Search Fragment --- .../tmdbandroid/home/ui/HomeFragment.kt | 2 +- .../tmdbandroid/search/ui/SearchFragment.kt | 30 +++--- .../search/ui/adapters/TVShowSearchAdapter.kt | 92 +++++++++++++++---- app/src/main/res/layout/fragment_search.xml | 34 +------ .../main/res/layout/header_results_search.xml | 16 ++++ .../main/res/layout/layout_search_hint.xml | 3 +- 6 files changed, 108 insertions(+), 69 deletions(-) create mode 100644 app/src/main/res/layout/header_results_search.xml diff --git a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt index b721279b..a01b8b41 100644 --- a/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/home/ui/HomeFragment.kt @@ -61,7 +61,7 @@ class HomeFragment : Fragment() { val appBarConfiguration = AppBarConfiguration(navController.graph) binding.fragmentHomeToolbar.setupWithNavController(navController, appBarConfiguration) binding.fragmentHomeToolbar.inflateMenu(R.menu.options_menu) - binding.fragmentHomeToolbar.setOnMenuItemClickListener(){ + binding.fragmentHomeToolbar.setOnMenuItemClickListener{ binding.fragmentHomeToolbar.findNavController().navigate(R.id.action_homeFragmentDest_to_searchFragment) true } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt index 8d24ac2a..d2e77b3a 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt @@ -10,15 +10,11 @@ import android.widget.SearchView import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope -import androidx.navigation.findNavController import androidx.navigation.fragment.findNavController import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.setupWithNavController import androidx.paging.PagingData -import androidx.paging.insertHeaderItem -import androidx.paging.map import androidx.recyclerview.widget.LinearLayoutManager -import com.intive.tmdbandroid.R import com.intive.tmdbandroid.common.State import com.intive.tmdbandroid.databinding.FragmentSearchBinding import com.intive.tmdbandroid.model.TVShow @@ -31,7 +27,12 @@ import kotlinx.coroutines.flow.collectLatest class SearchFragment: Fragment() { private val viewModel: SearchViewModel by viewModels() - private val searchAdapter = TVShowSearchAdapter() + private val clickListener = { tvShow: TVShow -> + val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetail(tvShow.id) + findNavController().navigate(action) + } + + private val searchAdapter = TVShowSearchAdapter(clickListener) private var searchViewQuery: String = "" @@ -67,7 +68,7 @@ class SearchFragment: Fragment() { val imm = binding.searchView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager binding.searchView.postDelayed( { imm.showSoftInput(binding.searchView, InputMethodManager.SHOW_IMPLICIT) - }, 100) + }, 50) } else{ binding.layoutSearchHint.hintContainer.visibility = View.GONE @@ -83,12 +84,12 @@ class SearchFragment: Fragment() { } fun subscribeViewModel(binding: FragmentSearchBinding){ - searchAdapter.addLoadStateListener { loadState -> - if ( loadState.append.endOfPaginationReached ){ - if ( searchAdapter.itemCount < 1) + searchAdapter.notifyItemChanged(0) + searchAdapter.differ.addLoadStateListener { loadState -> + if(loadState.append.endOfPaginationReached){ + if (searchAdapter.itemCount < 1 + 1) { binding.layoutEmpty.root.visibility = View.VISIBLE - else - binding.layoutEmpty.root.visibility = View.GONE + } else binding.layoutEmpty.root.visibility = View.GONE } } lifecycleScope.launchWhenStarted { @@ -98,8 +99,6 @@ class SearchFragment: Fragment() { is State.Success> -> { binding.layoutSearchHint.hintContainer.visibility = View.GONE binding.layoutProgressbar.progressBar.visibility = View.GONE - binding.searchHeaderContainer.visibility = View.VISIBLE - binding.searchHeaderText.text = binding.searchHeaderText.context.getString(R.string.search_result_header, searchViewQuery) searchAdapter.submitData(resultTVShow.data) } is State.Error -> { @@ -120,11 +119,6 @@ class SearchFragment: Fragment() { val resultsList = binding.searchResults resultsList.apply { - searchAdapter.clickListener = { tvShow -> - val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetail(tvShow.id) - findNavController().navigate(action) - } - layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) adapter = searchAdapter } diff --git a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt index 805279b3..b4475f4b 100644 --- a/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt +++ b/app/src/main/java/com/intive/tmdbandroid/search/ui/adapters/TVShowSearchAdapter.kt @@ -2,12 +2,16 @@ package com.intive.tmdbandroid.search.ui.adapters import android.view.LayoutInflater import android.view.ViewGroup -import androidx.paging.PagingDataAdapter +import androidx.paging.AsyncPagingDataDiffer +import androidx.paging.PagingData +import androidx.recyclerview.widget.AdapterListUpdateCallback import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListUpdateCallback import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.bumptech.glide.request.RequestOptions import com.intive.tmdbandroid.R +import com.intive.tmdbandroid.databinding.HeaderResultsSearchBinding import com.intive.tmdbandroid.databinding.ItemFoundSearchBinding import com.intive.tmdbandroid.model.TVShow import timber.log.Timber @@ -15,29 +19,75 @@ import java.lang.Exception import java.time.LocalDate import java.time.format.DateTimeFormatter -class TVShowSearchAdapter() : PagingDataAdapter(REPO_COMPARATOR) { +class TVShowSearchAdapter(private val clickListener: ((TVShow) -> Unit)) : RecyclerView.Adapter() { + var query: String = "" + + val adapterCallback = AdapterListUpdateCallback(this) + companion object { - private val REPO_COMPARATOR = object : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) - override fun areContentsTheSame(oldItem: TVShow, newItem: TVShow): Boolean = (oldItem == newItem) + private const val HEADER = 0 + private const val ITEM = 1 } -} - var query: String = "" - var clickListener: ((TVShow) -> Unit)? = null + val differ = AsyncPagingDataDiffer( + TVShowAsyncPagingDataDiffCallback(), + object : ListUpdateCallback { + override fun onInserted(position: Int, count: Int) { + adapterCallback.onInserted(position + 1, count) + } + + override fun onRemoved(position: Int, count: Int) { + adapterCallback.onRemoved(position + 1, count) + } + + override fun onMoved(fromPosition: Int, toPosition: Int) { + adapterCallback.onMoved(fromPosition + 1, toPosition + 1) + } + + override fun onChanged(position: Int, count: Int, payload: Any?) { + adapterCallback.onChanged(position + 1, count, payload) + } + + } + ) + + suspend fun submitData(tvShowPagingData: PagingData) { + differ.submitData(tvShowPagingData) + } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TVShowSearchAdapter.SearchResultHolder { - val resultHolder = ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false) - return SearchResultHolder(resultHolder) + override fun getItemCount(): Int { + return differ.itemCount + 1 + } + override fun getItemViewType(position: Int): Int { + return when (position) { + 0 -> HEADER + else -> ITEM + } } - override fun onBindViewHolder(holder: TVShowSearchAdapter.SearchResultHolder, position: Int) { - val tvShowItem = getItem(position) as TVShow - holder.bind(tvShowItem) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + HEADER -> HeaderHolder(HeaderResultsSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false)) + ITEM -> SearchResultHolder(ItemFoundSearchBinding.inflate(LayoutInflater.from(parent.context), parent, false), clickListener) + else -> throw Exception("Illegal ViewType") + } } - inner class SearchResultHolder (val binding: ItemFoundSearchBinding) : RecyclerView.ViewHolder(binding.root){ + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is HeaderHolder -> holder.bind(query) + is SearchResultHolder -> differ.getItem(position - 1)?.let { holder.bind(it) } + } + } + + inner class HeaderHolder (private val binding: HeaderResultsSearchBinding): RecyclerView.ViewHolder(binding.root){ + fun bind(query: String){ + binding.searchHeader.text = binding.root.context.getString(R.string.search_result_header, query) + } + } + + inner class SearchResultHolder (private val binding: ItemFoundSearchBinding, private val clickListener: ((TVShow) -> Unit)) : RecyclerView.ViewHolder(binding.root){ private val itemTitle = binding.itemTitleSearch private val itemYear = binding.itemYearSearch @@ -47,7 +97,7 @@ class TVShowSearchAdapter() : PagingDataAdapter() { + override fun areItemsTheSame(oldItem: TVShow, newItem: TVShow): Boolean { + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItem: TVShow, newItem: TVShow): Boolean { + return oldItem == newItem + } + } + } diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 46fabd5d..43a38ca8 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -33,38 +33,6 @@ - - - - - - - diff --git a/app/src/main/res/layout/header_results_search.xml b/app/src/main/res/layout/header_results_search.xml new file mode 100644 index 00000000..cbc60314 --- /dev/null +++ b/app/src/main/res/layout/header_results_search.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_search_hint.xml b/app/src/main/res/layout/layout_search_hint.xml index b2a52b2f..47f9149d 100644 --- a/app/src/main/res/layout/layout_search_hint.xml +++ b/app/src/main/res/layout/layout_search_hint.xml @@ -4,7 +4,8 @@ xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/hint_container" android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_height="match_parent" + android:background="@color/white">