Skip to content

Commit

Permalink
Merge pull request #1 from intive-FDV/feature/search_tv_show_fix
Browse files Browse the repository at this point in the history
Feature/search tv show
  • Loading branch information
juanigdom authored Sep 21, 2021
2 parents e320da2 + 49ef06f commit c624ca2
Show file tree
Hide file tree
Showing 24 changed files with 848 additions and 6 deletions.
9 changes: 9 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,18 @@ plugins {
id 'dagger.hilt.android.plugin'
id 'androidx.navigation.safeargs.kotlin'
id 'kotlin-parcelize'
id "org.sonarqube" version "3.3"
}

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
def _minor
Expand Down Expand Up @@ -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'
implementation "androidx.room:room-ktx:$roomVersion"

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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

class TVShowSearchSource(private val service: Service, private val query: String) : PagingSource<Int, TVShow>() {
companion object {
const val DEFAULT_PAGE_INDEX = 1
}

override suspend fun load(params: LoadParams<Int>): LoadResult<Int, TVShow> {
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, TVShow>): Int? {
return state.anchorPosition?.let {
state.closestPageToPosition(it)?.prevKey?.plus(1)
?: state.closestPageToPosition(it)?.nextKey?.minus(1)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ interface ApiClient {
@GET("tv/{tv_id}")
suspend fun getTVShowByID(@Path("tv_id") tvShowID: Int,
@Query("api_key") apiKey: String) : TVShow

@GET("search/tv")
suspend fun getTVShowByName(@Query("api_key") apiKey: String,
@Query("query") query: String,
@Query("page") page: Int) : ResultTVShowsEntity
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,21 @@ import kotlinx.coroutines.flow.flow
class Service {
private val retrofit = RetrofitHelper.getRetrofit()

fun getPaginatedPopularTVShows(page: Int) : Flow<ResultTVShowsEntity> {
fun getPaginatedPopularTVShows(page: Int): Flow<ResultTVShowsEntity> {
return flow {
emit(retrofit.create(ApiClient::class.java).getPaginatedPopularTVShows(BuildConfig.API_KEY, page))
}
}

fun getTVShowByID(tvShowID: Int) : Flow<TVShow> {
fun getTVShowByID(tvShowID: Int): Flow<TVShow> {
return flow {
emit(retrofit.create(ApiClient::class.java).getTVShowByID(tvShowID, BuildConfig.API_KEY))
}
}
}

fun getTvShowByTitle(tvShowTitle: String, page: Int): Flow<ResultTVShowsEntity> {
return flow {
emit(retrofit.create(ApiClient::class.java).getTVShowByName(BuildConfig.API_KEY, tvShowTitle, page))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ 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.paging.PagingData
import androidx.recyclerview.widget.GridLayoutManager
import com.intive.tmdbandroid.R
import com.intive.tmdbandroid.common.State
import com.intive.tmdbandroid.databinding.FragmentHomeBinding
import com.intive.tmdbandroid.home.ui.adapters.TVShowPageAdapter
Expand Down Expand Up @@ -58,6 +60,11 @@ class HomeFragment : Fragment() {
val navController = findNavController()
val appBarConfiguration = AppBarConfiguration(navController.graph)
binding.fragmentHomeToolbar.setupWithNavController(navController, appBarConfiguration)
binding.fragmentHomeToolbar.inflateMenu(R.menu.options_menu)
binding.fragmentHomeToolbar.setOnMenuItemClickListener{
binding.fragmentHomeToolbar.findNavController().navigate(R.id.action_homeFragmentDest_to_searchFragment)
true
}
}

private fun subscribePopularData(binding: FragmentHomeBinding) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ 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
import kotlinx.coroutines.flow.map
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -33,4 +35,16 @@ class CatalogRepository @Inject constructor(
fun getTVShowByID(id:Int): Flow<TVShow>{
return service.getTVShowByID(id)
}

fun search(name:String): Flow<PagingData<TVShow>> {
return Pager(
config = PagingConfig(
pageSize = DEFAULT_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory = {
TVShowSearchSource(service = service, name)
}
).flow
}
}
126 changes: 126 additions & 0 deletions app/src/main/java/com/intive/tmdbandroid/search/ui/SearchFragment.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
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
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.LinearLayoutManager
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 clickListener = { tvShow: TVShow ->
val action = SearchFragmentDirections.actionSearchFragmentToTVShowDetail(tvShow.id)
findNavController().navigate(action)
}

private val searchAdapter = TVShowSearchAdapter(clickListener)

private var searchViewQuery: String = ""

override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
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 {
return false
}

override fun onQueryTextSubmit(query: String): Boolean {
if (query.isNotEmpty()){
searchAdapter.query = query
binding.searchView.clearFocus()
viewModel.search(query)
searchViewQuery = query
subscribeViewModel(binding)
return true
}
return false
}
})
initViews(binding)
if(searchViewQuery.isEmpty()){
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)
}, 50)
}
else{
binding.layoutSearchHint.hintContainer.visibility = View.GONE
}
return binding.root
}

private fun setupToolbar(binding: FragmentSearchBinding) {
val navController = findNavController()
val appBarConfiguration = AppBarConfiguration(navController.graph)
val toolbar = binding.fragmentSearchToolbar
toolbar.setupWithNavController(navController, appBarConfiguration)
}

fun subscribeViewModel(binding: FragmentSearchBinding){
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
}
}
lifecycleScope.launchWhenStarted {
viewModel.uiState.collectLatest { resultTVShow ->

when (resultTVShow) {
is State.Success<PagingData<TVShow>> -> {
binding.layoutSearchHint.hintContainer.visibility = View.GONE
binding.layoutProgressbar.progressBar.visibility = View.GONE
searchAdapter.submitData(resultTVShow.data)
}
is State.Error -> {
binding.layoutError.errorContainer.visibility = View.VISIBLE
binding.layoutSearchHint.hintContainer.visibility = View.GONE
binding.layoutProgressbar.progressBar.visibility = View.GONE
}
is State.Loading -> {
binding.layoutSearchHint.hintContainer.visibility = View.GONE
binding.layoutProgressbar.progressBar.visibility = View.VISIBLE
}
}
}
}
}

private fun initViews(binding: FragmentSearchBinding) {
val resultsList = binding.searchResults

resultsList.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
adapter = searchAdapter
}
}
}
Loading

0 comments on commit c624ca2

Please sign in to comment.