Skip to content

Instantly share code, notes, and snippets.

@Belobobr
Created May 19, 2018 21:05
Show Gist options
  • Select an option

  • Save Belobobr/eda0d6afc07bdb32b501058fda78e9af to your computer and use it in GitHub Desktop.

Select an option

Save Belobobr/eda0d6afc07bdb32b501058fda78e9af to your computer and use it in GitHub Desktop.
android sample rx-mvvm
package com.alphaott.webtv.client.android.data
import com.alphaott.webtv.client.android.injection.SingletonHolderSingleArg
import com.alphaott.webtv.client.api.entities.contentgroup.category.Category
import com.alphaott.webtv.client.api.entities.contentgroup.genre.Genre
import com.alphaott.webtv.client.api.entities.contentitem.MediaStream
import com.alphaott.webtv.client.api.entities.contentitem.channel.tv.TvChannel
import com.alphaott.webtv.client.api.entities.dictionary.country.Country
import com.alphaott.webtv.client.api.entities.dictionary.language.Language
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import io.reactivex.rxkotlin.Singles
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.BehaviorSubject
val showedLatestChannels: Long = 10
val showedPopularChannels: Long = 10
//TODO можно напрямую передавать content path или contentPath observable
class TvRepository(
private val configRepository: ConfigRepository
) {
data class ChannelSources(
val streams: Set<MediaStream>?,
val error: Boolean
)
data class Dictionaries(
var categories: List<Category>,
val genres: List<Genre>,
val languages: List<Language>,
val countries: List<Country>
)
// TODO оставил для не отрефаченых методов
private val contentPath: String = configRepository.config.content.url
private val configObservable = configRepository.configObservable()
//channels
private val channelsBehaviour: BehaviorSubject<List<TvChannel>> = BehaviorSubject.create<List<TvChannel>>()
private val channelsBehaviourError: BehaviorSubject<Throwable> = BehaviorSubject.create<Throwable>()
private val channelsSourcesBehaviour: BehaviorSubject<MutableMap<String, ChannelSources>> = BehaviorSubject.createDefault(mutableMapOf())
//dictionaries
private val categoriesBehaviour: BehaviorSubject<List<Category>> = BehaviorSubject.create<List<Category>>()
private val genresBehaviour: BehaviorSubject<List<Genre>> = BehaviorSubject.create<List<Genre>>()
private val languagesBehaviour: BehaviorSubject<List<Language>> = BehaviorSubject.create<List<Language>>()
private val countriesBehaviour: BehaviorSubject<List<Country>> = BehaviorSubject.create<List<Country>>()
private val loadDictionariesErrorBehaviour: BehaviorSubject<Boolean> = BehaviorSubject.create<Boolean>()
//computed
private val computedChannelsObservable: Observable<List<TvChannel>>
private val popularChannelsObservable: Observable<List<TvChannel>>
private val latestChannelsObservable: Observable<List<TvChannel>>
private val computedCategoriesObservable: Observable<List<Category>>
private val computedGenresObservable: Observable<List<Genre>>
private val computedCountriesObservable: Observable<List<Country>>
private val computedLanguageObservable: Observable<List<Language>>
init {
computedChannelsObservable = Observables.combineLatest(
channelsBehaviour,
configObservable
).flatMap { pair ->
Observable.fromIterable(pair.first)
.map {
//TODO это должно собираться где то на уровне view model
it.logos.forEach { it.path = "${pair.second.content.url}${it.path}" }
it.backgrounds.forEach { it.path = "${pair.second.content.url}${it.path}" }
it
}
.toList().toObservable()
}.cache()
computedCategoriesObservable = Observables.combineLatest(
categoriesBehaviour,
configObservable,
{ categories, config ->
val contentPath = config.content.url
categories.apply {
forEach {
it.backgrounds.forEach { it.path = "$contentPath${it.path}" }
it.posters.forEach { it.path = "$contentPath${it.path}" }
}
}
}
).cache()
computedGenresObservable = Observables.combineLatest(
genresBehaviour,
configObservable
).flatMap { (genres, config) ->
Observable.fromIterable(genres)
.map {
val contentPath = config.content.url
it.posters.forEach { it.path = "$contentPath${it.path}" }
it.backgrounds.forEach { it.path = "$contentPath${it.path}" }
it
}
.toList()
.toObservable()
}.cache()
computedLanguageObservable = Observables.combineLatest(
languagesBehaviour,
configObservable
).flatMap { pair ->
Observable.fromIterable(pair.first)
.map {
//TODO это должно собираться где то на уровне view model
it.images.forEach { it.path = "${pair.second.content.url}${it.path}" }
it
}
.toList()
.toObservable()
}.cache()
computedCountriesObservable = Observables.combineLatest(
countriesBehaviour,
configObservable
).flatMap { pair ->
Observable.fromIterable(pair.first)
.map {
//TODO это должно собираться где то на уровне view model
it.images.forEach { it.path = "${pair.second.content.url}${it.path}" }
it
}
.toList()
.toObservable()
}.cache()
popularChannelsObservable = computedChannelsObservable.flatMap { channels ->
Observable.fromIterable(channels)
.sorted { a, b -> (b.rating - a.rating).toInt() }
.take(showedPopularChannels)
.toList()
.toObservable()
}.cache()
latestChannelsObservable = computedChannelsObservable.flatMap { channels ->
Observable.fromIterable(channels)
.sorted { a, b ->
//TODO блин я не могу без tmp сравнивать иначе concurrent, проверка не верная если чего то нет, то то что есть больше
val aTime = a.created?.time
val bTime = b.created?.time
if (aTime != null && bTime != null) (aTime - bTime).toInt() else 0
}
.take(showedLatestChannels)
.toList()
.toObservable()
}.cache()
//TODO нужно добавить логику что пока все не заэмитят данные хотя бы по 1 му разу не обновлять ui
}
//TODO есть очень много комбинаций как тут делать. Можно комбинировать из
//TODO 1) возвращать Completable 2) Возвращать Disposable, 3) Сохранить результат выполнение операции в repository
//TODO один из вариантов - сохранить данные о процессе выполнения в репозитории, вернуть Completable что бы был
//TODO явный callback и позволять прекращать загрузку через stopLoadChannels ( сохранив disposable )
fun loadChannels() {
//TODO customer repository should be observable
//я в сомнениях что лучше single в api или observable
ApiClient.publicApi.getTvChannels(CustomerRepository.customer.serviceSpec)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe({ channels -> channelsBehaviour.onNext(channels) }, { error -> channelsBehaviourError.onNext(error) })
}
fun loadDictionaries() {
//Блин не знаю как их загрузить, затем померджить
loadDictionariesErrorBehaviour.onNext(false)
ApiClient.publicApi.getTvCategories(CustomerRepository.customer.serviceSpec)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe({ categories -> categoriesBehaviour.onNext(categories) }, { error -> loadDictionariesErrorBehaviour.onNext(true) })
//TODO а они действительно идут параллельно? не нужно юзать flat map?
//TODO мне кажется что зип кидает error того кто первый кинет ошибку, а не позволяет обработать ошибки
//TODO или получить массив ответов
Singles.zip(
ApiClient.publicApi.getTvCategories(CustomerRepository.customer.serviceSpec).subscribeOn(Schedulers.io()),
ApiClient.publicApi.getTvGenres(CustomerRepository.customer.serviceSpec).subscribeOn(Schedulers.io()),
ApiClient.publicApi.getTvLanguages(CustomerRepository.customer.serviceSpec).subscribeOn(Schedulers.io()),
ApiClient.publicApi.getTvCountries(CustomerRepository.customer.serviceSpec).subscribeOn(Schedulers.io()),
{ categories, genres, languages, countries -> Dictionaries(categories, genres, languages, countries) }
)
.subscribeOn(Schedulers.io())
.doOnError({ _ -> loadDictionariesErrorBehaviour.onNext(true) })
.doOnSuccess { dictionaries ->
categoriesBehaviour.onNext(dictionaries.categories)
genresBehaviour.onNext(dictionaries.genres)
languagesBehaviour.onNext(dictionaries.languages)
countriesBehaviour.onNext(dictionaries.countries)
}
.subscribe()
}
fun loadChannelSources(channelId: String) {
ApiClient.privateApi.getTvSourcesById(channelId)
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.computation())
.subscribe(
{ channels ->
channelsSourcesBehaviour.value[channelId] = ChannelSources(channels.first().sources, false)
channelsSourcesBehaviour.onNext(channelsSourcesBehaviour.value)
},
{ _ ->
channelsSourcesBehaviour.value[channelId] = ChannelSources(null, true)
channelsSourcesBehaviour.onNext(channelsSourcesBehaviour.value)
}
)
}
fun channelsSourcesObservable(): Observable<Map<String, ChannelSources>> {
return channelsSourcesBehaviour.map { it -> it.toMap()}
}
//TODO Мне кажется что тут можно поиграть и использоваться трансформаторы - что бы репозиторий эмитил только необработанные данные
//TODO а наши view, могли его переиспользовать
//TODO я правильно использую sorted?
//TODO почему когда я делал pair приходилось делать not null проверки?
fun channelsObservable(): Observable<List<TvChannel>> {
return computedChannelsObservable
}
fun latestChannelsObservable(): Observable<List<TvChannel>> {
return latestChannelsObservable
}
fun popularChannelsObservable(): Observable<List<TvChannel>> {
return popularChannelsObservable
}
fun categoriesObservable(): Observable<List<Category>> {
return computedCategoriesObservable
}
fun genresObservable(): Observable<List<Genre>> {
return computedGenresObservable
}
fun languagesObservable(): Observable<List<Language>> {
return computedLanguageObservable
}
fun countriesObservable(): Observable<List<Country>> {
return computedCountriesObservable
}
companion object : SingletonHolderSingleArg<TvRepository, ConfigRepository>(::TvRepository)
}
package com.alphaott.webtv.client.android.ui.leanback.activities.tv.catalog
import android.arch.lifecycle.ViewModelProviders
import android.content.Context
import android.support.v17.leanback.widget.*
import com.alphaott.webtv.client.android.R
import com.alphaott.webtv.client.android.injection.WebTvViewModelFactory
import com.alphaott.webtv.client.android.ui.leanback.activities.BaseBrowseActivity
import com.alphaott.webtv.client.android.ui.leanback.activities.SearchActivity
import com.alphaott.webtv.client.android.ui.leanback.activities.tv.playback.TvChannelPlaybackActivity
import com.alphaott.webtv.client.android.ui.leanback.activities.tv.playback.TvChannelPlaybackViewModel
import com.alphaott.webtv.client.android.ui.leanback.presenters.ItemPresenterSelector
import com.alphaott.webtv.client.android.ui.leanback.presenters.NoItemsPresenter
import com.alphaott.webtv.client.android.ui.leanback.util.*
import com.alphaott.webtv.client.android.ui.util.ActivityUtil
import com.alphaott.webtv.client.api.entities.contentgroup.category.Category
import com.alphaott.webtv.client.api.entities.contentgroup.genre.Genre
import com.alphaott.webtv.client.api.entities.contentitem.channel.tv.TvChannel
import com.alphaott.webtv.client.api.entities.dictionary.country.Country
import com.alphaott.webtv.client.api.entities.dictionary.language.Language
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.Observables
fun TvCatalogViewModel.SelectedFilter.isEmpty(): Boolean {
return category == null && genre == null && language == null && country == null
}
class TvCatalogActivity() : BaseBrowseActivity() {
private val viewModel: TvCatalogViewModel by lazy(LazyThreadSafetyMode.NONE) {
ViewModelProviders
.of(this, WebTvViewModelFactory.getInstance(this))
.get(TvCatalogViewModel::class.java)
}
private val recentAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val favoritesAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val mostWatchedAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val popularAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val latestAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val filteredChannelsAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val categoriesAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val genresAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val languagesAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val countriesAdapter = ArrayObjectAdapter(ItemPresenterSelector)
private val recentRow by lazy {ListRow(IconHeaderItem(R.drawable.ic_recent, getString(R.string.recent)),recentAdapter)}
private val mostWatchedRow by lazy {ListRow(IconHeaderItem(R.drawable.ic_most_watched, getString(R.string.most_watched)), mostWatchedAdapter)}
private val favoritesRow by lazy {ListRow(IconHeaderItem(R.drawable.ic_favorites_category, getString(R.string.favorites)), favoritesAdapter)}
private val popularsRow by lazy {ListRow(IconHeaderItem(R.drawable.ic_popular, getString(R.string.popular)), popularAdapter)}
private val latestRow by lazy {ListRow(IconHeaderItem(R.drawable.ic_latest_additions, getString(R.string.latest_additions)), latestAdapter)}
private val channelsRow by lazy {ListRow(filteredChannelsAdapter)}
private val categoriesRow by lazy {ListRow(categoriesAdapter)}
private val genresRow by lazy {ListRow(genresAdapter)}
private val languagesRow by lazy {ListRow(languagesAdapter)}
private val countriesRow by lazy {ListRow(countriesAdapter)}
private val subscriptions = CompositeDisposable()
override val shouldShowSearch: Boolean = true
private val rowsPositions by lazy {
hashMapOf(
channelsRow to 1,
popularsRow to 2,
latestRow to 3,
recentRow to 4,
mostWatchedRow to 5,
favoritesRow to 6,
categoriesRow to 7,
genresRow to 8,
languagesRow to 9,
countriesRow to 10
)
}
private fun getRowInsertPosition(insertedRow: ListRow): Int {
if (listRowAdapter.indexOf(insertedRow) != -1) {
return -1
}
val firstLowerItemPosition = listRowAdapter.indexOfFirst { row ->
rowsPositions[insertedRow]!! < rowsPositions[row]!!
}
return if (firstLowerItemPosition != -1) firstLowerItemPosition else listRowAdapter.size()
}
override fun onSearchClicked() {
SearchActivity.start(this, SearchActivity.SEARCH_CHANNELS)
}
override fun onResume() {
super.onResume()
bindViewModel()
}
override fun onPause() {
super.onPause()
unBindViewModel()
}
private fun bindViewModel() {
val uiModel: TvCatalogUiModel = viewModel.getUiModel()
subscribeChannels(uiModel)
subscribeDictionaries(uiModel)
subscribeSelectedFilter(uiModel)
}
private fun unBindViewModel() {
subscriptions.dispose()
}
private fun subscribeChannels(uiModel: TvCatalogUiModel) {
subscriptions.add(uiModel.filteredChannels.subscribe { channels ->
filteredChannelsAdapter.setElements(
if (channels.isNotEmpty()) channels else listOf(NoItemsPresenter.Item)
)
})
subscriptions.add(uiModel.latestChannelsObservable.subscribe { channels ->
latestAdapter.setElements(
if (channels.isNotEmpty()) channels else listOf(NoItemsPresenter.Item)
)
})
subscriptions.add(uiModel.popularChannelsObservable.subscribe { channels ->
popularAdapter.setElements(
if (channels.isNotEmpty()) channels else listOf(NoItemsPresenter.Item)
)
})
subscriptions.add(uiModel.recentChannelsObservable.subscribe { channels ->
if (channels.isNotEmpty()) {
listRowAdapter.addIfNotPresent(getRowInsertPosition(recentRow), recentRow)
recentAdapter.setElements(
if (channels.isNotEmpty()) channels else listOf(NoItemsPresenter.Item)
)
} else {
listRowAdapter.remove(recentRow)
}
})
subscriptions.add(uiModel.mostWatchedChannelsObservable.subscribe { channels ->
if (channels.isNotEmpty()) {
listRowAdapter.addIfNotPresent(getRowInsertPosition(mostWatchedRow), mostWatchedRow)
mostWatchedAdapter.setElements(
if (channels.isNotEmpty()) channels else listOf(NoItemsPresenter.Item)
)
} else {
listRowAdapter.remove(mostWatchedRow)
}
})
subscriptions.add(uiModel.favoriteChannelsObservable.subscribe { channels ->
if (channels.isNotEmpty()) {
listRowAdapter.addIfNotPresent(getRowInsertPosition(favoritesRow), favoritesRow)
favoritesAdapter.setElements(
if (channels.isNotEmpty()) channels else listOf(NoItemsPresenter.Item)
)
} else {
listRowAdapter.remove(favoritesRow)
}
})
}
private fun subscribeDictionaries(uiModel: TvCatalogUiModel) {
subscriptions.add(
Observables.combineLatest(
uiModel.categories,
uiModel.selectedFilter,
{
categories, selectedFilter ->
if (selectedFilter.category == null && categories.size > 1) {
listRowAdapter.addIfNotPresent(getRowInsertPosition(categoriesRow), categoriesRow)
categoriesAdapter.setElements(
if (categories.isNotEmpty()) categories else listOf(NoItemsPresenter.Item)
)
} else {
listRowAdapter.remove(categoriesRow)
}
}
).subscribe()
)
subscriptions.add(
Observables.combineLatest(
uiModel.genres,
uiModel.selectedFilter,
{
genres, selectedFilter ->
if (selectedFilter.genre == null && genres.size > 1) {
listRowAdapter.addIfNotPresent(getRowInsertPosition(genresRow), genresRow)
genresAdapter.setElements(
if (genres.isNotEmpty()) genres else listOf(NoItemsPresenter.Item)
)
} else {
listRowAdapter.remove(genresRow)
}
}
).subscribe()
)
subscriptions.add(
Observables.combineLatest(
uiModel.countries,
uiModel.selectedFilter,
{
countries, selectedFilter ->
if (selectedFilter.country == null && countries.size > 1) {
listRowAdapter.addIfNotPresent(getRowInsertPosition(countriesRow), countriesRow)
countriesAdapter.setElements(
if (countries.isNotEmpty()) countries else listOf(NoItemsPresenter.Item)
)
} else {
listRowAdapter.remove(countriesRow)
}
}
).subscribe()
)
subscriptions.add(
Observables.combineLatest(
uiModel.languages,
uiModel.selectedFilter,
{
languages, selectedFilter ->
if (selectedFilter.language == null && languages.size > 1) {
listRowAdapter.addIfNotPresent(getRowInsertPosition(languagesRow), languagesRow)
languagesAdapter.setElements(
if (languages.isNotEmpty()) languages else listOf(NoItemsPresenter.Item)
)
} else {
listRowAdapter.remove(languagesRow)
}
}
).subscribe()
)
}
private fun subscribeSelectedFilter(uiModel: TvCatalogUiModel) {
subscriptions.add(uiModel.selectedFilter.subscribe { selectedFilter ->
if (selectedFilter.isEmpty()) {
listRowAdapter.addIfNotPresent(getRowInsertPosition(popularsRow), popularsRow)
listRowAdapter.addIfNotPresent(getRowInsertPosition(latestRow), latestRow)
listRowAdapter.remove(channelsRow)
} else {
listRowAdapter.remove(popularsRow)
listRowAdapter.remove(latestRow)
listRowAdapter.addIfNotPresent(getRowInsertPosition(channelsRow), channelsRow)
}
val channelsRowTitle = getString(R.string.placeholder_channels, getBaseTitle(selectedFilter)).trim()
val categoryRowTitle = getString(R.string.placeholder_by_category, getBaseTitle(selectedFilter)).trim()
val genresRowTitle = getString(R.string.placeholder_by_genre, getBaseTitle(selectedFilter)).trim()
val languageRowTitle = getString(R.string.placeholder_by_language, getBaseTitle(selectedFilter)).trim()
val countriesRowTitle = getString(R.string.placeholder_by_country, getBaseTitle(selectedFilter)).trim()
channelsRow.headerItem = IconHeaderItem(getBaseIcon(selectedFilter), channelsRowTitle)
categoriesRow.headerItem = IconHeaderItem(R.drawable.ic_categories, categoryRowTitle)
genresRow.headerItem = IconHeaderItem(R.drawable.ic_genres, genresRowTitle)
languagesRow.headerItem = IconHeaderItem(R.drawable.ic_languages_category, languageRowTitle)
countriesRow.headerItem = IconHeaderItem(R.drawable.ic_countries, countriesRowTitle)
title = getString(R.string.placeholder_channels, getBaseTitle(selectedFilter))
})
}
private fun getBaseTitle(selectedFilter: TvCatalogViewModel.SelectedFilter): String {
val builder = StringBuilder()
if (selectedFilter.category != null) builder.append(selectedFilter.category.title).append(" ")
if (selectedFilter.genre != null) builder.append(selectedFilter.genre.title).append(" ")
if (selectedFilter.language != null) builder.append(selectedFilter.language.name).append(" ")
if (selectedFilter.country != null) builder.append(selectedFilter.country.commonName)
return builder.trim().toString().trim()
}
private fun getBaseIcon(selectedFilter: TvCatalogViewModel.SelectedFilter): Int = when {
selectedFilter.country != null -> R.drawable.ic_countries
selectedFilter.language != null -> R.drawable.ic_languages_category
selectedFilter.genre != null -> R.drawable.ic_genres
selectedFilter.category != null -> R.drawable.ic_categories
else -> 0
}
override fun onItemClicked(itemViewHolder: Presenter.ViewHolder?, item: Any?, rowViewHolder: RowPresenter.ViewHolder?, row: Row?) {
//нам нужен только id канала и какой-либо идентификатор row из которого мы выбираем
//popular, latest, channels
when (item) {
is TvChannel -> {
val additionalRowType = when (row) {
popularsRow -> TvChannelPlaybackViewModel.RowTypes.POPULAR_ROW
latestRow -> TvChannelPlaybackViewModel.RowTypes.LATEST_ROW
channelsRow -> TvChannelPlaybackViewModel.RowTypes.ALL_ROW
else -> TvChannelPlaybackViewModel.RowTypes.NO
}
TvChannelPlaybackActivity.start(this, item, false, additionalRowType, itemViewHolder?.view)
}
is Category ->
viewModel.setCategoryFilter(item)
is Genre ->
viewModel.setGenreFilter(item)
is Language ->
viewModel.setLanguageFilter(item)
is Country ->
viewModel.setCountryFilter(item)
}
}
override fun onBackPressed() {
if (!viewModel.removeLastFilter()) {
super.onBackPressed()
}
}
companion object {
fun start(context: Context) =
ActivityUtil.on(context, TvCatalogActivity::class).start()
}
}
package com.alphaott.webtv.client.android.ui.leanback.activities.tv.catalog
import com.alphaott.webtv.client.api.entities.contentgroup.category.Category
import com.alphaott.webtv.client.api.entities.contentgroup.genre.Genre
import com.alphaott.webtv.client.api.entities.contentitem.channel.tv.TvChannel
import com.alphaott.webtv.client.api.entities.dictionary.country.Country
import com.alphaott.webtv.client.api.entities.dictionary.language.Language
import io.reactivex.Observable
data class TvCatalogUiModel(
val filteredChannels: Observable<List<TvChannel>>,
val latestChannelsObservable: Observable<List<TvChannel>>,
val popularChannelsObservable: Observable<List<TvChannel>>,
val recentChannelsObservable: Observable<List<TvChannel>>,
val mostWatchedChannelsObservable: Observable<List<TvChannel>>,
val favoriteChannelsObservable: Observable<List<TvChannel>>,
val categories: Observable<List<Category>>,
val genres: Observable<List<Genre>>,
val countries: Observable<List<Country>>,
val languages: Observable<List<Language>>,
val selectedFilter: Observable<TvCatalogViewModel.SelectedFilter>
)
package com.alphaott.webtv.client.android.ui.leanback.activities.tv.catalog
import android.arch.lifecycle.ViewModel
import com.alphaott.webtv.client.android.data.TvRepository
import com.alphaott.webtv.client.android.data.UserContent
import com.alphaott.webtv.client.api.entities.Filter
import com.alphaott.webtv.client.api.entities.contentgroup.category.Category
import com.alphaott.webtv.client.api.entities.contentgroup.genre.Genre
import com.alphaott.webtv.client.api.entities.contentitem.channel.tv.TvChannel
import com.alphaott.webtv.client.api.entities.dictionary.country.Country
import com.alphaott.webtv.client.api.entities.dictionary.language.Language
import io.reactivex.Observable
import io.reactivex.rxkotlin.Observables
import io.reactivex.subjects.BehaviorSubject
import java.util.*
class TvCatalogViewModel(
private val tvRepository: TvRepository, private val userContent: UserContent
) : ViewModel() {
data class SelectedFilter(
val category: Category?,
val genre: Genre?,
val language: Language?,
val country: Country?
)
private val selectedFilterBehaviour: BehaviorSubject<SelectedFilter> = BehaviorSubject.createDefault(
SelectedFilter(null, null, null, null)
)
//TODO может быть можно более наглядно сделать
private val filterOrder: Stack<Filter> = Stack()
private val filteredCategoriesObservable: Observable<List<Category>>
private val filteredGenresObservable: Observable<List<Genre>>
private val filteredCountriesObservable: Observable<List<Country>>
private val filteredLanguagesObservable: Observable<List<Language>>
private val filteredChannelsObservable: Observable<List<TvChannel>>
fun setCategoryFilter(category: Category) {
filterOrder.push(category)
selectedFilterBehaviour.onNext(selectedFilterBehaviour.value.copy(
category = category
))
}
fun setGenreFilter(genre: Genre) {
filterOrder.push(genre)
selectedFilterBehaviour.onNext(selectedFilterBehaviour.value.copy(
genre = genre
))
}
fun setCountryFilter(country: Country) {
filterOrder.push(country)
selectedFilterBehaviour.onNext(selectedFilterBehaviour.value.copy(
country = country
))
}
fun setLanguageFilter(language: Language) {
filterOrder.push(language)
selectedFilterBehaviour.onNext(selectedFilterBehaviour.value.copy(
language = language
))
}
fun removeLastFilter(): Boolean {
if (filterOrder.empty()) {
return false
}
val lastFilter = filterOrder.pop()
when (lastFilter) {
is Category -> selectedFilterBehaviour.onNext(selectedFilterBehaviour.value.copy(
category = null
))
is Genre -> selectedFilterBehaviour.onNext(selectedFilterBehaviour.value.copy(
genre = null
))
is Language -> selectedFilterBehaviour.onNext(selectedFilterBehaviour.value.copy(
language = null
))
is Country -> selectedFilterBehaviour.onNext(selectedFilterBehaviour.value.copy(
country = null
))
}
return true
}
//Тут мы делаем запрос на обновление
fun loadTvChannels() {
tvRepository.loadChannels()
}
fun loadDictionaries() {
tvRepository.loadDictionaries()
}
//TODO combine in combine latests
init {
filteredCategoriesObservable = Observables.combineLatest(
tvRepository.channelsObservable(),
tvRepository.categoriesObservable(),
{
channels, categories ->
categories
.filter { category -> channels.find { it.categories.contains(category.id) } != null }
}
).cache()
filteredGenresObservable = Observables.combineLatest(
tvRepository.channelsObservable(),
tvRepository.genresObservable(),
{
channels, genres ->
genres
.filter { genre -> channels.find { it.genres.contains(genre.id) } != null }
}
).cache()
filteredLanguagesObservable = Observables.combineLatest(
tvRepository.channelsObservable(),
tvRepository.languagesObservable(),
{
channels, languages ->
languages
.filter { language -> channels.find { it.languages.contains(language.id) } != null }
}
).cache()
filteredCountriesObservable = Observables.combineLatest(
tvRepository.channelsObservable(),
tvRepository.countriesObservable(),
{
channels, countries ->
countries
.filter { country -> channels.find { it.countries.contains(country.id) } != null }
}
).cache()
filteredChannelsObservable = Observables.combineLatest(
tvRepository.channelsObservable(),
selectedFilterBehaviour,
{
tvChannels, selectedFilter ->
tvChannels.filter { channel ->
(selectedFilter.category?.id == null || channel.categories.contains(selectedFilter.category.id))
&& (selectedFilter.genre?.id == null || channel.genres.contains(selectedFilter.genre.id))
&& (selectedFilter.language?.id == null || channel.languages.contains(selectedFilter.language.id))
&& (selectedFilter.country?.id == null || channel.countries.contains(selectedFilter.country.id))
}
}
).cache()
}
//TODO какая разница между zip и combine latest?
//Тут мы наблюдаем за моделями
fun getUiModel(): TvCatalogUiModel {
return TvCatalogUiModel(
filteredChannelsObservable,
tvRepository.latestChannelsObservable(),
tvRepository.popularChannelsObservable(),
userContent.recentChannels,
userContent.mostWatchedChannels,
userContent.favoriteChannels,
filteredCategoriesObservable,
filteredGenresObservable,
filteredCountriesObservable,
filteredLanguagesObservable,
selectedFilterBehaviour
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment