Created
May 19, 2018 21:05
-
-
Save Belobobr/eda0d6afc07bdb32b501058fda78e9af to your computer and use it in GitHub Desktop.
android sample rx-mvvm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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() | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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> | |
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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