Skip to content

Commit c624ca2

Browse files
authored
Merge pull request #1 from intive-FDV/feature/search_tv_show_fix
Feature/search tv show
2 parents e320da2 + 49ef06f commit c624ca2

File tree

24 files changed

+848
-6
lines changed

24 files changed

+848
-6
lines changed

app/build.gradle

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,18 @@ plugins {
55
id 'dagger.hilt.android.plugin'
66
id 'androidx.navigation.safeargs.kotlin'
77
id 'kotlin-parcelize'
8+
id "org.sonarqube" version "3.3"
89
}
910

1011
android {
1112
compileSdk 30
13+
compileOptions {
14+
// Flag to enable support for the new language APIs
15+
coreLibraryDesugaringEnabled true
16+
// Sets Java compatibility to Java 8
17+
sourceCompatibility JavaVersion.VERSION_1_8
18+
targetCompatibility JavaVersion.VERSION_1_8
19+
}
1220

1321
def _major
1422
def _minor
@@ -96,6 +104,7 @@ dependencies {
96104
implementation "com.github.bumptech.glide:glide:$glideVersion"
97105
implementation "androidx.palette:palette-ktx:$paletteVersion"
98106
implementation "com.jakewharton.timber:timber:$timberVersion"
107+
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
99108
implementation "androidx.room:room-ktx:$roomVersion"
100109

101110
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.intive.tmdbandroid.datasource
2+
3+
import androidx.paging.PagingSource
4+
import com.intive.tmdbandroid.entity.ResultTVShowsEntity
5+
import androidx.paging.PagingState
6+
import com.intive.tmdbandroid.datasource.network.Service
7+
import com.intive.tmdbandroid.model.TVShow
8+
import kotlinx.coroutines.flow.collect
9+
10+
class TVShowSearchSource(private val service: Service, private val query: String) : PagingSource<Int, TVShow>() {
11+
companion object {
12+
const val DEFAULT_PAGE_INDEX = 1
13+
}
14+
15+
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TVShow> {
16+
return try {
17+
val pageNumber = params.key ?: DEFAULT_PAGE_INDEX
18+
lateinit var response: ResultTVShowsEntity
19+
service.getTvShowByTitle(query, pageNumber).collect { response = it }
20+
21+
val prevKey = if (pageNumber > DEFAULT_PAGE_INDEX) pageNumber - 1 else null
22+
val nextKey = if (response.TVShows.isNotEmpty()) pageNumber + 1 else null
23+
24+
LoadResult.Page(
25+
data = response.toTVShowList(),
26+
prevKey = prevKey,
27+
nextKey = nextKey
28+
)
29+
} catch (e: Exception) {
30+
LoadResult.Error(e)
31+
}
32+
}
33+
34+
override fun getRefreshKey(state: PagingState<Int, TVShow>): Int? {
35+
return state.anchorPosition?.let {
36+
state.closestPageToPosition(it)?.prevKey?.plus(1)
37+
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
38+
}
39+
}
40+
}

app/src/main/java/com/intive/tmdbandroid/datasource/network/ApiClient.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,9 @@ interface ApiClient {
1414
@GET("tv/{tv_id}")
1515
suspend fun getTVShowByID(@Path("tv_id") tvShowID: Int,
1616
@Query("api_key") apiKey: String) : TVShow
17+
18+
@GET("search/tv")
19+
suspend fun getTVShowByName(@Query("api_key") apiKey: String,
20+
@Query("query") query: String,
21+
@Query("page") page: Int) : ResultTVShowsEntity
1722
}

app/src/main/java/com/intive/tmdbandroid/datasource/network/Service.kt

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,21 @@ import kotlinx.coroutines.flow.flow
1010
class Service {
1111
private val retrofit = RetrofitHelper.getRetrofit()
1212

13-
fun getPaginatedPopularTVShows(page: Int) : Flow<ResultTVShowsEntity> {
13+
fun getPaginatedPopularTVShows(page: Int): Flow<ResultTVShowsEntity> {
1414
return flow {
1515
emit(retrofit.create(ApiClient::class.java).getPaginatedPopularTVShows(BuildConfig.API_KEY, page))
1616
}
1717
}
1818

19-
fun getTVShowByID(tvShowID: Int) : Flow<TVShow> {
19+
fun getTVShowByID(tvShowID: Int): Flow<TVShow> {
2020
return flow {
2121
emit(retrofit.create(ApiClient::class.java).getTVShowByID(tvShowID, BuildConfig.API_KEY))
2222
}
2323
}
24-
}
24+
25+
fun getTvShowByTitle(tvShowTitle: String, page: Int): Flow<ResultTVShowsEntity> {
26+
return flow {
27+
emit(retrofit.create(ApiClient::class.java).getTVShowByName(BuildConfig.API_KEY, tvShowTitle, page))
28+
}
29+
}
30+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ import android.view.ViewGroup
77
import androidx.fragment.app.Fragment
88
import androidx.fragment.app.viewModels
99
import androidx.lifecycle.lifecycleScope
10+
import androidx.navigation.findNavController
1011
import androidx.navigation.fragment.findNavController
1112
import androidx.navigation.ui.AppBarConfiguration
1213
import androidx.navigation.ui.setupWithNavController
1314
import androidx.paging.PagingData
1415
import androidx.recyclerview.widget.GridLayoutManager
16+
import com.intive.tmdbandroid.R
1517
import com.intive.tmdbandroid.common.State
1618
import com.intive.tmdbandroid.databinding.FragmentHomeBinding
1719
import com.intive.tmdbandroid.home.ui.adapters.TVShowPageAdapter
@@ -58,6 +60,11 @@ class HomeFragment : Fragment() {
5860
val navController = findNavController()
5961
val appBarConfiguration = AppBarConfiguration(navController.graph)
6062
binding.fragmentHomeToolbar.setupWithNavController(navController, appBarConfiguration)
63+
binding.fragmentHomeToolbar.inflateMenu(R.menu.options_menu)
64+
binding.fragmentHomeToolbar.setOnMenuItemClickListener{
65+
binding.fragmentHomeToolbar.findNavController().navigate(R.id.action_homeFragmentDest_to_searchFragment)
66+
true
67+
}
6168
}
6269

6370
private fun subscribePopularData(binding: FragmentHomeBinding) {

app/src/main/java/com/intive/tmdbandroid/repository/CatalogRepository.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import androidx.paging.Pager
44
import androidx.paging.PagingConfig
55
import androidx.paging.PagingData
66
import com.intive.tmdbandroid.datasource.TVShowPagingSource
7+
import com.intive.tmdbandroid.datasource.TVShowSearchSource
78
import com.intive.tmdbandroid.datasource.network.Service
89
import com.intive.tmdbandroid.model.TVShow
910
import kotlinx.coroutines.flow.Flow
11+
import kotlinx.coroutines.flow.map
1012
import javax.inject.Inject
1113
import javax.inject.Singleton
1214

@@ -33,4 +35,16 @@ class CatalogRepository @Inject constructor(
3335
fun getTVShowByID(id:Int): Flow<TVShow>{
3436
return service.getTVShowByID(id)
3537
}
38+
39+
fun search(name:String): Flow<PagingData<TVShow>> {
40+
return Pager(
41+
config = PagingConfig(
42+
pageSize = DEFAULT_PAGE_SIZE,
43+
enablePlaceholders = false
44+
),
45+
pagingSourceFactory = {
46+
TVShowSearchSource(service = service, name)
47+
}
48+
).flow
49+
}
3650
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
package com.intive.tmdbandroid.search.ui
2+
3+
import android.content.Context
4+
import android.os.Bundle
5+
import android.view.LayoutInflater
6+
import android.view.View
7+
import android.view.ViewGroup
8+
import android.view.inputmethod.InputMethodManager
9+
import android.widget.SearchView
10+
import androidx.fragment.app.Fragment
11+
import androidx.fragment.app.viewModels
12+
import androidx.lifecycle.lifecycleScope
13+
import androidx.navigation.fragment.findNavController
14+
import androidx.navigation.ui.AppBarConfiguration
15+
import androidx.navigation.ui.setupWithNavController
16+
import androidx.paging.PagingData
17+
import androidx.recyclerview.widget.LinearLayoutManager
18+
import com.intive.tmdbandroid.common.State
19+
import com.intive.tmdbandroid.databinding.FragmentSearchBinding
20+
import com.intive.tmdbandroid.model.TVShow
21+
import com.intive.tmdbandroid.search.ui.adapters.TVShowSearchAdapter
22+
import com.intive.tmdbandroid.search.viewmodel.SearchViewModel
23+
import dagger.hilt.android.AndroidEntryPoint
24+
import kotlinx.coroutines.flow.collectLatest
25+
26+
@AndroidEntryPoint
27+
class SearchFragment: Fragment() {
28+
private val viewModel: SearchViewModel by viewModels()
29+
30+
private val clickListener = { tvShow: TVShow ->
31+
val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetail(tvShow.id)
32+
findNavController().navigate(action)
33+
}
34+
35+
private val searchAdapter = TVShowSearchAdapter(clickListener)
36+
37+
private var searchViewQuery: String = ""
38+
39+
override fun onCreateView(
40+
inflater: LayoutInflater,
41+
container: ViewGroup?,
42+
savedInstanceState: Bundle?
43+
): View {
44+
val binding = FragmentSearchBinding.inflate(inflater, container, false)
45+
binding.layoutProgressbar.progressBar.visibility = View.GONE
46+
setupToolbar(binding)
47+
binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
48+
49+
override fun onQueryTextChange(newText: String): Boolean {
50+
return false
51+
}
52+
53+
override fun onQueryTextSubmit(query: String): Boolean {
54+
if (query.isNotEmpty()){
55+
searchAdapter.query = query
56+
binding.searchView.clearFocus()
57+
viewModel.search(query)
58+
searchViewQuery = query
59+
subscribeViewModel(binding)
60+
return true
61+
}
62+
return false
63+
}
64+
})
65+
initViews(binding)
66+
if(searchViewQuery.isEmpty()){
67+
binding.searchView.requestFocus()
68+
val imm = binding.searchView.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
69+
binding.searchView.postDelayed( {
70+
imm.showSoftInput(binding.searchView, InputMethodManager.SHOW_IMPLICIT)
71+
}, 50)
72+
}
73+
else{
74+
binding.layoutSearchHint.hintContainer.visibility = View.GONE
75+
}
76+
return binding.root
77+
}
78+
79+
private fun setupToolbar(binding: FragmentSearchBinding) {
80+
val navController = findNavController()
81+
val appBarConfiguration = AppBarConfiguration(navController.graph)
82+
val toolbar = binding.fragmentSearchToolbar
83+
toolbar.setupWithNavController(navController, appBarConfiguration)
84+
}
85+
86+
fun subscribeViewModel(binding: FragmentSearchBinding){
87+
searchAdapter.notifyItemChanged(0)
88+
searchAdapter.differ.addLoadStateListener { loadState ->
89+
if(loadState.append.endOfPaginationReached){
90+
if (searchAdapter.itemCount < 1 + 1) {
91+
binding.layoutEmpty.root.visibility = View.VISIBLE
92+
} else binding.layoutEmpty.root.visibility = View.GONE
93+
}
94+
}
95+
lifecycleScope.launchWhenStarted {
96+
viewModel.uiState.collectLatest { resultTVShow ->
97+
98+
when (resultTVShow) {
99+
is State.Success<PagingData<TVShow>> -> {
100+
binding.layoutSearchHint.hintContainer.visibility = View.GONE
101+
binding.layoutProgressbar.progressBar.visibility = View.GONE
102+
searchAdapter.submitData(resultTVShow.data)
103+
}
104+
is State.Error -> {
105+
binding.layoutError.errorContainer.visibility = View.VISIBLE
106+
binding.layoutSearchHint.hintContainer.visibility = View.GONE
107+
binding.layoutProgressbar.progressBar.visibility = View.GONE
108+
}
109+
is State.Loading -> {
110+
binding.layoutSearchHint.hintContainer.visibility = View.GONE
111+
binding.layoutProgressbar.progressBar.visibility = View.VISIBLE
112+
}
113+
}
114+
}
115+
}
116+
}
117+
118+
private fun initViews(binding: FragmentSearchBinding) {
119+
val resultsList = binding.searchResults
120+
121+
resultsList.apply {
122+
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
123+
adapter = searchAdapter
124+
}
125+
}
126+
}

0 commit comments

Comments
 (0)