Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/search #35

Open
wants to merge 4 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ class NetworkModule {
.readTimeout(60, TimeUnit.SECONDS)
.writeTimeout(60, TimeUnit.SECONDS)
.addInterceptor(interceptor)
.build() }
.build()
}

@Provides
@Reusable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.example.movieapp.model.network
import com.example.movieapp.model.network.data.movie.MovieInfo
import com.example.movieapp.model.network.data.movie.Results
import com.example.movieapp.model.network.data.movie.SmallMovie
import com.example.movieapp.model.network.data.search.GenreResponse
import com.example.movieapp.utils.API_KEY
import io.reactivex.rxjava3.core.Single
import retrofit2.http.GET
Expand Down Expand Up @@ -31,4 +32,10 @@ interface MovieApi {

@GET("movie/{id}?api_key=$API_KEY&language=ru")
fun getMovieByID(@Path("id") id: Int): Single<MovieInfo>

@GET("genre/movie/list")
fun getGenres(
@Query("api_key") key: String = API_KEY,
@Query("language") language: String = "ru"
): Single<GenreResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,80 @@ package com.example.movieapp.model.network

import com.example.movieapp.model.network.data.movie.ListMovie
import com.example.movieapp.model.network.data.movie.MovieInfo
import com.example.movieapp.model.network.data.movie.ParentListMovie
import com.example.movieapp.model.network.data.movie.SmallMovieList
import com.example.movieapp.model.network.data.search.Genre
import com.example.movieapp.model.network.data.search.GenreResponse
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.functions.BiFunction
import io.reactivex.rxjava3.schedulers.Schedulers
import javax.inject.Inject

Comment on lines 9 to 15
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

отрлтлдж

class MovieListSource @Inject constructor(private val api: MovieApi) {
fun fetchMovieList(category: String, key: String, language: String, page: Int): Single<List<ListMovie>> {
fun fetchMovieList(
category: String,
key: String,
language: String,
page: Int
): Single<List<ListMovie>> {
return api.getPropertyAsync(category, key, language, page)
.map { it.networkMovie }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
}

class SmallMovieListSource @Inject constructor(private val api: MovieApi) {
class SmallMovieListSource @Inject constructor(
private val api: MovieApi
) {

private val titleCategoryMap = hashMapOf(
1 to ("Сейчас в кино" to "now_playing"),
2 to ("Топ рейтинг" to "top_rated"),
3 to ("Популярное" to "popular"),
4 to ("Рекомендации" to "upcoming")
)

Comment on lines +31 to +40
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ттт

Suggested change
private val api: MovieApi
) {
private val titleCategoryMap = hashMapOf(
1 to ("Сейчас в кино" to "now_playing"),
2 to ("Топ рейтинг" to "top_rated"),
3 to ("Популярное" to "popular"),
4 to ("Рекомендации" to "upcoming")
)
private val api: MovieApi
) {
private val titleCategoryMap = hashMapOf(
1 to ("Сейчас в кино" to "now_playing"),
2 to ("Топ " to "top_rated"),
3 to ("Популярное" to "popular"),
4 to ("Рекомендации" to "upcoming")
)

fun fetchSmallMovieList(
categories: List<String>,
key: String,
language: String
): Single<List<ParentListMovie>> {
val moviesSource = fetchMovies(categories, language)
val genresSource = fetchGenres(language)

return moviesSource.zipWith(genresSource,
BiFunction { movies: List<List<SmallMovieList>>, genres: List<Genre> ->
movies.mapIndexed { index, list ->
ParentListMovie(
titleCategoryMap[index]?.first ?: "",
titleCategoryMap[index]?.second ?: "",
list.withGenres(genres)
)
}
}).observeOn(AndroidSchedulers.mainThread())
Comment on lines +52 to +57
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

дюд

}

private fun fetchMovies(
categories: List<String>,
language: String
): Single<List<List<SmallMovieList>>> {
return Flowable.fromIterable(categories)
.concatMap { category ->
api.getListOfPosters(category, key, language)
api.getListOfPosters(category = category, language = language)
.toFlowable()
.subscribeOn(Schedulers.io())
}
.map { it.smallMovieList }
.toList()
.observeOn(AndroidSchedulers.mainThread())
}

private fun fetchGenres(language: String): Single<List<Genre>> {
return api.getGenres(language = language)
.map(GenreResponse::genres)
.subscribeOn(Schedulers.io())
}
}

class MovieDetailSource @Inject constructor(private val api: MovieApi) {
Expand All @@ -45,5 +86,12 @@ class MovieDetailSource @Inject constructor(private val api: MovieApi) {
}
}



fun List<SmallMovieList>.withGenres(allGenres: List<Genre>): List<SmallMovieList> {
return map { item ->
item.apply {
genres = genreIds.map { id ->
allGenres.find { genre -> genre.id == id }?.name ?: ""
}.filterNot(String::isNullOrEmpty)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ data class SmallMovieList constructor(
@SerializedName("poster_path") val posterPath: String,
@SerializedName("title") val title: String,
@SerializedName("vote_average")val voteAverage: Float,
@SerializedName("backdrop_path") val backdropPath: String
@SerializedName("backdrop_path") val backdropPath: String,
@SerializedName("genre_ids") var genreIds: List<Int> = emptyList(),
var genres: List<String> = emptyList()
):Parcelable{
constructor(): this(0,"","", 0.0F, "")
constructor(): this(0,"","", 0.0F, "", emptyList(), emptyList())
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import com.example.movieapp.model.network.data.movie.SmallMovieList
import io.reactivex.rxjava3.disposables.CompositeDisposable
import javax.inject.Inject

class OverviewViewModel @Inject constructor(private val networkSource: SmallMovieListSource) :
ViewModel() {
class OverviewViewModel @Inject constructor(
private val networkSource: SmallMovieListSource
) : ViewModel() {

private var disposableBack = CompositeDisposable()
private val categoryList = listOf("upcoming", "top_rated", "popular", "now_playing")
Expand Down Expand Up @@ -50,32 +51,16 @@ class OverviewViewModel @Inject constructor(private val networkSource: SmallMovi
private fun fetchMoviesLists() {
Log.d("ViewModel", "load data")

val titleCategoryMap = hashMapOf(
1 to ("Сейчас в кино" to "now_playing"),
2 to ("Топ рейтинг" to "top_rated"),
3 to ("Популярное" to "popular"),
4 to ("Рекомендации" to "upcoming")
)

val dis = networkSource.fetchSmallMovieList(categoryList, "26f381d6ab8dd659b22d983cab9aa255", "ru")
.subscribe({ collectionList ->
val mListMovie = collectionList.mapIndexed { index, list ->
ParentListMovie(
titleCategoryMap[index]?.first ?: "",
titleCategoryMap[index]?.second ?: "",
list
)
}
networkSource.fetchSmallMovieList(categoryList, "ru")
.subscribe({ parentMoviesList ->
_eventNetworkError.value = false
_isNetworkErrorShown.value = false
_parentListMovie.value = mListMovie
_parentListMovie.value = parentMoviesList
}, {
if (parentListMovie.value.isNullOrEmpty()) {
_eventNetworkError.value = true
}
}
)
disposableBack.add(dis)
}).let(disposableBack::add)
}

fun displayPropertyDetails(movie: SmallMovieList) {
Expand Down
51 changes: 23 additions & 28 deletions app/src/main/java/com/example/movieapp/ui/search/SearchFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,19 @@ import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.movieapp.dagger.App
import com.example.movieapp.dagger.module.viewModule.ViewModelFactory
import com.example.movieapp.databinding.SearchFragmentBinding
import com.example.movieapp.model.network.data.search.SearchItem
import com.example.movieapp.utils.adapters.SearchAdapter
import com.example.movieapp.utils.injectViewModel
import com.jakewharton.rxbinding.widget.RxTextView
import com.example.movieapp.utils.toFlowable
import com.example.movieapp.utils.withParams
import dagger.android.support.DaggerFragment
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.disposables.CompositeDisposable
import kotlinx.android.synthetic.main.search_fragment.*
import rx.Single
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import java.util.concurrent.TimeUnit
import javax.inject.Inject

class SearchFragment : DaggerFragment() {
Expand All @@ -31,13 +27,13 @@ class SearchFragment : DaggerFragment() {
lateinit var viewModel: SearchViewModel
lateinit var binding: SearchFragmentBinding
lateinit var adapter: SearchAdapter
private val disposables = CompositeDisposable()

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {


viewModel = injectViewModel(viewModelFactory)
binding = SearchFragmentBinding.inflate(inflater)
adapter = SearchAdapter(SearchAdapter.ClickListener {
Expand All @@ -57,34 +53,28 @@ class SearchFragment : DaggerFragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
RxTextView.textChanges(binding.edit)
.subscribeOn(AndroidSchedulers.mainThread())
.filter {
it.length > 2
}
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.doOnNext { showProgress() }
.observeOn(Schedulers.io())
.switchMap {
apiRequest(it).toObservable()
}

binding.edit.toFlowable()
.withParams(
minLength = 2,
debounce = 500,
switchMapper = ::apiRequest,
doOnNext = ::showProgress
)
.map { resp ->
resp
.filterNot { it.name == null || it.name == "null" }
resp.filterNot { it.name.isNullOrEmpty() || it.name == "null" }
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
Log.d("response", "result: $it")
hideProgress()
adapter.submitList(it)
}, {
Log.d("response", "error: $it")
})
}
).let(disposables::add)
}

private fun apiRequest(chars: CharSequence): Single<List<SearchItem>> {
return viewModel.getSearchResult(chars.toString())
private fun apiRequest(chars: CharSequence): Flowable<List<SearchItem>> {
return viewModel.getSearchResult(chars.toString()).toFlowable()
}

private fun showProgress() {
Expand All @@ -96,4 +86,9 @@ class SearchFragment : DaggerFragment() {
searchProgressBar.visibility = View.INVISIBLE
search_rv.visibility = View.VISIBLE
}

override fun onDestroy() {
super.onDestroy()
disposables.clear()
}
}
50 changes: 27 additions & 23 deletions app/src/main/java/com/example/movieapp/ui/search/SearchViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,46 @@ package com.example.movieapp.ui.search

import androidx.lifecycle.ViewModel
import com.example.movieapp.model.network.SearchApi
import com.example.movieapp.model.network.data.movie.SmallMovieList
import com.example.movieapp.model.network.data.search.Genre
import com.example.movieapp.model.network.data.search.SearchItem
import com.example.movieapp.model.network.data.search.SearchResponse
import com.google.gson.annotations.SerializedName
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.schedulers.Schedulers
import rx.Single
import javax.inject.Inject

class SearchViewModel @Inject constructor(private val api: SearchApi) : ViewModel() {

// val genres: io.reactivex.rxjava3.core.Single<List<Genre>>
// get() = api.getGenres().map { it.genres }.cache().subscribeOn(Schedulers.io())

fun getSearchResult(query: String): Single<List<SearchItem>> {
return Single.create<List<SearchItem>> { s ->
private lateinit var allGenres: List<Genre>

api.getListOfPosters(query)
.map(SearchResponse::results)
init {
api.getGenres()
.map { it.genres }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
allGenres = it
}, {
allGenres = emptyList()
})
}

// .flattenAsFlowable { it.results }
// .flatMap { r ->
// Flowable.just(r.genreIds)
// .flatMap { gId ->
// genres.toFlowable().zipWith(
// Flowable.just(r),
// BiFunction<List<Genre>, SearchItem, SearchItem> { t1, t2 ->
// t2.apply { genres = t1.filter { u -> gId.contains(u.id) }.map(Genre::name) }
// }
// )
// }
fun getSearchResult(query: String): Single<List<SearchItem>> {
return api.getListOfPosters(query)
.map(SearchResponse::results)
// .map { items ->
// items.map { item ->
// item.apply {
// genres = genreIds.map { id ->
// allGenres.find { genre -> genre.id == id }?.name ?: ""
// }.filterNot(String::isNullOrEmpty)
// }
// }
// .toList()
.subscribe({
s.onSuccess(it)
}, {
s.onError(it)
})
}
// }
}
}
Loading