From 7c50cd5cdefab7409b3579481b48bf50021d2ee7 Mon Sep 17 00:00:00 2001 From: "andres.seoane" Date: Thu, 2 Sep 2021 11:53:22 -0300 Subject: [PATCH 01/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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/41] 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 56989c25e8ed23612b0df8ce4c91174f954240fe Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Mon, 6 Sep 2021 16:07:31 -0300 Subject: [PATCH 12/41] 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 230db80a93c7e726888fcf2b1f5a9ced15820ae2 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Mon, 6 Sep 2021 16:20:19 -0300 Subject: [PATCH 13/41] 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 8f3f1cd3b459cdbbb87dbd6f474cded02edbbeb3 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Mon, 6 Sep 2021 17:55:59 -0300 Subject: [PATCH 14/41] 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 68bc48f183a9713a8a3dccbbc800c3d981fd77c8 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Tue, 7 Sep 2021 17:30:44 -0300 Subject: [PATCH 15/41] 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 16/41] 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 17/41] 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 18/41] 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 19/41] 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 20/41] 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 21/41] 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 22/41] 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 23/41] 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 24/41] 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 25/41] 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 26/41] 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 27/41] 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 28/41] 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 29/41] 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 30/41] 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 31/41] 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 32/41] 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 33/41] 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 34/41] 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 7a241de2645cd2b761a6291fd6ae006b219bce70 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Wed, 15 Sep 2021 10:02:33 -0300 Subject: [PATCH 35/41] 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 36/41] 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 525ccbb42c3d41679f4917d9d8c2388aa61a96e9 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 16 Sep 2021 10:56:48 -0300 Subject: [PATCH 37/41] 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 2b65707ae3e46605ca398f45c9f70ff1b62e528b Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Thu, 16 Sep 2021 16:34:42 -0300 Subject: [PATCH 38/41] 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 a24fffc65ab833efa6d5669782e14b938e2a6a8b Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Fri, 17 Sep 2021 18:46:55 -0300 Subject: [PATCH 39/41] 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 b67f6081c2c67581442b5a2d46ded1d8ff4a0677 Mon Sep 17 00:00:00 2001 From: Juan Dominguez Date: Mon, 20 Sep 2021 16:57:30 -0300 Subject: [PATCH 40/41] 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 41/41] 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">